@nestia/sdk
Version:
Nestia SDK and Swagger generator
387 lines • 16.8 kB
JavaScript
;
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