@cap-js/openapi
Version:
CAP tool for OpenAPI
187 lines (163 loc) • 5.86 kB
JavaScript
const csdl2openapi = require('./csdl2openapi')
const cds = require('@sap/cds/lib');
const fs = require("fs");
const DEBUG = cds.debug('openapi');
module.exports = function processor(csn, options = {}) {
const edmOptions = Object.assign({
odataOpenapiHints: true, // hint to cds-compiler
edm4OpenAPI: true, // downgrades certain OData errors to warnings in cds-compiler
to: 'openapi' // hint to cds.compile.to.edm (usually set by CLI, but also do this in programmatic usages)
}, options)
// must not be part of function* otherwise thrown errors are swallowed
const csdl = cds.compile.to.edm(csn, edmOptions);
let openApiDocs = {};
if (csdl[Symbol.iterator]) { // generator function means multiple services
openApiDocs = _getOpenApiForMultipleServices(csdl, csn, options);
return _iterate(openApiDocs);
} else {
const openApiOptions = toOpenApiOptions(csdl, csn, options);
const serviceName = csdl.$EntityContainer.replace(/\.[^.]+$/, "");
openApiDocs = _getOpenApi(csdl, openApiOptions,serviceName);
if(Object.keys(openApiDocs).length===1){
return openApiDocs[serviceName];
}else{
return _iterate(openApiDocs);
}
}
}
function _getOpenApiForMultipleServices(csdl, csn, options) {
let openApiDocs = {};
for (let [content, metadata] of csdl) {
if (typeof content === "string") {
content = JSON.parse(content);
}
const openApiOptions = toOpenApiOptions(content, csn, options);
const openApiDocsForService = _getOpenApi(content, openApiOptions, metadata.file);
openApiDocs = { ...openApiDocs, ...openApiDocsForService };
}
return openApiDocs;
}
function* _iterate(openApiDocs) {
for (const key in openApiDocs) {
if (key != "") {
yield [openApiDocs[key], { file: key }];
} else {
yield [openApiDocs[key]];
}
}
}
function _getOpenApi(csdl, options, serviceName = "") {
const openApiDocs = {};
let filename;
const protocols = Object.keys(options["url"]);
protocols.forEach((protocol) => {
let sOptions = Object.assign({}, options);
let url = options["url"][protocol];
if (protocol == "rest" && !options["odataVersion"]) {
options["odataVersion"] = "4.01";
}
sOptions["url"] = url;
const openapi = csdl2openapi.csdl2openapi(csdl, sOptions);
if (protocols.length > 1) {
filename = serviceName + "." + protocol;
} else {
filename = serviceName;
}
openApiDocs[filename] = openapi;
});
return openApiDocs;
}
function toOpenApiOptions(csdl, csn, options = {}) {
const result = {};
for (const key in options) {
if (/^openapi:(.*)/.test(key) && RegExp.$1) {
result[RegExp.$1] = options[key];
} else if (key === "odata-version") {
result["odataVersion"] = options[key];
}
}
if (result["config-file"]) {
if (fs.existsSync(result["config-file"])) {
const fileContent = require(result["config-file"]);
Object.keys(fileContent).forEach((key) => {
if (key === "odata-version" && !result["odataVersion"]) {
result["odataVersion"] = fileContent[key];
} else if (key === "diagram") {
result["diagram"] = !result["diagram"] && fileContent[key] === "true";
} else if (!(key in result)) { // inline options take precedence
result[key] = JSON.stringify(fileContent[key]);
}
});
} else {
throw new Error("Error while parsing the openapi configuration file " + result["config-file"]);
}
}
const protocols = _getProtocols(csdl, csn, result["odataVersion"]);
if (result.url) {
const servicePaths = _servicePath(csdl, csn, protocols);
let keys = Object.keys(servicePaths);
let urls = {};
keys.forEach((protocol) => {
urls[protocol] = result.url.replace(
/\/*\$\{service-path\}/g,
servicePaths[protocol]
);
});
result.url = urls;
} else {
// no 'url' option set: infer URL from service path
result.url = _servicePath(csdl, csn, protocols); // /catalog
}
return result;
}
function _getProtocols(csdl, csn, odataVersion) {
if (csdl.$EntityContainer) {
const serviceName = csdl.$EntityContainer.replace(/\.[^.]+$/, "");
const service = csn.definitions[serviceName];
let protocols = [];
if(odataVersion === "4.01"){
protocols.push("rest");
}
else if(odataVersion === "4.0"){
protocols.push("odata");
}
else if (!service["@protocol"]) {
protocols.push("rest"); //taking rest as default in case no relevant protocol is there
} else if (service["@protocol"] === "none") {
// if @protocol is 'none' then throw an error
throw new Error(
`Service "${serviceName}" is annotated with @protocol:'none' which is not supported in openAPI generation.`
);
} else if (
service["@protocol"] === "rest" ||
service["@protocol"] === "odata" || service["@protocol"] === "odata-v4"
) {
protocols.push(service["@protocol"]);
} else if (Array.isArray(service["@protocol"])) {
service["@protocol"].forEach((protocol) => {
if (protocol === "rest" || protocol === "odata" || protocol === "odata-v4") {
protocols.push(protocol);
} else {
DEBUG?.(`"${protocol}" protocol is not supported`);
}
});
}
return protocols;
}
}
function _servicePath(csdl, csn, protocols) {
if (csdl.$EntityContainer) {
const serviceName = csdl.$EntityContainer.replace(/\.[^.]+$/, "");
const service = csn.definitions[serviceName];
let paths = {};
let path;
if (Array.isArray(protocols)) {
protocols.forEach((protocol) => {
service["@protocol"] = protocol;
path = cds.service.path4?.(service) || cds.serve.path4(service);
paths[protocol] = path;
});
}
return paths;
}
}