UNPKG

@nestia/sdk

Version:

Nestia SDK and Swagger generator

205 lines (200 loc) 7.12 kB
import { MetadataFactory } from "typia/lib/factories/MetadataFactory"; import { IMetadata } from "typia/lib/schemas/metadata/IMetadata"; import { IMetadataComponents } from "typia/lib/schemas/metadata/IMetadataComponents"; import { IMetadataDictionary } from "typia/lib/schemas/metadata/IMetadataDictionary"; import { Metadata } from "typia/lib/schemas/metadata/Metadata"; import { MetadataComponents } from "typia/lib/schemas/metadata/MetadataComponents"; import { MetadataObjectType } from "typia/lib/schemas/metadata/MetadataObjectType"; import { Escaper } from "typia/lib/utils/Escaper"; import { IReflectController } from "../structures/IReflectController"; import { IReflectHttpOperation } from "../structures/IReflectHttpOperation"; import { IReflectOperationError } from "../structures/IReflectOperationError"; import { ITypedHttpRoute } from "../structures/ITypedHttpRoute"; import { ITypedHttpRouteException } from "../structures/ITypedHttpRouteException"; import { ITypedHttpRouteParameter } from "../structures/ITypedHttpRouteParameter"; import { ITypedHttpRouteSuccess } from "../structures/ITypedHttpRouteSuccess"; import { PathUtil } from "../utils/PathUtil"; export namespace TypedHttpRouteAnalyzer { export const dictionary = ( controllers: IReflectController[], ): IMetadataDictionary => { const individual: IMetadataComponents[] = []; 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: MetadataComponents = MetadataComponents.from({ objects: Object.values( Object.fromEntries( individual.map((c) => c.objects.map((o) => [o.name, o])).flat(), ), ), arrays: Object.values( Object.fromEntries( individual.map((c) => c.arrays.map((a) => [a.name, a])).flat(), ), ), tuples: Object.values( Object.fromEntries( individual.map((c) => c.tuples.map((t) => [t.name, t])).flat(), ), ), aliases: Object.values( Object.fromEntries( individual.map((c) => c.aliases.map((a) => [a.name, a])).flat(), ), ), }); return components.dictionary; }; export const analyze = (props: { controller: IReflectController; errors: IReflectOperationError[]; dictionary: IMetadataDictionary; operation: IReflectHttpOperation; paths: string[]; }): ITypedHttpRoute[] => { const errors: IReflectOperationError[] = []; const cast = ( next: { metadata: IMetadata; validate: MetadataFactory.Validator; }, from: string, escape: boolean, ): Metadata => { const metadata: Metadata = Metadata.from(next.metadata, props.dictionary); const metaErrors: MetadataFactory.IError[] = 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: Record< number | "2XX" | "3XX" | "4XX" | "5XX", ITypedHttpRouteException > = Object.fromEntries( Object.entries(props.operation.exceptions).map(([key, value]) => [ key as any, { status: value.status, description: value.description, example: value.example, examples: value.examples, type: value.type, metadata: cast(value, `exception (status: ${key})`, true), }, ]), ); const parameters: ITypedHttpRouteParameter[] = props.operation.parameters.map((p) => ({ ...p, metadata: cast( p, `parameter (name: ${JSON.stringify(p.name)})`, p.category === "body" && (p.contentType === "application/json" || p.encrypted === true), ), })); const success: ITypedHttpRouteSuccess = { ...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) => t.text?.length && t.text[0].text && (t.name === "setHeader" || t.name === "assignHeaders"), ) .map((t) => t.name === "setHeader" ? { type: "setter", source: t.text![0].text.split(" ")[0].trim(), target: t.text![0].text.split(" ")[1]?.trim(), } : { type: "assigner", source: t.text![0].text, }, ), }; if (errors.length) { props.errors.push(...errors); return []; } return props.paths.map( (path) => ({ ...props.operation, controller: props.controller, path, accessor: [...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: parameters .filter((p) => p.category === "query") .filter((p) => p.field === null)[0] ?? null, body: parameters.filter((p) => p.category === "body")[0] ?? null, headerObject: parameters .filter((p) => p.category === "headers") .filter((p) => p.field === null)[0] ?? null, success, extensions: props.operation.extensions, }) satisfies ITypedHttpRoute, ); }; } const join = ({ object, key, }: { object: MetadataObjectType; key: string | object | null; }) => { if (key === null) return object.name; else if (typeof key === "object") return `${object.name}[key]`; else if (Escaper.variable(key)) return `${object.name}.${key}`; return `${object.name}[${JSON.stringify(key)}]`; };