Skip to content

Commit

Permalink
Close #880: support multiple openapi versions generation.
Browse files Browse the repository at this point in the history
  • Loading branch information
samchon committed Jul 11, 2024
1 parent 8440b05 commit 4106018
Show file tree
Hide file tree
Showing 73 changed files with 2,250 additions and 17 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@nestia/station",
"version": "3.6.1",
"version": "3.7.0-dev.20240711",
"description": "Nestia station",
"scripts": {
"build": "node build/index.js",
Expand Down
6 changes: 3 additions & 3 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestia/core",
"version": "3.6.1",
"version": "3.7.0-dev.20240711",
"description": "Super-fast validation decorators of NestJS",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down Expand Up @@ -36,7 +36,7 @@
},
"homepage": "https://nestia.io",
"dependencies": {
"@nestia/fetcher": "^3.6.1",
"@nestia/fetcher": "^3.7.0-dev.20240711",
"@nestjs/common": ">=7.0.1",
"@nestjs/core": ">=7.0.1",
"@samchon/openapi": "^0.3.4",
Expand All @@ -53,7 +53,7 @@
"ws": "^7.5.3"
},
"peerDependencies": {
"@nestia/fetcher": ">=3.6.1",
"@nestia/fetcher": ">=3.7.0-dev.20240711",
"@nestjs/common": ">=7.0.1",
"@nestjs/core": ">=7.0.1",
"reflect-metadata": ">=0.1.12",
Expand Down
2 changes: 1 addition & 1 deletion packages/fetcher/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestia/fetcher",
"version": "3.6.1",
"version": "3.7.0-dev.20240711",
"description": "Fetcher library of Nestia SDK",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down
10 changes: 5 additions & 5 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestia/sdk",
"version": "3.6.1",
"version": "3.7.0-dev.20240711",
"description": "Nestia SDK and Swagger generator",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down Expand Up @@ -32,8 +32,8 @@
},
"homepage": "https://nestia.io",
"dependencies": {
"@nestia/fetcher": "^3.6.1",
"@nestia/core": "^3.6.1",
"@nestia/fetcher": "^3.7.0-dev.20240711",
"@nestia/core": "^3.7.0-dev.20240711",
"@samchon/openapi": "^0.3.4",
"cli": "^1.0.1",
"get-function-location": "^2.0.0",
Expand All @@ -47,8 +47,8 @@
"typia": "^6.4.3"
},
"peerDependencies": {
"@nestia/fetcher": ">=3.6.1",
"@nestia/core": ">=3.6.1",
"@nestia/fetcher": ">=3.7.0-dev.20240711",
"@nestia/core": ">=3.7.0-dev.20240711",
"@nestjs/common": ">=7.0.1",
"@nestjs/core": ">=7.0.1",
"reflect-metadata": ">=0.1.12",
Expand Down
12 changes: 12 additions & 0 deletions packages/sdk/src/INestiaConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,18 @@ export namespace INestiaConfig {
*/
output: string;

/**
* OpenAPI version.
*
* If you configure this property to be `2.0` or `3.0`, the newly generated
* `swagger.json` file would follow the specified OpenAPI version. The newly
* generated `swagger.json` file would be downgraded from the OpenAPI v3.1
* specification by {@link OpenApi.downgrade} method.
*
* @default 3.1
*/
openapi?: "2.0" | "3.0" | "3.1";

/**
* Whether to beautify JSON content or not.
*
Expand Down
15 changes: 12 additions & 3 deletions packages/sdk/src/generates/SwaggerGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OpenApi } from "@samchon/openapi";
import { OpenApi, OpenApiV3, SwaggerV2 } from "@samchon/openapi";
import fs from "fs";
import path from "path";
import { Singleton } from "tstl";
Expand Down Expand Up @@ -208,12 +208,21 @@ export namespace SwaggerGenerator {
}

// DO GENERATE
const document:
| OpenApi.IDocument
| SwaggerV2.IDocument
| OpenApiV3.IDocument =
config.openapi === "2.0"
? OpenApi.downgrade(swagger, config.openapi as "2.0")
: config.openapi === "3.0"
? OpenApi.downgrade(swagger, config.openapi as "3.0")
: swagger;
await fs.promises.writeFile(
location,
!config.beautify
? JSON.stringify(swagger)
? JSON.stringify(document)
: JSON.stringify(
swagger,
document,
null,
typeof config.beautify === "number" ? config.beautify : 2,
),
Expand Down
25 changes: 25 additions & 0 deletions test/features/openapi_v2/nestia.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { INestiaConfig } from "@nestia/sdk";
import { INestApplication } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";

import { ApplicationModule } from "./src/modules/ApplicationModule";

export const NESTIA_CONFIG: INestiaConfig = {
input: async () => {
const app: INestApplication = await NestFactory.create(ApplicationModule, {
logger: false,
});
return app;
},
output: "src/api",
swagger: {
output: "swagger.json",
openapi: "2.0",
security: {
bearer: {
type: "apiKey",
},
},
},
};
export default NESTIA_CONFIG;
20 changes: 20 additions & 0 deletions test/features/openapi_v2/src/Backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { INestApplication } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { Singleton } from "tstl";

import { ApplicationModule } from "./modules/ApplicationModule";

export class Backend {
public readonly application: Singleton<Promise<INestApplication>> =
new Singleton(() =>
NestFactory.create(ApplicationModule, { logger: false }),
);

public async open(): Promise<void> {
return (await this.application.get()).listen(37_000);
}

public async close(): Promise<void> {
return (await this.application.get()).close();
}
}
1 change: 1 addition & 0 deletions test/features/openapi_v2/src/api/HttpError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { HttpError } from "@nestia/fetcher";
1 change: 1 addition & 0 deletions test/features/openapi_v2/src/api/IConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { IConnection } from "@nestia/fetcher";
1 change: 1 addition & 0 deletions test/features/openapi_v2/src/api/Primitive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { Primitive } from "@nestia/fetcher";
204 changes: 204 additions & 0 deletions test/features/openapi_v2/src/api/functional/articles/comments/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/**
* @packageDocumentation
* @module api.functional.articles.comments
* @nestia Generated by Nestia - https://github.com/samchon/nestia
*/
//================================================================
import type { IConnection, Resolved, Primitive } from "@nestia/fetcher";
import { PlainFetcher } from "@nestia/fetcher/lib/PlainFetcher";
import type { Format } from "typia/lib/tags/Format";

import type { IBbsComment } from "../../../structures/IBbsComment";
import type { IPage } from "../../../structures/IPage";

/**
* @controller BbsCommentsController.index
* @path GET /:section/articles/:articleId/comments
* @nestia Generated by Nestia - https://github.com/samchon/nestia
*/
export async function index(
connection: IConnection,
section: string,
articleId: string & Format<"uuid">,
query: index.Query,
): Promise<index.Output> {
return PlainFetcher.fetch(connection, {
...index.METADATA,
template: index.METADATA.path,
path: index.path(section, articleId, query),
});
}
export namespace index {
export type Query = Resolved<IPage.IRequest>;
export type Output = Primitive<IPage<IBbsComment>>;

export const METADATA = {
method: "GET",
path: "/:section/articles/:articleId/comments",
request: null,
response: {
type: "application/json",
encrypted: false,
},
status: null,
} as const;

export const path = (
section: string,
articleId: string & Format<"uuid">,
query: index.Query,
) => {
const variables: URLSearchParams = new URLSearchParams();
for (const [key, value] of Object.entries(query as any))
if (undefined === value) continue;
else if (Array.isArray(value))
value.forEach((elem: any) => variables.append(key, String(elem)));
else variables.set(key, String(value));
const location: string = `/${encodeURIComponent(section ?? "null")}/articles/${encodeURIComponent(articleId ?? "null")}/comments`;
return 0 === variables.size
? location
: `${location}?${variables.toString()}`;
};
}

/**
* @controller BbsCommentsController.at
* @path GET /:section/articles/:articleId/comments/:id
* @nestia Generated by Nestia - https://github.com/samchon/nestia
*/
export async function at(
connection: IConnection,
section: string,
articleId: string & Format<"uuid">,
id: string & Format<"uuid">,
): Promise<at.Output> {
return PlainFetcher.fetch(connection, {
...at.METADATA,
template: at.METADATA.path,
path: at.path(section, articleId, id),
});
}
export namespace at {
export type Output = Primitive<IBbsComment>;

export const METADATA = {
method: "GET",
path: "/:section/articles/:articleId/comments/:id",
request: null,
response: {
type: "application/json",
encrypted: false,
},
status: null,
} as const;

export const path = (
section: string,
articleId: string & Format<"uuid">,
id: string & Format<"uuid">,
) =>
`/${encodeURIComponent(section ?? "null")}/articles/${encodeURIComponent(articleId ?? "null")}/comments/${encodeURIComponent(id ?? "null")}`;
}

/**
* @controller BbsCommentsController.store
* @path POST /:section/articles/:articleId/comments
* @nestia Generated by Nestia - https://github.com/samchon/nestia
*/
export async function store(
connection: IConnection,
section: string,
articleId: string & Format<"uuid">,
input: store.Input,
): Promise<store.Output> {
return PlainFetcher.fetch(
{
...connection,
headers: {
...connection.headers,
"Content-Type": "application/json",
},
},
{
...store.METADATA,
template: store.METADATA.path,
path: store.path(section, articleId),
},
input,
);
}
export namespace store {
export type Input = Primitive<IBbsComment.IStore>;
export type Output = Primitive<IBbsComment>;

export const METADATA = {
method: "POST",
path: "/:section/articles/:articleId/comments",
request: {
type: "application/json",
encrypted: false,
},
response: {
type: "application/json",
encrypted: false,
},
status: null,
} as const;

export const path = (section: string, articleId: string & Format<"uuid">) =>
`/${encodeURIComponent(section ?? "null")}/articles/${encodeURIComponent(articleId ?? "null")}/comments`;
}

/**
* @controller BbsCommentsController.update
* @path PUT /:section/articles/:articleId/comments/:id
* @nestia Generated by Nestia - https://github.com/samchon/nestia
*/
export async function update(
connection: IConnection,
section: string,
articleId: string & Format<"uuid">,
id: string & Format<"uuid">,
input: update.Input,
): Promise<update.Output> {
return PlainFetcher.fetch(
{
...connection,
headers: {
...connection.headers,
"Content-Type": "application/json",
},
},
{
...update.METADATA,
template: update.METADATA.path,
path: update.path(section, articleId, id),
},
input,
);
}
export namespace update {
export type Input = Primitive<IBbsComment.IStore>;
export type Output = Primitive<IBbsComment>;

export const METADATA = {
method: "PUT",
path: "/:section/articles/:articleId/comments/:id",
request: {
type: "application/json",
encrypted: false,
},
response: {
type: "application/json",
encrypted: false,
},
status: null,
} as const;

export const path = (
section: string,
articleId: string & Format<"uuid">,
id: string & Format<"uuid">,
) =>
`/${encodeURIComponent(section ?? "null")}/articles/${encodeURIComponent(articleId ?? "null")}/comments/${encodeURIComponent(id ?? "null")}`;
}
7 changes: 7 additions & 0 deletions test/features/openapi_v2/src/api/functional/articles/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @packageDocumentation
* @module api.functional.articles
* @nestia Generated by Nestia - https://github.com/samchon/nestia
*/
//================================================================
export * as comments from "./comments";
Loading

0 comments on commit 4106018

Please sign in to comment.