@nestia/sdk
Version:
Nestia SDK and Swagger generator
128 lines (118 loc) • 4.45 kB
text/typescript
import { SwaggerExample } from "@nestia/core";
import {
HEADERS_METADATA,
HTTP_CODE_METADATA,
INTERCEPTORS_METADATA,
} from "@nestjs/common/constants";
import typia from "typia";
import { JsonMetadataFactory } from "typia/lib/factories/JsonMetadataFactory";
import { HttpQueryProgrammer } from "typia/lib/programmers/http/HttpQueryProgrammer";
import { IReflectController } from "../structures/IReflectController";
import { IReflectHttpOperationSuccess } from "../structures/IReflectHttpOperationSuccess";
import { IReflectOperationError } from "../structures/IReflectOperationError";
import { IOperationMetadata } from "../transformers/IOperationMetadata";
import { TextPlainValidator } from "../transformers/TextPlainValidator";
export namespace ReflectHttpOperationResponseAnalyzer {
export interface IContext {
controller: IReflectController;
function: Function;
functionName: string;
httpMethod: string;
metadata: IOperationMetadata;
errors: IReflectOperationError[];
}
export const analyze = (
ctx: IContext,
): IReflectHttpOperationSuccess | null => {
const errors: Array<string | IOperationMetadata.IError> = [];
const report = () => {
ctx.errors.push({
file: ctx.controller.file,
class: ctx.controller.class.name,
function: ctx.functionName,
from: "return",
contents: errors,
});
return null;
};
const encrypted: boolean = hasInterceptor({
name: "EncryptedRouteInterceptor",
function: ctx.function,
});
const contentType: string | null = encrypted
? "text/plain"
: hasInterceptor({
name: "TypedQueryRouteInterceptor",
function: ctx.function,
})
? "application/x-www-form-urlencoded"
: (Reflect.getMetadata(HEADERS_METADATA, ctx.function)?.find(
(h: Record<string, string>) =>
typeof h?.name === "string" &&
typeof h?.value === "string" &&
h.name.toLowerCase() === "content-type",
)?.value ??
Reflect.getMetadata("swagger/apiProduces", ctx.function)?.[0] ??
(ctx.httpMethod === "HEAD" ? null : "application/json"));
const schema =
contentType === "application/json"
? ctx.metadata.success.primitive
: ctx.metadata.success.resolved;
if (schema.success === false) errors.push(...schema.errors);
if (ctx.httpMethod === "HEAD" && contentType !== null)
errors.push(`HEAD method must not have a content type.`);
if (
typia.is<IReflectHttpOperationSuccess["contentType"]>(contentType) ===
false
)
errors.push(
`@nestia/sdk does not support ${JSON.stringify(contentType)} content type.`,
);
if (errors.length) return report();
else if (
ctx.metadata.success.type === null ||
schema.success === false ||
!typia.is<IReflectHttpOperationSuccess["contentType"]>(contentType)
)
return null;
const example: SwaggerExample.IData<any> | undefined = Reflect.getMetadata(
"nestia/SwaggerExample/Response",
ctx.function,
);
return {
contentType: contentType,
encrypted,
status:
getStatus(ctx.function) ?? (ctx.httpMethod === "POST" ? 201 : 200),
type: ctx.metadata.success.type,
...schema.data,
validate:
contentType === "application/json" || encrypted === true
? JsonMetadataFactory.validate
: contentType === "application/x-www-form-urlencoded"
? HttpQueryProgrammer.validate
: contentType === "text/plain"
? TextPlainValidator.validate
: (meta) =>
meta.size()
? ["HEAD method must not have any return value."]
: [],
example: example?.example,
examples: example?.examples,
};
};
const getStatus = (func: Function): number | null => {
const text = Reflect.getMetadata(HTTP_CODE_METADATA, func);
if (text === undefined) return null;
const value: number = Number(text);
return isNaN(value) ? null : value;
};
const hasInterceptor = (props: {
name: string;
function: Function;
}): boolean => {
const meta = Reflect.getMetadata(INTERCEPTORS_METADATA, props.function);
if (Array.isArray(meta) === false) return false;
return meta.some((elem) => elem?.constructor?.name === props.name);
};
}