UNPKG

@nestia/sdk

Version:

Nestia SDK and Swagger generator

387 lines 16.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TypedHttpRouteAnalyzer = void 0; const utils_1 = require("@typia/utils"); const legacy_1 = require("../internal/legacy"); const PathUtil_1 = require("../utils/PathUtil"); var TypedHttpRouteAnalyzer; (function (TypedHttpRouteAnalyzer) { TypedHttpRouteAnalyzer.dictionary = (controllers) => { const individual = []; for (const c of controllers) for (const o of c.operations) { if (o.protocol !== "http") continue; if (o.success) individual.push(o.success.components); for (const p of o.parameters) individual.push(p.components); for (const e of Object.values(o.exceptions)) individual.push(e.components); } const components = legacy_1.MetadataComponents.from({ objects: uniqueComponents(individual.flatMap((c) => c.objects)), arrays: uniqueComponents(individual.flatMap((c) => c.arrays)), tuples: uniqueComponents(individual.flatMap((c) => c.tuples)), aliases: uniqueComponents(individual.flatMap((c) => c.aliases)), }); return components.dictionary; }; TypedHttpRouteAnalyzer.analyze = (props) => { const errors = []; const cast = (next, from, escape) => { const components = legacy_1.MetadataComponents.from(next.components); const metadata = legacy_1.MetadataSchema.from(next.metadata, components.dictionary); const metaErrors = legacy_1.MetadataFactory.validate({ options: { escape, constant: true, absorb: true, validate: next.validate, // @todo -> CHECK IN TYPIA }, functor: next.validate, // @todo -> CHECK IN TYPIA metadata, }); if (metaErrors.length) errors.push({ file: props.controller.file, class: props.controller.class.name, function: props.operation.name, from, contents: metaErrors.map((e) => ({ name: e.name, accessor: e.explore.object !== null ? join({ object: e.explore.object, key: e.explore.property, }) : null, messages: e.messages, })), }); return metadata; }; const exceptions = Object.fromEntries(Object.entries(props.operation.exceptions).map(([key, value]) => [ key, { status: value.status, description: value.description, example: value.example, examples: value.examples, type: value.type, metadata: cast(value, `exception (status: ${key})`, true), }, ])); const parameters = props.operation.parameters.map((p) => (Object.assign(Object.assign({}, p), { metadata: cast(p, `parameter (name: ${JSON.stringify(p.name)})`, p.category === "body" && (p.contentType === "application/json" || p.encrypted === true)) }))); const success = Object.assign(Object.assign({}, props.operation.success), { metadata: cast(props.operation.success, "success", props.operation.success.encrypted || props.operation.success.contentType === "application/json"), setHeaders: props.operation.jsDocTags .filter((t) => { var _a; return ((_a = t.text) === null || _a === void 0 ? void 0 : _a.length) && t.text[0].text && (t.name === "setHeader" || t.name === "assignHeaders"); }) .map((t) => { var _a; return t.name === "setHeader" ? { type: "setter", source: t.text[0].text.split(" ")[0].trim(), target: (_a = t.text[0].text.split(" ")[1]) === null || _a === void 0 ? void 0 : _a.trim(), } : { type: "assigner", source: t.text[0].text, }; }) }); if (errors.length) { props.errors.push(...errors); return []; } return props.paths.map((path) => { var _a, _b, _c; return (Object.assign(Object.assign({}, props.operation), { controller: props.controller, path, accessor: [...PathUtil_1.PathUtil.accessors(path), props.operation.name], exceptions, pathParameters: parameters.filter((p) => p.category === "param"), queryParameters: parameters .filter((p) => p.category === "query") .filter((p) => p.field !== null), headerParameters: parameters .filter((p) => p.category === "headers") .filter((p) => p.field !== null), queryObject: (_a = parameters .filter((p) => p.category === "query") .filter((p) => p.field === null)[0]) !== null && _a !== void 0 ? _a : null, body: (_b = parameters.filter((p) => p.category === "body")[0]) !== null && _b !== void 0 ? _b : null, headerObject: (_c = parameters .filter((p) => p.category === "headers") .filter((p) => p.field === null)[0]) !== null && _c !== void 0 ? _c : null, success, extensions: props.operation.extensions })); }); }; TypedHttpRouteAnalyzer.routeDictionary = (routes) => { renameDuplicateComponents(routes); const dictionary = { objects: new Map(), aliases: new Map(), arrays: new Map(), tuples: new Map(), }; for (const route of routes) { for (const p of [ ...route.pathParameters, ...route.queryParameters, ...route.headerParameters, ...(route.queryObject ? [route.queryObject] : []), ...(route.body ? [route.body] : []), ...(route.headerObject ? [route.headerObject] : []), ]) enrollMetadata(dictionary, p.metadata); for (const e of Object.values(route.exceptions)) enrollMetadata(dictionary, e.metadata); enrollMetadata(dictionary, route.success.metadata); } return dictionary; }; })(TypedHttpRouteAnalyzer || (exports.TypedHttpRouteAnalyzer = TypedHttpRouteAnalyzer = {})); const renameDuplicateComponents = (routes) => { var _a; const entries = []; for (const route of routes) for (const metadata of routeMetadatas(route)) collectComponentEntries(entries, route, metadata); const used = new Set(entries.map((e) => e.type.name)); const groups = new Map(); for (const entry of entries) { const key = `${entry.kind}:${entry.type.name}`; const group = groups.get(key); if (group === undefined) groups.set(key, [entry]); else group.push(entry); } for (const group of groups.values()) { const signatures = new Set(group.map((e) => e.signature)); if (signatures.size <= 1) continue; const renamed = new Map(); for (const entry of group) { const oldName = entry.type.name; const next = (_a = renamed.get(entry.signature)) !== null && _a !== void 0 ? _a : (() => { const prefix = normalizeComponentNamespace(entry.route.controller.class.name); const name = escapeComponentName(used, `${prefix}.${oldName}`); renamed.set(entry.signature, name); used.add(name); return name; })(); entry.type.name = next; } } for (const route of routes) for (const metadata of routeMetadatas(route)) clearMetadataNameCache(metadata); }; const routeMetadatas = (route) => [ ...[ ...route.pathParameters, ...route.queryParameters, ...route.headerParameters, ...(route.queryObject ? [route.queryObject] : []), ...(route.body ? [route.body] : []), ...(route.headerObject ? [route.headerObject] : []), ].map((p) => p.metadata), ...Object.values(route.exceptions).map((e) => e.metadata), route.success.metadata, ]; const collectComponentEntries = (entries, route, metadata, visited = createCollectVisited()) => { if (visited.schemas.has(metadata)) return; visited.schemas.add(metadata); if (metadata.rest !== null) collectComponentEntries(entries, route, metadata.rest, visited); if (metadata.escaped !== null) { collectComponentEntries(entries, route, metadata.escaped.original, visited); collectComponentEntries(entries, route, metadata.escaped.returns, visited); } for (const func of metadata.functions) { for (const p of func.parameters) collectComponentEntries(entries, route, p.type, visited); collectComponentEntries(entries, route, func.output, visited); } for (const set of metadata.sets) collectComponentEntries(entries, route, set.value, visited); for (const map of metadata.maps) { collectComponentEntries(entries, route, map.key, visited); collectComponentEntries(entries, route, map.value, visited); } for (const array of metadata.arrays) if (visited.arrays.has(array.type) === false) { visited.arrays.add(array.type); entries.push(componentEntry(route, "arrays", array.type)); collectComponentEntries(entries, route, array.type.value, visited); } for (const tuple of metadata.tuples) if (visited.tuples.has(tuple.type) === false) { visited.tuples.add(tuple.type); entries.push(componentEntry(route, "tuples", tuple.type)); for (const elem of tuple.type .elements) collectComponentEntries(entries, route, elem, visited); } for (const alias of metadata.aliases) if (visited.aliases.has(alias.type) === false) { visited.aliases.add(alias.type); entries.push(componentEntry(route, "aliases", alias.type)); collectComponentEntries(entries, route, alias.type.value, visited); } for (const obj of metadata.objects) if (visited.objects.has(obj.type) === false) { visited.objects.add(obj.type); entries.push(componentEntry(route, "objects", obj.type)); for (const p of obj.type .properties) { collectComponentEntries(entries, route, p.key, visited); collectComponentEntries(entries, route, p.value, visited); } } }; const componentEntry = (route, kind, type) => ({ kind, route, signature: componentSignature(type), type, }); const componentSignature = (type) => JSON.stringify(type, (key, value) => // `type` is the resolved back-reference attached by // `MetadataSchema.from`; it closes a cycle on recursive schemas. // `index` carries the per-route ordinal and is not part of the // component's identity. key === "index" || key === "type" ? undefined : value); const normalizeComponentNamespace = (name) => { const next = name.replace(/[^A-Za-z0-9_$]/g, "_"); return next.length ? next : "Route"; }; const escapeComponentName = (used, name) => used.has(name) ? escapeComponentName(used, `_${name}`) : name; const clearMetadataNameCache = (metadata, visited = new WeakSet()) => { if (visited.has(metadata)) return; visited.add(metadata); metadata.name_ = undefined; if (metadata.rest !== null) clearMetadataNameCache(metadata.rest, visited); if (metadata.escaped !== null) { clearMetadataNameCache(metadata.escaped.original, visited); clearMetadataNameCache(metadata.escaped.returns, visited); } for (const func of metadata.functions) { for (const p of func.parameters) clearMetadataNameCache(p.type, visited); clearMetadataNameCache(func.output, visited); } for (const set of metadata.sets) { set.name_ = undefined; clearMetadataNameCache(set.value, visited); } for (const map of metadata.maps) { map.name_ = undefined; clearMetadataNameCache(map.key, visited); clearMetadataNameCache(map.value, visited); } for (const array of metadata.arrays) { array.name_ = undefined; clearMetadataNameCache(array.type.value, visited); } for (const tuple of metadata.tuples) { tuple.name_ = undefined; for (const elem of tuple.type .elements) clearMetadataNameCache(elem, visited); } for (const alias of metadata.aliases) { alias.name_ = undefined; clearMetadataNameCache(alias.type.value, visited); } for (const obj of metadata.objects) { obj.name_ = undefined; for (const p of obj.type .properties) { clearMetadataNameCache(p.key, visited); clearMetadataNameCache(p.value, visited); } } }; const createCollectVisited = () => ({ aliases: new WeakSet(), arrays: new WeakSet(), objects: new WeakSet(), schemas: new WeakSet(), tuples: new WeakSet(), }); const uniqueComponents = (input) => { const dict = {}; for (const elem of input) { const oldbie = dict[elem.name]; if (oldbie === undefined) dict[elem.name] = elem; else if (elem.name.includes(".") || componentScore(oldbie) < componentScore(elem)) dict[elem.name] = elem; } return Object.values(dict); }; const componentScore = (input) => JSON.stringify(input, (key, value) => // Resolved-reference back-edges close cycles on recursive schemas; // skip them when scoring component "richness". key === "type" ? undefined : value).length; const enrollMetadata = (dictionary, metadata, visited = createVisitedMetadata()) => { if (visited.schemas.has(metadata)) return; visited.schemas.add(metadata); if (metadata.rest !== null) enrollMetadata(dictionary, metadata.rest, visited); if (metadata.escaped !== null) { enrollMetadata(dictionary, metadata.escaped.original, visited); enrollMetadata(dictionary, metadata.escaped.returns, visited); } for (const func of metadata.functions) { for (const p of func.parameters) enrollMetadata(dictionary, p.type, visited); enrollMetadata(dictionary, func.output, visited); } for (const set of metadata.sets) enrollMetadata(dictionary, set.value, visited); for (const map of metadata.maps) { enrollMetadata(dictionary, map.key, visited); enrollMetadata(dictionary, map.value, visited); } for (const array of metadata.arrays) if (enroll(dictionary.arrays, array.type)) enrollMetadata(dictionary, array.type.value, visited); for (const tuple of metadata.tuples) if (enroll(dictionary.tuples, tuple.type)) for (const elem of tuple.type .elements) enrollMetadata(dictionary, elem, visited); for (const alias of metadata.aliases) if (enroll(dictionary.aliases, alias.type)) enrollMetadata(dictionary, alias.type.value, visited); for (const obj of metadata.objects) if (enroll(dictionary.objects, obj.type)) for (const p of obj.type .properties) { enrollMetadata(dictionary, p.key, visited); enrollMetadata(dictionary, p.value, visited); } }; const enroll = (dict, elem) => { const oldbie = dict.get(elem.name); if (oldbie === elem) return false; if (oldbie === undefined) { dict.set(elem.name, elem); return true; } else if (elem.name.includes(".") || componentScore(oldbie) < componentScore(elem)) { dict.set(elem.name, elem); return true; } return false; }; const createVisitedMetadata = () => ({ schemas: new WeakSet(), }); const join = ({ object, key, }) => { if (key === null) return object.name; else if (typeof key === "object") return `${object.name}[key]`; else if (utils_1.NamingConvention.variable(key)) return `${object.name}.${key}`; return `${object.name}[${JSON.stringify(key)}]`; }; //# sourceMappingURL=TypedHttpRouteAnalyzer.js.map