@nestia/sdk
Version:
Nestia SDK and Swagger generator
178 lines (168 loc) • 6.4 kB
text/typescript
import { OpenApi } from "@samchon/openapi";
import { VariadicSingleton } from "tstl";
import { IJsDocTagInfo, IJsonSchemaCollection } from "typia";
import { JsonSchemasProgrammer } from "typia/lib/programmers/json/JsonSchemasProgrammer";
import { INestiaConfig } from "../../INestiaConfig";
import { ITypedHttpRouteParameter } from "../../structures/ITypedHttpRouteParameter";
import { SwaggerDescriptionComposer } from "./SwaggerDescriptionComposer";
export namespace SwaggerOperationParameterComposer {
export interface IProps<Parameter extends ITypedHttpRouteParameter> {
config: Omit<INestiaConfig.ISwaggerConfig, "output">;
document: OpenApi.IDocument;
schema: OpenApi.IJsonSchema;
jsDocTags: IJsDocTagInfo[];
parameter: Parameter;
}
export const compose = (
props: IProps<ITypedHttpRouteParameter>,
): OpenApi.IOperation.IParameter[] =>
props.parameter.category === "body"
? []
: props.parameter.category === "param"
? [path({ ...props, parameter: props.parameter })]
: props.parameter.category === "query"
? query({ ...props, parameter: props.parameter })
: header({ ...props, parameter: props.parameter });
export const body = (
props: Omit<IProps<ITypedHttpRouteParameter.IBody>, "config" | "document">,
): OpenApi.IOperation.IRequestBody => {
const description: string | undefined =
SwaggerDescriptionComposer.descriptionFromJsDocTag({
jsDocTags: props.jsDocTags,
tag: "param",
parameter: props.parameter.name,
});
return {
description: props.parameter.encrypted
? `${warning.get(!!description)}${description ?? ""}`
: description,
content: {
[props.parameter.contentType]: {
schema: props.schema,
example: props.parameter.example,
examples: props.parameter.examples,
},
},
required: props.parameter.metadata.isRequired(),
...(props.parameter.encrypted ? { "x-nestia-encrypted": true } : {}),
};
};
export const path = (
props: Omit<IProps<ITypedHttpRouteParameter.IParam>, "config" | "document">,
): OpenApi.IOperation.IParameter => ({
name: props.parameter.field,
in: "path",
schema: props.schema,
required: props.parameter.metadata.isRequired(),
...SwaggerDescriptionComposer.compose({
description:
props.parameter.description ??
props.parameter.jsDocTags.find((tag) => tag.name === "description")
?.text?.[0].text ??
props.jsDocTags
.find(
(tag) =>
tag.name === "param" &&
tag.text?.[0].text === props.parameter.name,
)
?.text?.map((e) => e.text)
.join("")
.substring(props.parameter.name.length) ??
null,
jsDocTags: props.parameter.jsDocTags,
kind: "title",
}),
example: props.parameter.example,
examples: props.parameter.examples,
});
export const query = (
props: IProps<ITypedHttpRouteParameter.IQuery>,
): OpenApi.IOperation.IParameter[] => decomposible(props);
export const header = (
props: IProps<ITypedHttpRouteParameter.IHeaders>,
): OpenApi.IOperation.IParameter[] => decomposible(props);
const decomposible = (
props: IProps<
ITypedHttpRouteParameter.IHeaders | ITypedHttpRouteParameter.IQuery
>,
): OpenApi.IOperation.IParameter[] => {
const param: OpenApi.IOperation.IParameter = {
name: props.parameter.field ?? props.parameter.name,
in: props.parameter.category === "query" ? "query" : "header",
schema: props.schema,
...SwaggerDescriptionComposer.compose({
description:
props.parameter.description ??
props.parameter.jsDocTags.find((tag) => tag.name === "description")
?.text?.[0].text ??
props.jsDocTags
.find(
(tag) =>
tag.name === "param" &&
tag.text?.[0].text === props.parameter.name,
)
?.text?.map((e) => e.text)
.join("")
.substring(props.parameter.name.length) ??
null,
jsDocTags: props.jsDocTags,
kind: "title",
}),
required: props.parameter.metadata.isRequired(),
example: props.parameter.example,
examples: props.parameter.examples,
};
if (
props.config.decompose === false ||
props.parameter.metadata.objects.length === 0
)
return [param];
return props.parameter.metadata.objects[0].type.properties
.filter((p) =>
p.jsDocTags.every(
(tag) => tag.name !== "hidden" && tag.name !== "ignore",
),
)
.map((p) => {
const json: IJsonSchemaCollection = JsonSchemasProgrammer.write({
version: "3.1",
metadatas: [p.value],
}) as IJsonSchemaCollection;
if (Object.keys(json.components.schemas ?? {}).length !== 0) {
props.document.components ??= {};
props.document.components.schemas ??= {};
Object.assign(
props.document.components.schemas,
json.components.schemas,
);
}
return {
name: p.key.constants[0].values[0].value as string,
in: props.parameter.category === "query" ? "query" : "header",
schema: json.schemas[0],
required: p.value.isRequired(),
...SwaggerDescriptionComposer.compose({
description: p.description ?? null,
jsDocTags: p.jsDocTags,
kind: "title",
}),
};
});
};
}
const warning = new VariadicSingleton((described: boolean): string => {
const summary = "Request body must be encrypted.";
const component =
"[EncryptedBody](https://github.com/samchon/@nestia/core#encryptedbody)";
const content: string[] = [
"## Warning",
"",
summary,
"",
`The request body data would be encrypted as "AES-128(256) / CBC mode / PKCS#5 Padding / Base64 Encoding", through the ${component} component.`,
"",
`Therefore, just utilize this swagger editor only for referencing. If you need to call the real API, using [SDK](https://github.com/samchon/nestia#software-development-kit) would be much better.`,
];
if (described === true) content.push("", "----------------", "", "");
return content.join("\n");
});