Skip to content

Commit

Permalink
Merge pull request #52 from samchon/feature/schema
Browse files Browse the repository at this point in the history
Exact JSON schema downgrading.
  • Loading branch information
samchon authored Sep 24, 2024
2 parents ded2afc + 84512af commit 44a753a
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 14 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@samchon/openapi",
"version": "1.0.3",
"version": "1.0.4",
"description": "OpenAPI definitions and converters for 'typia' and 'nestia'.",
"main": "./lib/index.js",
"module": "./lib/index.mjs",
Expand Down
7 changes: 6 additions & 1 deletion src/converters/HttpLlmConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ const escape = (props: {
if (oneOf.some((v) => v === null)) return null;
return {
...props.input,
oneOf: oneOf as OpenApi.IJsonSchema[],
oneOf: flat(oneOf as OpenApi.IJsonSchema[]),
};
} else if (OpenApiTypeChecker.isObject(props.input)) {
// OBJECT
Expand Down Expand Up @@ -282,3 +282,8 @@ const escape = (props: {
}
return props.input;
};

const flat = (elements: OpenApi.IJsonSchema[]): OpenApi.IJsonSchema[] =>
elements
.map((elem) => (OpenApiTypeChecker.isOneOf(elem) ? flat(elem.oneOf) : elem))
.flat();
27 changes: 22 additions & 5 deletions src/converters/OpenApiV3Downgrader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,9 @@ export namespace OpenApiV3Downgrader {
export const downgradeSchema =
(collection: IComponentsCollection) =>
(input: OpenApi.IJsonSchema): OpenApiV3.IJsonSchema => {
const nullable: boolean =
OpenApiTypeChecker.isNull(input) ||
(OpenApiTypeChecker.isOneOf(input) &&
input.oneOf.some(OpenApiTypeChecker.isNull));
const nullable: boolean = isNullable(new Set())(collection.original)(
input,
);
const union: OpenApiV3.IJsonSchema[] = [];
const attribute: OpenApiV3.IJsonSchema.__IAttribute = {
title: input.title,
Expand Down Expand Up @@ -286,7 +285,7 @@ export namespace OpenApiV3Downgrader {
? { type: undefined }
: union.length === 1
? { ...union[0] }
: { oneOf: union.map((u) => ({ ...u, nullable: undefined })) }),
: { oneOf: union }),
...attribute,
};
};
Expand All @@ -309,4 +308,22 @@ export namespace OpenApiV3Downgrader {
}
schema.$ref += ".Nullable";
};

const isNullable =
(visited: Set<string>) =>
(components: OpenApi.IComponents) =>
(schema: OpenApi.IJsonSchema): boolean => {
if (OpenApiTypeChecker.isNull(schema)) return true;
else if (OpenApiTypeChecker.isReference(schema)) {
if (visited.has(schema.$ref)) return false;
visited.add(schema.$ref);
const key: string = schema.$ref.split("/").pop()!;
const next: OpenApi.IJsonSchema | undefined = components.schemas?.[key];
return next ? isNullable(visited)(components)(next) : false;
}
return (
OpenApiTypeChecker.isOneOf(schema) &&
schema.oneOf.some(isNullable(visited)(components))
);
};
}
27 changes: 22 additions & 5 deletions src/converters/SwaggerV2Downgrader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,9 @@ export namespace SwaggerV2Downgrader {
export const downgradeSchema =
(collection: IComponentsCollection) =>
(input: OpenApi.IJsonSchema): SwaggerV2.IJsonSchema => {
const nullable: boolean =
OpenApiTypeChecker.isNull(input) ||
(OpenApiTypeChecker.isOneOf(input) &&
input.oneOf.some(OpenApiTypeChecker.isNull));
const nullable: boolean = isNullable(new Set())(collection.original)(
input,
);
const union: SwaggerV2.IJsonSchema[] = [];
const attribute: SwaggerV2.IJsonSchema.__IAttribute = {
title: input.title,
Expand Down Expand Up @@ -297,7 +296,7 @@ export namespace SwaggerV2Downgrader {
? { type: undefined }
: union.length === 1
? { ...union[0] }
: { "x-oneOf": union.map((u) => ({ ...u, nullable: undefined })) }),
: { "x-oneOf": union }),
...attribute,
...(union.length > 1 ? { discriminator: undefined } : {}),
};
Expand Down Expand Up @@ -363,4 +362,22 @@ export namespace SwaggerV2Downgrader {
}
return [];
};

const isNullable =
(visited: Set<string>) =>
(components: OpenApi.IComponents) =>
(schema: OpenApi.IJsonSchema): boolean => {
if (OpenApiTypeChecker.isNull(schema)) return true;
else if (OpenApiTypeChecker.isReference(schema)) {
if (visited.has(schema.$ref)) return false;
visited.add(schema.$ref);
const key: string = schema.$ref.split("/").pop()!;
const next: OpenApi.IJsonSchema | undefined = components.schemas?.[key];
return next ? isNullable(visited)(components)(next) : false;
}
return (
OpenApiTypeChecker.isOneOf(schema) &&
schema.oneOf.some(isNullable(visited)(components))
);
};
}
54 changes: 54 additions & 0 deletions test/features/llm/test_llm_schema_nullable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { TestValidator } from "@nestia/e2e";
import { HttpLlm, ILlmSchema, OpenApi } from "@samchon/openapi";

export const test_llm_schema_union = (): void => {
const components: OpenApi.IComponents = {
schemas: {
union1: {
oneOf: [
{
type: "string",
},
{
type: "number",
},
{
$ref: "#/components/schemas/union2",
},
],
},
union2: {
oneOf: [
{
type: "boolean",
},
{
type: "null",
},
],
},
},
};
const schema: OpenApi.IJsonSchema = {
oneOf: [
{
type: "boolean",
},
{
$ref: "#/components/schemas/union1",
},
],
};
const llm: ILlmSchema | null = HttpLlm.schema({
components,
schema,
});
TestValidator.equals("nullable")(llm)({
oneOf: [
{ type: "boolean", nullable: true },
{ type: "string", nullable: true },
{ type: "number", nullable: true },
{ type: "boolean", nullable: true },
],
});
};
38 changes: 38 additions & 0 deletions test/features/llm/test_llm_schema_union.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { TestValidator } from "@nestia/e2e";
import { HttpLlm, ILlmSchema, OpenApi } from "@samchon/openapi";

export const test_llm_schema_union = (): void => {
const components: OpenApi.IComponents = {
schemas: {
named: {
oneOf: [
{
const: 4,
},
{
const: 5,
},
],
},
},
};
const schema: OpenApi.IJsonSchema = {
oneOf: [
{
const: 3,
},
{
$ref: "#/components/schemas/named",
},
],
};

const llm: ILlmSchema | null = HttpLlm.schema({
components,
schema,
});
TestValidator.equals("union")(llm)({
type: "number",
enum: [3, 4, 5],
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { TestValidator } from "@nestia/e2e";
import { OpenApi, SwaggerV2 } from "@samchon/openapi";
import { SwaggerV2Downgrader } from "@samchon/openapi/lib/converters/SwaggerV2Downgrader";

export const test_json_schema_downgrade_v20 = () => {
export const test_json_schema_downgrade_v20_enum = () => {
const schema: OpenApi.IJsonSchema = {
oneOf: [{ const: "a" }, { const: "b" }, { const: "c" }],
title: "something",
Expand Down
67 changes: 67 additions & 0 deletions test/features/openapi/test_json_schema_downgrade_v20_nullable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { TestValidator } from "@nestia/e2e";
import { OpenApi, SwaggerV2 } from "@samchon/openapi";
import { SwaggerV2Downgrader } from "@samchon/openapi/lib/converters/SwaggerV2Downgrader";

export const test_json_schema_downgrade_v20_nullable = (): void => {
const original: OpenApi.IComponents = {
schemas: {
union: {
oneOf: [
{
type: "null",
},
{
type: "string",
},
{
type: "number",
},
],
},
},
};
const components: Record<string, SwaggerV2.IJsonSchema> = {};
const schema: SwaggerV2.IJsonSchema = SwaggerV2Downgrader.downgradeSchema({
original,
downgraded: components,
})({
oneOf: [
{
type: "boolean",
},
{
$ref: "#/components/schemas/union",
},
],
} satisfies OpenApi.IJsonSchema);
TestValidator.equals("nullable")({
components,
schema,
})({
components: {
"union.Nullable": {
"x-oneOf": [
{
type: "string",
"x-nullable": true,
},
{
type: "number",
"x-nullable": true,
},
],
},
},
schema: {
"x-oneOf": [
{
type: "boolean",
"x-nullable": true,
},
{
$ref: "#/definitions/union.Nullable",
},
],
},
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { TestValidator } from "@nestia/e2e";
import { OpenApi, OpenApiV3 } from "@samchon/openapi";
import { OpenApiV3Downgrader } from "@samchon/openapi/lib/converters/OpenApiV3Downgrader";

export const test_json_schema_downgrade_v30 = () => {
export const test_json_schema_downgrade_v30_enum = () => {
const schema: OpenApi.IJsonSchema = {
oneOf: [{ const: "a" }, { const: "b" }, { const: "c" }],
title: "something",
Expand Down
71 changes: 71 additions & 0 deletions test/features/openapi/test_json_schema_downgrade_v30_nullable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { TestValidator } from "@nestia/e2e";
import { OpenApi, OpenApiV3 } from "@samchon/openapi";
import { OpenApiV3Downgrader } from "@samchon/openapi/lib/converters/OpenApiV3Downgrader";

export const test_json_schema_downgrade_v30_nullable = (): void => {
const original: OpenApi.IComponents = {
schemas: {
union: {
oneOf: [
{
type: "null",
},
{
type: "string",
},
{
type: "number",
},
],
},
},
};
const components: OpenApiV3.IComponents = {
schemas: {},
};
const schema: OpenApiV3.IJsonSchema = OpenApiV3Downgrader.downgradeSchema({
original,
downgraded: components,
})({
oneOf: [
{
type: "boolean",
},
{
$ref: "#/components/schemas/union",
},
],
} satisfies OpenApi.IJsonSchema);
TestValidator.equals("nullable")({
components,
schema,
})({
components: {
schemas: {
"union.Nullable": {
oneOf: [
{
type: "string",
nullable: true,
},
{
type: "number",
nullable: true,
},
],
},
},
},
schema: {
oneOf: [
{
type: "boolean",
nullable: true,
},
{
$ref: "#/components/schemas/union.Nullable",
},
],
},
});
};

0 comments on commit 44a753a

Please sign in to comment.