UNPKG

@samchon/openapi

Version:

OpenAPI definitions and converters for 'typia' and 'nestia'.

377 lines (376 loc) 18.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HttpMigrateRouteComposer = void 0; const EndpointUtil_1 = require("../../utils/EndpointUtil"); const Escaper_1 = require("../../utils/Escaper"); const OpenApiTypeChecker_1 = require("../../utils/OpenApiTypeChecker"); var HttpMigrateRouteComposer; (function (HttpMigrateRouteComposer) { HttpMigrateRouteComposer.compose = (props) => { var _a, _b, _c, _d, _e; //---- // REQUEST AND RESPONSE BODY //---- const body = emplaceBodySchema("request")((schema) => emplaceReference({ document: props.document, name: EndpointUtil_1.EndpointUtil.pascal(`I/Api/${props.path}`) + "." + EndpointUtil_1.EndpointUtil.pascal(`${props.method}/Body`), schema, }))(props.operation.requestBody); const success = (() => { var _a, _b, _c, _d, _e, _f, _g; const body = emplaceBodySchema("response")((schema) => emplaceReference({ document: props.document, name: EndpointUtil_1.EndpointUtil.pascal(`I/Api/${props.path}`) + "." + EndpointUtil_1.EndpointUtil.pascal(`${props.method}/Response`), schema, }))((_d = (_b = (_a = props.operation.responses) === null || _a === void 0 ? void 0 : _a["201"]) !== null && _b !== void 0 ? _b : (_c = props.operation.responses) === null || _c === void 0 ? void 0 : _c["200"]) !== null && _d !== void 0 ? _d : (_e = props.operation.responses) === null || _e === void 0 ? void 0 : _e.default); return body ? Object.assign(Object.assign({}, body), { status: ((_f = props.operation.responses) === null || _f === void 0 ? void 0 : _f["201"]) ? "201" : ((_g = props.operation.responses) === null || _g === void 0 ? void 0 : _g["200"]) ? "200" : "default" }) : body; })(); const failures = []; if (body === false) failures.push(`supports only "application/json", "application/x-www-form-urlencoded", "multipart/form-data" and "text/plain" content type in the request body.`); if (success === false) failures.push(`supports only "application/json", "application/x-www-form-urlencoded" and "text/plain" content type in the response body.`); //---- // HEADERS AND QUERY //--- const [headers, query] = ["header", "query"].map((type) => { var _a, _b, _c, _d; // FIND TARGET PARAMETERS const parameters = ((_a = props.operation.parameters) !== null && _a !== void 0 ? _a : []).filter((p) => p.in === type); if (parameters.length === 0) return null; // CHECK PARAMETER TYPES -> TO BE OBJECT const objects = parameters .map((p) => { var _a, _b; return OpenApiTypeChecker_1.OpenApiTypeChecker.isObject(p.schema) ? p.schema : OpenApiTypeChecker_1.OpenApiTypeChecker.isReference(p.schema) && OpenApiTypeChecker_1.OpenApiTypeChecker.isObject((_b = (_a = props.document.components.schemas) === null || _a === void 0 ? void 0 : _a[p.schema.$ref.replace(`#/components/schemas/`, ``)]) !== null && _b !== void 0 ? _b : {}) ? p.schema : null; }) .filter((s) => !!s); const primitives = parameters.filter((p) => OpenApiTypeChecker_1.OpenApiTypeChecker.isBoolean(p.schema) || OpenApiTypeChecker_1.OpenApiTypeChecker.isInteger(p.schema) || OpenApiTypeChecker_1.OpenApiTypeChecker.isNumber(p.schema) || OpenApiTypeChecker_1.OpenApiTypeChecker.isString(p.schema) || OpenApiTypeChecker_1.OpenApiTypeChecker.isArray(p.schema) || OpenApiTypeChecker_1.OpenApiTypeChecker.isTuple(p.schema)); const out = (elem) => (Object.assign(Object.assign({}, elem), { name: type, key: type, title: () => elem.title, description: () => elem.description, example: () => elem.example, examples: () => elem.examples })); if (objects.length === 1 && primitives.length === 0) return out(parameters[0]); else if (objects.length > 1) { failures.push(`${type} typed parameters must be only one object type`); return false; } // GATHER TO OBJECT TYPE const dto = objects[0] ? OpenApiTypeChecker_1.OpenApiTypeChecker.isObject(objects[0]) ? objects[0] : ((_b = props.document.components.schemas) !== null && _b !== void 0 ? _b : {})[objects[0].$ref.replace(`#/components/schemas/`, ``)] : null; const entire = [ ...objects.map((o) => { var _a; return OpenApiTypeChecker_1.OpenApiTypeChecker.isObject(o) ? o : (_a = props.document.components.schemas) === null || _a === void 0 ? void 0 : _a[o.$ref.replace(`#/components/schemas/`, ``)]; }), { type: "object", properties: Object.fromEntries([ ...primitives.map((p) => { var _a; return [ p.name, Object.assign(Object.assign({}, p.schema), { description: (_a = p.schema.description) !== null && _a !== void 0 ? _a : p.description }), ]; }), ...(dto ? Object.entries((_c = dto.properties) !== null && _c !== void 0 ? _c : {}) : []), ]), required: [ ...new Set([ ...primitives.filter((p) => p.required).map((p) => p.name), ...((_d = dto === null || dto === void 0 ? void 0 : dto.required) !== null && _d !== void 0 ? _d : []), ]), ], }, ]; return parameters.length === 0 ? null : out({ schema: emplaceReference({ document: props.document, name: EndpointUtil_1.EndpointUtil.pascal(`I/Api/${props.path}`) + "." + EndpointUtil_1.EndpointUtil.pascal(`${props.method}/${type}`), schema: { type: "object", properties: Object.fromEntries([ ...new Map(entire .map((o) => { var _a; return Object.entries((_a = o.properties) !== null && _a !== void 0 ? _a : {}).map(([name, schema]) => { var _a; return [ name, Object.assign(Object.assign({}, schema), { description: (_a = schema.description) !== null && _a !== void 0 ? _a : schema.description }), ]; }); }) .flat()), ]), required: [ ...new Set(entire.map((o) => { var _a; return (_a = o.required) !== null && _a !== void 0 ? _a : []; }).flat()), ], }, }), }); }); //---- // PATH PARAMETERS //---- const parameterNames = EndpointUtil_1.EndpointUtil.splitWithNormalization(props.emendedPath) .filter((str) => str[0] === ":") .map((str) => str.substring(1)); const pathParameters = ((_a = props.operation.parameters) !== null && _a !== void 0 ? _a : []).filter((p) => p.in === "path"); if (parameterNames.length !== pathParameters.length) if (pathParameters.length < parameterNames.length && pathParameters.every((p) => p.name !== undefined && parameterNames.includes(p.name))) { for (const name of parameterNames) if (pathParameters.find((p) => p.name === name) === undefined) pathParameters.push({ name, in: "path", schema: { type: "string" }, }); pathParameters.sort((a, b) => parameterNames.indexOf(a.name) - parameterNames.indexOf(b.name)); props.operation.parameters = [ ...pathParameters, ...((_b = props.operation.parameters) !== null && _b !== void 0 ? _b : []).filter((p) => p.in !== "path"), ]; } else failures.push("number of path parameters are not matched with its full path."); if (failures.length) return failures; const parameters = ((_c = props.operation.parameters) !== null && _c !== void 0 ? _c : []) .filter((p) => p.in === "path") .map((p, i) => ({ // FILL KEY NAME IF NOT EXISTsS name: parameterNames[i], key: (() => { let key = EndpointUtil_1.EndpointUtil.normalize(parameterNames[i]); if (Escaper_1.Escaper.variable(key)) return key; while (true) { key = "_" + key; if (!parameterNames.some((s) => s === key)) return key; } })(), schema: p.schema, parameter: () => p, })); return { method: props.method, path: props.path, emendedPath: props.emendedPath, accessor: ["@lazy"], parameters: ((_d = props.operation.parameters) !== null && _d !== void 0 ? _d : []) .filter((p) => p.in === "path") .map((p, i) => ({ // FILL KEY NAME IF NOT EXISTsS name: parameterNames[i], key: (() => { let key = EndpointUtil_1.EndpointUtil.normalize(parameterNames[i]); if (Escaper_1.Escaper.variable(key)) return key; while (true) { key = "_" + key; if (!parameterNames.some((s) => s === key)) return key; } })(), schema: p.schema, parameter: () => p, })), headers: headers || null, query: query || null, body: body || null, success: success || null, exceptions: Object.fromEntries(Object.entries((_e = props.operation.responses) !== null && _e !== void 0 ? _e : {}) .filter(([key]) => key !== "200" && key !== "201" && key !== "default") .map(([status, response]) => { var _a, _b, _c; return [ status, { schema: ((_c = (_b = (_a = response.content) === null || _a === void 0 ? void 0 : _a["application/json"]) === null || _b === void 0 ? void 0 : _b.schema) !== null && _c !== void 0 ? _c : {}), response: () => response, media: () => { var _a, _b; return ((_b = (_a = response.content) === null || _a === void 0 ? void 0 : _a["application/json"]) !== null && _b !== void 0 ? _b : {}); }, }, ]; })), comment: () => writeRouteComment({ operation: props.operation, parameters, query: query || null, body: body || null, }), operation: () => props.operation, }; }; const writeRouteComment = (props) => { var _a, _b, _c, _d, _e, _f; const commentTags = []; const add = (text) => { if (commentTags.every((line) => line !== text)) commentTags.push(text); }; let description = (_a = props.operation.description) !== null && _a !== void 0 ? _a : ""; if (props.operation.summary) { const emended = props.operation.summary.endsWith(".") ? props.operation.summary : props.operation.summary + "."; if (!!description.length && !description.startsWith(props.operation.summary)) description = `${emended}\n${description}`; } description = description .split("\n") .map((s) => s.trim()) .join("\n"); for (const p of (_b = props.parameters) !== null && _b !== void 0 ? _b : []) { const param = p.parameter(); if (param.description || param.title) { const text = ((_c = param.description) !== null && _c !== void 0 ? _c : param.title); add(`@param ${p.name} ${writeIndented(text, p.name.length + 8)}`); } } if ((_e = (_d = props.body) === null || _d === void 0 ? void 0 : _d.description()) === null || _e === void 0 ? void 0 : _e.length) add(`@param body ${writeIndented(props.body.description(), 12)}`); for (const security of (_f = props.operation.security) !== null && _f !== void 0 ? _f : []) for (const [name, scopes] of Object.entries(security)) add(`@security ${[name, ...scopes].join("")}`); if (props.operation.tags) props.operation.tags.forEach((name) => add(`@tag ${name}`)); if (props.operation.deprecated) add("@deprecated"); description = description.length ? commentTags.length ? `${description}\n\n${commentTags.join("\n")}` : description : commentTags.join("\n"); description = description.split("*/").join("*\\/"); return description; }; const writeIndented = (text, spaces) => text .split("\n") .map((s) => s.trim()) .map((s, i) => (i === 0 ? s : `${" ".repeat(spaces)}${s}`)) .join("\n"); const emplaceBodySchema = (from) => (emplacer) => (meta) => { if (!(meta === null || meta === void 0 ? void 0 : meta.content)) return null; const entries = Object.entries(meta.content).filter(([_, v]) => !!v); const json = entries.find((e) => meta["x-nestia-encrypted"] === true ? e[0].includes("text/plain") || e[0].includes("application/json") : e[0].includes("application/json") || e[0].includes("*/*")); if (json) { const { schema } = json[1]; return schema || from === "response" ? { type: "application/json", name: "body", key: "body", schema: schema ? isNotObjectLiteral(schema) ? schema : emplacer(schema) : {}, description: () => meta.description, media: () => json[1], "x-nestia-encrypted": meta["x-nestia-encrypted"], } : null; } const query = entries.find((e) => e[0].includes("application/x-www-form-urlencoded")); if (query) { const { schema } = query[1]; return schema || from === "response" ? { type: "application/x-www-form-urlencoded", name: "body", key: "body", schema: schema ? isNotObjectLiteral(schema) ? schema : emplacer(schema) : {}, description: () => meta.description, media: () => query[1], } : null; } const text = entries.find((e) => e[0].includes("text/plain")); if (text) return { type: "text/plain", name: "body", key: "body", schema: { type: "string" }, description: () => meta.description, media: () => text[1], }; if (from === "request") { const multipart = entries.find((e) => e[0].includes("multipart/form-data")); if (multipart) { const { schema } = multipart[1]; return { type: "multipart/form-data", name: "body", key: "body", schema: schema ? isNotObjectLiteral(schema) ? schema : emplacer(schema) : {}, description: () => meta.description, media: () => multipart[1], }; } } return false; }; const emplaceReference = (props) => { var _a; var _b; (_a = (_b = props.document.components).schemas) !== null && _a !== void 0 ? _a : (_b.schemas = {}); props.document.components.schemas[props.name] = props.schema; return { $ref: `#/components/schemas/${props.name}`, }; }; const isNotObjectLiteral = (schema) => OpenApiTypeChecker_1.OpenApiTypeChecker.isReference(schema) || OpenApiTypeChecker_1.OpenApiTypeChecker.isBoolean(schema) || OpenApiTypeChecker_1.OpenApiTypeChecker.isNumber(schema) || OpenApiTypeChecker_1.OpenApiTypeChecker.isString(schema) || OpenApiTypeChecker_1.OpenApiTypeChecker.isUnknown(schema) || (OpenApiTypeChecker_1.OpenApiTypeChecker.isOneOf(schema) && schema.oneOf.every(isNotObjectLiteral)) || (OpenApiTypeChecker_1.OpenApiTypeChecker.isArray(schema) && isNotObjectLiteral(schema.items)); })(HttpMigrateRouteComposer || (exports.HttpMigrateRouteComposer = HttpMigrateRouteComposer = {}));