From 76b59e2976f2a05274da5c7bc5ac582abb290381 Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Sun, 14 Apr 2024 20:42:20 +0900 Subject: [PATCH 1/4] Make `OpenApi.components.schemas` to be optional. It is the regular spec of OpenAPI v3.1. --- package.json | 2 +- src/OpenApi.ts | 2 +- src/internal/OpenApiV3Converter.ts | 12 +++++++----- src/internal/OpenApiV3_1Converter.ts | 12 +++++++----- src/internal/SwaggerV2Converter.ts | 12 +++++++----- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 1ab36a8..8066a8d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@samchon/openapi", - "version": "0.1.8", + "version": "0.1.9", "description": "OpenAPI definitions and converters for 'typia' and 'nestia'.", "main": "./lib/index.js", "typings": "./lib/index.d.ts", diff --git a/src/OpenApi.ts b/src/OpenApi.ts index fbc7f86..f11f5c8 100644 --- a/src/OpenApi.ts +++ b/src/OpenApi.ts @@ -173,7 +173,7 @@ export namespace OpenApi { SCHEMA DEFINITIONS ----------------------------------------------------------- */ export interface IComponents { - schemas: Record; + schemas?: Record; securitySchemes?: Record; } diff --git a/src/internal/OpenApiV3Converter.ts b/src/internal/OpenApiV3Converter.ts index 7d43e28..e9dc017 100644 --- a/src/internal/OpenApiV3Converter.ts +++ b/src/internal/OpenApiV3Converter.ts @@ -185,11 +185,13 @@ export namespace OpenApiV3Converter { const convertComponents = ( input: OpenApiV3.IComponents, ): OpenApi.IComponents => ({ - schemas: Object.fromEntries( - Object.entries(input.schemas ?? {}) - .filter(([_, v]) => v !== undefined) - .map(([key, value]) => [key, convertSchema(value)]), - ), + schemas: input.schemas + ? Object.fromEntries( + Object.entries(input.schemas) + .filter(([_, v]) => v !== undefined) + .map(([key, value]) => [key, convertSchema(value)]), + ) + : undefined, securitySchemes: input.securitySchemes, }); const convertSchema = (input: OpenApiV3.IJsonSchema): OpenApi.IJsonSchema => { diff --git a/src/internal/OpenApiV3_1Converter.ts b/src/internal/OpenApiV3_1Converter.ts index cea73d9..b065972 100644 --- a/src/internal/OpenApiV3_1Converter.ts +++ b/src/internal/OpenApiV3_1Converter.ts @@ -209,11 +209,13 @@ export namespace OpenApiV3_1Converter { const convertComponents = ( input: OpenApiV3_1.IComponents, ): OpenApi.IComponents => ({ - schemas: Object.fromEntries( - Object.entries(input.schemas ?? {}) - .filter(([_, v]) => v !== undefined) - .map(([key, value]) => [key, convertSchema(value)] as const), - ), + schemas: input.schemas + ? Object.fromEntries( + Object.entries(input.schemas) + .filter(([_, v]) => v !== undefined) + .map(([key, value]) => [key, convertSchema(value)] as const), + ) + : undefined, securitySchemes: input.securitySchemes, }); const convertSchema = ( diff --git a/src/internal/SwaggerV2Converter.ts b/src/internal/SwaggerV2Converter.ts index 5de87c5..12543b4 100644 --- a/src/internal/SwaggerV2Converter.ts +++ b/src/internal/SwaggerV2Converter.ts @@ -165,11 +165,13 @@ export namespace SwaggerV2Converter { const convertComponents = ( input: SwaggerV2.IDocument, ): OpenApi.IComponents => ({ - schemas: Object.fromEntries( - Object.entries(input.definitions ?? {}) - .filter(([_, v]) => v !== undefined) - .map(([key, value]) => [key, convertSchema(value)]), - ), + schemas: input.definitions + ? Object.fromEntries( + Object.entries(input.definitions) + .filter(([_, v]) => v !== undefined) + .map(([key, value]) => [key, convertSchema(value)]), + ) + : undefined, securitySchemes: input.securityDefinitions ? Object.fromEntries( Object.entries(input.securityDefinitions) From 89180b319cdd23ddff3a2ab3b22e767796e32049 Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Mon, 15 Apr 2024 01:25:15 +0900 Subject: [PATCH 2/4] Fix `OpenApi.IOperation` properties' types --- package.json | 2 +- src/OpenApi.ts | 34 ++++++++++++++-------- src/OpenApiV3.ts | 8 +++--- src/OpenApiV3_1.ts | 8 +++--- src/SwaggerV2.ts | 8 +++--- src/internal/OpenApiV3Converter.ts | 36 ++++++++++++----------- src/internal/OpenApiV3_1Converter.ts | 43 +++++++++++++++------------- src/internal/SwaggerV2Converter.ts | 34 ++++++++++++---------- 8 files changed, 97 insertions(+), 76 deletions(-) diff --git a/package.json b/package.json index 8066a8d..1787f20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@samchon/openapi", - "version": "0.1.9", + "version": "0.1.11", "description": "OpenAPI definitions and converters for 'typia' and 'nestia'.", "main": "./lib/index.js", "typings": "./lib/index.d.ts", diff --git a/src/OpenApi.ts b/src/OpenApi.ts index f11f5c8..c1fc8b8 100644 --- a/src/OpenApi.ts +++ b/src/OpenApi.ts @@ -25,7 +25,7 @@ import { SwaggerV2Converter } from "./internal/SwaggerV2Converter"; * Here is the entire list of differences between OpenAPI v3.1 and emended `OpenApi`. * * - Operation - * - Merged {@link OpenApiV3_1.IPathItem.parameters} to {@link OpenApi.IOperation.parameters} + * - Merged {@link OpenApiV3_1.IPath.parameters} to {@link OpenApi.IOperation.parameters} * - Resolved {@link OpenApi.IJsonSchema.IReference references} of {@link OpenApiV3_1.IOperation} mebers * - JSON Schema * - Decomposed mixed type: {@link OpenApiV3_1.IJsonSchema.IMixed} @@ -75,10 +75,10 @@ export namespace OpenApi { servers?: IServer[]; info?: IDocument.IInfo; components: IComponents; - paths?: Record; + paths?: Record; webhooks?: Record< string, - IJsonSchema.IReference<`#/components/pathItems/${string}`> | IPathItem + IJsonSchema.IReference<`#/components/pathItems/${string}`> | IPath >; security?: Record[]; tags?: IDocument.ITag[]; @@ -126,7 +126,7 @@ export namespace OpenApi { /* ----------------------------------------------------------- OPERATORS ----------------------------------------------------------- */ - export type IPathItem = { + export type IPath = { servers?: IServer[]; summary?: string; description?: string; @@ -134,10 +134,8 @@ export namespace OpenApi { export interface IOperation { operationId?: string; - parameters: Array; - requestBody?: - | IOperation.IRequestBody - | IJsonSchema.IReference<`#/components/requestBodies/${string}`>; + parameters?: IOperation.IParameter[]; + requestBody?: IOperation.IRequestBody; responses?: Record; servers?: IServer[]; summary?: string; @@ -152,21 +150,33 @@ export namespace OpenApi { in: "path" | "query" | "header" | "cookie"; schema: IJsonSchema; required?: boolean; + title?: string; description?: string; } export interface IRequestBody { description?: string; required?: boolean; - content?: Record; + content?: IContent; + "x-nestia-encrypted"?: boolean; } export interface IResponse { - content?: Record; + content?: IContent; headers?: Record; description?: string; + "x-nestia-encrypted"?: boolean; } + + export type IContent = Partial>; export interface IMediaType { schema?: IJsonSchema; } + export type ContentType = + | "text/plain" + | "application/json" + | "application/x-www-form-url-encoded" + | "multipart/form-data" + | "*/*" + | (string & {}); } /* ----------------------------------------------------------- @@ -201,8 +211,8 @@ export namespace OpenApi { /** @type int */ default?: number; /** @type int */ minimum?: number; /** @type int */ maximum?: number; - /** @type int */ exclusiveMinimum?: boolean; - /** @type int */ exclusiveMaximum?: boolean; + exclusiveMinimum?: boolean; + exclusiveMaximum?: boolean; /** @type uint */ multipleOf?: number; } export interface INumber extends __ISignificant<"number"> { diff --git a/src/OpenApiV3.ts b/src/OpenApiV3.ts index 09ebe2f..a3c5736 100644 --- a/src/OpenApiV3.ts +++ b/src/OpenApiV3.ts @@ -31,7 +31,7 @@ export namespace OpenApiV3 { servers?: IServer[]; info?: IDocument.IInfo; components?: IComponents; - paths?: Record; + paths?: Record; security?: Record[]; tags?: IDocument.ITag[]; } @@ -75,7 +75,7 @@ export namespace OpenApiV3 { /* ----------------------------------------------------------- PATH ITEMS ----------------------------------------------------------- */ - export type IPathItem = { + export type IPath = { parameters?: Array< | IOperation.IParameter | IJsonSchema.IReference<`#/components/headers/${string}`> @@ -170,8 +170,8 @@ export namespace OpenApiV3 { /** @type int */ enum?: number[]; /** @type int */ minimum?: number; /** @type int */ maximum?: number; - /** @type int */ exclusiveMinimum?: boolean; - /** @type int */ exclusiveMaximum?: boolean; + exclusiveMinimum?: boolean; + exclusiveMaximum?: boolean; /** @type uint */ multipleOf?: number; } export interface INumber extends __ISignificant<"number"> { diff --git a/src/OpenApiV3_1.ts b/src/OpenApiV3_1.ts index f4ef6d6..1e9f34c 100644 --- a/src/OpenApiV3_1.ts +++ b/src/OpenApiV3_1.ts @@ -31,10 +31,10 @@ export namespace OpenApiV3_1 { servers?: IServer[]; info?: IDocument.IInfo; components?: IComponents; - paths?: Record; + paths?: Record; webhooks?: Record< string, - IJsonSchema.IReference<`#/components/pathItems/${string}`> | IPathItem + IJsonSchema.IReference<`#/components/pathItems/${string}`> | IPath >; security?: Record[]; tags?: IDocument.ITag[]; @@ -82,7 +82,7 @@ export namespace OpenApiV3_1 { /* ----------------------------------------------------------- OPERATORS ----------------------------------------------------------- */ - export type IPathItem = { + export type IPath = { parameters?: Array< | IOperation.IParameter | IJsonSchema.IReference<`#/components/headers/${string}`> @@ -147,7 +147,7 @@ export namespace OpenApiV3_1 { ----------------------------------------------------------- */ export interface IComponents { schemas?: Record; - pathItems?: Record; + pathItems?: Record; responses?: Record; parameters?: Record; requestBodies?: Record; diff --git a/src/SwaggerV2.ts b/src/SwaggerV2.ts index ede77de..50215dc 100644 --- a/src/SwaggerV2.ts +++ b/src/SwaggerV2.ts @@ -38,7 +38,7 @@ export namespace SwaggerV2 { responses?: Record; securityDefinitions?: Record; security?: Record[]; - paths?: Record; + paths?: Record; tags?: IDocument.ITag[]; } export namespace IDocument { @@ -68,7 +68,7 @@ export namespace SwaggerV2 { /* ----------------------------------------------------------- OPERATORS ----------------------------------------------------------- */ - export type IPathItem = { + export type IPath = { parameters?: Array< IOperation.IParameter | IJsonSchema.IReference<`#/parameters/${string}`> >; @@ -138,8 +138,8 @@ export namespace SwaggerV2 { /** @type int */ enum?: number[]; /** @type int */ minimum?: number; /** @type int */ maximum?: number; - /** @type int */ exclusiveMinimum?: boolean; - /** @type int */ exclusiveMaximum?: boolean; + exclusiveMinimum?: boolean; + exclusiveMaximum?: boolean; /** @type uint */ multipleOf?: number; } export interface INumber extends __ISignificant<"number"> { diff --git a/src/internal/OpenApiV3Converter.ts b/src/internal/OpenApiV3Converter.ts index e9dc017..a5fd664 100644 --- a/src/internal/OpenApiV3Converter.ts +++ b/src/internal/OpenApiV3Converter.ts @@ -23,7 +23,7 @@ export namespace OpenApiV3Converter { ----------------------------------------------------------- */ const convertPathItem = (doc: OpenApiV3.IDocument) => - (pathItem: OpenApiV3.IPathItem): OpenApi.IPathItem => ({ + (pathItem: OpenApiV3.IPath): OpenApi.IPath => ({ ...(pathItem as any), ...(pathItem.get ? { get: convertOperation(doc)(pathItem)(pathItem.get) } @@ -52,24 +52,28 @@ export namespace OpenApiV3Converter { }); const convertOperation = (doc: OpenApiV3.IDocument) => - (pathItem: OpenApiV3.IPathItem) => + (pathItem: OpenApiV3.IPath) => (input: OpenApiV3.IOperation): OpenApi.IOperation => ({ ...input, - parameters: [...(pathItem.parameters ?? []), ...(input.parameters ?? [])] - .map((p) => { - if (!TypeChecker.isReference(p)) return convertParameter(p); - const found: Omit | undefined = - p.$ref.startsWith("#/components/headers/") - ? doc.components?.headers?.[p.$ref.split("/").pop() ?? ""] - : doc.components?.parameters?.[p.$ref.split("/").pop() ?? ""]; - return found !== undefined - ? convertParameter({ - ...found, - in: "header", + parameters: + pathItem.parameters !== undefined && input.parameters !== undefined + ? [...(pathItem.parameters ?? []), ...(input.parameters ?? [])] + .map((p) => { + if (!TypeChecker.isReference(p)) return convertParameter(p); + const found: + | Omit + | undefined = p.$ref.startsWith("#/components/headers/") + ? doc.components?.headers?.[p.$ref.split("/").pop() ?? ""] + : doc.components?.parameters?.[p.$ref.split("/").pop() ?? ""]; + return found !== undefined + ? convertParameter({ + ...found, + in: "header", + }) + : undefined!; }) - : undefined!; - }) - .filter((_, v) => v !== undefined), + .filter((_, v) => v !== undefined) + : undefined, requestBody: input.requestBody ? convertRequestBody(doc)(input.requestBody) : undefined, diff --git a/src/internal/OpenApiV3_1Converter.ts b/src/internal/OpenApiV3_1Converter.ts index b065972..1d36ba3 100644 --- a/src/internal/OpenApiV3_1Converter.ts +++ b/src/internal/OpenApiV3_1Converter.ts @@ -38,18 +38,18 @@ export namespace OpenApiV3_1Converter { (doc: OpenApiV3_1.IDocument) => ( webhook: - | OpenApiV3_1.IPathItem + | OpenApiV3_1.IPath | OpenApiV3_1.IJsonSchema.IReference<`#/components/pathItems/${string}`>, - ): OpenApi.IPathItem | undefined => { + ): OpenApi.IPath | undefined => { if (!TypeChecker.isReference(webhook)) return convertPathItem(doc)(webhook); - const found: OpenApiV3_1.IPathItem | undefined = + const found: OpenApiV3_1.IPath | undefined = doc.components?.pathItems?.[webhook.$ref.split("/").pop() ?? ""]; return found ? convertPathItem(doc)(found) : undefined; }; const convertPathItem = (doc: OpenApiV3_1.IDocument) => - (pathItem: OpenApiV3_1.IPathItem): OpenApi.IPathItem => ({ + (pathItem: OpenApiV3_1.IPath): OpenApi.IPath => ({ ...(pathItem as any), ...(pathItem.get ? { get: convertOperation(doc)(pathItem)(pathItem.get) } @@ -78,25 +78,28 @@ export namespace OpenApiV3_1Converter { }); const convertOperation = (doc: OpenApiV3_1.IDocument) => - (pathItem: OpenApiV3_1.IPathItem) => + (pathItem: OpenApiV3_1.IPath) => (input: OpenApiV3_1.IOperation): OpenApi.IOperation => ({ ...input, - parameters: [...(pathItem.parameters ?? []), ...(input.parameters ?? [])] - .map((p) => { - if (!TypeChecker.isReference(p)) return convertParameter(p); - const found: - | Omit - | undefined = p.$ref.startsWith("#/components/headers/") - ? doc.components?.headers?.[p.$ref.split("/").pop() ?? ""] - : doc.components?.parameters?.[p.$ref.split("/").pop() ?? ""]; - return found !== undefined - ? convertParameter({ - ...found, - in: "header", + parameters: + pathItem.parameters !== undefined || input.parameters === undefined + ? [...(pathItem.parameters ?? []), ...(input.parameters ?? [])] + .map((p) => { + if (!TypeChecker.isReference(p)) return convertParameter(p); + const found: + | Omit + | undefined = p.$ref.startsWith("#/components/headers/") + ? doc.components?.headers?.[p.$ref.split("/").pop() ?? ""] + : doc.components?.parameters?.[p.$ref.split("/").pop() ?? ""]; + return found !== undefined + ? convertParameter({ + ...found, + in: "header", + }) + : undefined!; }) - : undefined!; - }) - .filter((_, v) => v !== undefined), + .filter((_, v) => v !== undefined) + : undefined, requestBody: input.requestBody ? convertRequestBody(doc)(input.requestBody) : undefined, diff --git a/src/internal/SwaggerV2Converter.ts b/src/internal/SwaggerV2Converter.ts index 12543b4..fd7ce47 100644 --- a/src/internal/SwaggerV2Converter.ts +++ b/src/internal/SwaggerV2Converter.ts @@ -32,7 +32,7 @@ export namespace SwaggerV2Converter { ----------------------------------------------------------- */ const convertPathItem = (doc: SwaggerV2.IDocument) => - (pathItem: SwaggerV2.IPathItem): OpenApi.IPathItem => ({ + (pathItem: SwaggerV2.IPath): OpenApi.IPath => ({ ...(pathItem as any), ...(pathItem.get ? { get: convertOperation(doc)(pathItem)(pathItem.get) } @@ -61,22 +61,26 @@ export namespace SwaggerV2Converter { }); const convertOperation = (doc: SwaggerV2.IDocument) => - (pathItem: SwaggerV2.IPathItem) => + (pathItem: SwaggerV2.IPath) => (input: SwaggerV2.IOperation): OpenApi.IOperation => ({ ...input, - parameters: ( - [...(pathItem.parameters ?? []), ...(input.parameters ?? [])] - .map((p) => - TypeChecker.isReference(p) - ? doc.parameters?.[p.$ref.split("/").pop() ?? ""]! - : p, - ) - .filter( - (p) => - p !== undefined && - (p as SwaggerV2.IOperation.IBodyParameter).schema === undefined, - ) as SwaggerV2.IOperation.IGeneralParameter[] - ).map(convertParameter), + parameters: + pathItem.parameters !== undefined || input.parameters !== undefined + ? ( + [...(pathItem.parameters ?? []), ...(input.parameters ?? [])] + .map((p) => + TypeChecker.isReference(p) + ? doc.parameters?.[p.$ref.split("/").pop() ?? ""]! + : p, + ) + .filter( + (p) => + p !== undefined && + (p as SwaggerV2.IOperation.IBodyParameter).schema === + undefined, + ) as SwaggerV2.IOperation.IGeneralParameter[] + ).map(convertParameter) + : undefined, requestBody: (() => { const found: SwaggerV2.IOperation.IBodyParameter | undefined = input.parameters?.find((p) => { From 59010d01263369ddf2959cda1a0f84c471ef19e5 Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Mon, 15 Apr 2024 02:48:05 +0900 Subject: [PATCH 3/4] `OpenApiV3_1` does not require `x-samchon-emended` property --- package.json | 2 +- src/OpenApiV3_1.ts | 1 - src/internal/OpenApiV3_1Converter.ts | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 1787f20..eaa4c92 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@samchon/openapi", - "version": "0.1.11", + "version": "0.1.12", "description": "OpenAPI definitions and converters for 'typia' and 'nestia'.", "main": "./lib/index.js", "typings": "./lib/index.d.ts", diff --git a/src/OpenApiV3_1.ts b/src/OpenApiV3_1.ts index 1e9f34c..12deffb 100644 --- a/src/OpenApiV3_1.ts +++ b/src/OpenApiV3_1.ts @@ -38,7 +38,6 @@ export namespace OpenApiV3_1 { >; security?: Record[]; tags?: IDocument.ITag[]; - "x-samchon-emended"?: boolean; } export namespace IDocument { export interface IInfo { diff --git a/src/internal/OpenApiV3_1Converter.ts b/src/internal/OpenApiV3_1Converter.ts index 1d36ba3..3b0f187 100644 --- a/src/internal/OpenApiV3_1Converter.ts +++ b/src/internal/OpenApiV3_1Converter.ts @@ -3,7 +3,8 @@ import { OpenApiV3_1 } from "../OpenApiV3_1"; export namespace OpenApiV3_1Converter { export const convert = (input: OpenApiV3_1.IDocument): OpenApi.IDocument => { - if (input["x-samchon-emended"] === true) return input as OpenApi.IDocument; + if ((input as OpenApi.IDocument)["x-samchon-emended"] === true) + return input as OpenApi.IDocument; return { ...input, components: convertComponents(input.components ?? {}), From 3a541e85d32aa28ba7755082842305a2bf36b9e3 Mon Sep 17 00:00:00 2001 From: Jeongho Nam Date: Mon, 15 Apr 2024 03:33:40 +0900 Subject: [PATCH 4/4] Erased vulnerable field `OpenApi.IJsonSchema.IString.enum` --- package.json | 2 +- src/OpenApi.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index eaa4c92..ea5a0a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@samchon/openapi", - "version": "0.1.12", + "version": "0.1.13", "description": "OpenAPI definitions and converters for 'typia' and 'nestia'.", "main": "./lib/index.js", "typings": "./lib/index.d.ts", diff --git a/src/OpenApi.ts b/src/OpenApi.ts index c1fc8b8..4ffe793 100644 --- a/src/OpenApi.ts +++ b/src/OpenApi.ts @@ -226,7 +226,6 @@ export namespace OpenApi { export interface IString extends __ISignificant<"string"> { contentMediaType?: string; default?: string; - enum?: string[]; format?: | "binary" | "byte"