UNPKG

@nestia/sdk

Version:

Nestia SDK and Swagger generator

210 lines (200 loc) 6.74 kB
import ts from "typescript"; import { CommentFactory } from "typia/lib/factories/CommentFactory"; import { MetadataCollection } from "typia/lib/factories/MetadataCollection"; import { MetadataFactory } from "typia/lib/factories/MetadataFactory"; import { TypeFactory } from "typia/lib/factories/TypeFactory"; import { Metadata } from "typia/lib/schemas/metadata/Metadata"; import { MetadataObjectType } from "typia/lib/schemas/metadata/MetadataObjectType"; import { ValidationPipe } from "typia/lib/typings/ValidationPipe"; import { Escaper } from "typia/lib/utils/Escaper"; import { ImportAnalyzer } from "../analyses/ImportAnalyzer"; import { MetadataUtil } from "../utils/MetadataUtil"; import { IOperationMetadata } from "./IOperationMetadata"; import { ISdkOperationTransformerContext } from "./ISdkOperationTransformerContext"; export namespace SdkOperationProgrammer { export interface IProps { context: ISdkOperationTransformerContext; generics: WeakMap<ts.Type, ts.Type>; node: ts.MethodDeclaration; symbol: ts.Symbol | undefined; exceptions: ts.TypeNode[]; } export const write = (p: IProps): IOperationMetadata => { return { parameters: p.node.parameters.map((parameter, index) => writeParameter({ context: p.context, generics: p.generics, parameter, index, }), ), success: writeResponse({ context: p.context, generics: p.generics, type: getReturnType({ checker: p.context.checker, signature: p.context.checker.getSignatureFromDeclaration(p.node), }), }), exceptions: p.exceptions.map((e) => writeResponse({ context: p.context, generics: p.generics, type: p.context.checker.getTypeFromTypeNode(e), }), ), jsDocTags: p.symbol?.getJsDocTags() ?? [], description: p.symbol ? (CommentFactory.description(p.symbol) ?? null) : null, }; }; const writeParameter = (props: { context: ISdkOperationTransformerContext; generics: WeakMap<ts.Type, ts.Type>; parameter: ts.ParameterDeclaration; index: number; }): IOperationMetadata.IParameter => { const symbol: ts.Symbol | undefined = props.context.checker.getSymbolAtLocation(props.parameter); const common: IOperationMetadata.IResponse = writeResponse({ context: props.context, generics: props.generics, type: props.context.checker.getTypeFromTypeNode( props.parameter.type ?? TypeFactory.keyword("any"), ) ?? null, }); const optional: boolean = props.parameter.questionToken !== undefined; if (common.primitive.success) common.primitive.data.metadata.optional = optional; if (common.resolved.success) common.resolved.data.metadata.optional = optional; return { ...common, name: props.parameter.name.getText(), index: props.index, description: (symbol && CommentFactory.description(symbol)) ?? null, jsDocTags: symbol?.getJsDocTags() ?? [], }; }; const writeResponse = (p: { context: ISdkOperationTransformerContext; generics: WeakMap<ts.Type, ts.Type>; type: ts.Type | null; }): IOperationMetadata.IResponse => { const analyzed: ImportAnalyzer.IOutput = p.type ? ImportAnalyzer.analyze(p.context.checker, p.generics, p.type) : { type: { name: "any" }, imports: [], }; const [primitive, resolved] = [true, false].map((escape) => MetadataFactory.analyze({ checker: p.context.checker, transformer: p.context.transformer, options: { escape, constant: true, absorb: true, }, collection: p.context.collection, type: p.type, }), ); return { ...analyzed, primitive: writeSchema({ collection: p.context.collection, result: primitive, }), resolved: writeSchema({ collection: p.context.collection, result: resolved, }), }; }; const writeSchema = (p: { collection: MetadataCollection; result: ValidationPipe<Metadata, MetadataFactory.IError>; }): ValidationPipe<IOperationMetadata.ISchema, IOperationMetadata.IError> => { if (p.result.success === false) return { success: false, errors: p.result.errors.map((e) => ({ name: e.name, accessor: e.explore.object !== null ? join({ object: e.explore.object, key: e.explore.property, }) : null, messages: e.messages, })), }; const visited: Set<string> = iterateVisited(p.result.data); return { success: true, data: { components: { objects: p.collection .objects() .filter((o) => visited.has(o.name)) .map((o) => o.toJSON()), aliases: p.collection .aliases() .filter((a) => visited.has(a.name)) .map((a) => a.toJSON()), arrays: p.collection .arrays() .filter((a) => visited.has(a.name)) .map((a) => a.toJSON()), tuples: p.collection .tuples() .filter((t) => visited.has(t.name)) .map((t) => t.toJSON()), }, metadata: p.result.data.toJSON(), }, }; }; const getReturnType = (p: { checker: ts.TypeChecker; signature: ts.Signature | undefined; }): ts.Type | null => { const type: ts.Type | null = (p.signature && p.checker.getReturnTypeOfSignature(p.signature)) ?? null; if (type === null) return null; else if (type.symbol?.name === "Promise") { const generic: readonly ts.Type[] = p.checker.getTypeArguments( type as ts.TypeReference, ); return generic[0] ?? null; } return type; }; } const iterateVisited = (metadata: Metadata): Set<string> => { const names: Set<string> = new Set(); MetadataUtil.visit((m) => { for (const alias of m.aliases) names.add(alias.type.name); for (const array of m.arrays) names.add(array.type.name); for (const tuple of m.tuples) names.add(tuple.type.name); for (const object of m.objects) names.add(object.type.name); })(metadata); return names; }; 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)}]`; };