Skip to content

Commit

Permalink
Merge pull request #1182 from samchon/feature/enum
Browse files Browse the repository at this point in the history
Close #1181: support enumeration level description comment in JSON schema.
  • Loading branch information
samchon committed Jul 27, 2024
2 parents ab2db4e + 14d386c commit 1668fe8
Show file tree
Hide file tree
Showing 21 changed files with 198 additions and 44 deletions.
2 changes: 1 addition & 1 deletion benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,6 @@
"suppress-warnings": "^1.0.2",
"tstl": "^3.0.0",
"uuid": "^9.0.1",
"typia": "../typia-6.5.5.tgz"
"typia": "../typia-6.6.0.tgz"
}
}
39 changes: 39 additions & 0 deletions debug/features/enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import typia from "typia";

import { TestValidator } from "./internal/TestValidator";

const app = typia.json.application<[ConstEnum]>();
console.log(app.components.schemas?.ConstEnum);

TestValidator.equals("enum-description")(app.components.schemas?.ConstEnum)({
oneOf: [
{
const: 1,
title: "The value one",
description:
"The value one.\n\nThe value one defined in the constant enumeration.",
},
{
const: 2,
title: "The value two",
description:
"The value two.\n\nThe value two defined in the constant enumeration.",
},
],
});

const enum ConstEnum {
/**
* The value one.
*
* The value one defined in the constant enumeration.
*/
ONE = 1,

/**
* The value two.
*
* The value two defined in the constant enumeration.
*/
TWO = 2,
}
47 changes: 47 additions & 0 deletions debug/features/internal/TestValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export namespace TestValidator {
export const equals =
(title: string, exception: (key: string) => boolean = () => false) =>
<T>(x: T) =>
(y: T) => {
const diff: string[] = json_equal_to(exception)(x)(y);
if (diff.length)
throw new Error(
`Bug on ${title}: found different values - [${diff.join(", ")}]`,
);
};
}

const json_equal_to =
(exception: (key: string) => boolean) =>
<T>(x: T) =>
(y: T): string[] => {
const container: string[] = [];
const iterate =
(accessor: string) =>
(x: any) =>
(y: any): void => {
if (typeof x !== typeof y) container.push(accessor);
else if (x instanceof Array)
if (!(y instanceof Array)) container.push(accessor);
else array(accessor)(x)(y);
else if (x instanceof Object) object(accessor)(x)(y);
else if (x !== y) container.push(accessor);
};
const array =
(accessor: string) =>
(x: any[]) =>
(y: any[]): void => {
if (x.length !== y.length) container.push(`${accessor}.length`);
x.forEach((xItem, i) => iterate(`${accessor}[${i}]`)(xItem)(y[i]));
};
const object =
(accessor: string) =>
(x: any) =>
(y: any): void =>
Object.keys(x)
.filter((key) => x[key] !== undefined && !exception(key))
.forEach((key) => iterate(`${accessor}.${key}`)(x[key])(y[key]));

iterate("")(x)(y);
return container;
};
2 changes: 1 addition & 1 deletion debug/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
"typescript": "^5.4.2"
},
"dependencies": {
"typia": "../typia-6.5.5.tgz"
"typia": "../typia-6.6.0-dev.20240727.tgz"
}
}
2 changes: 1 addition & 1 deletion errors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@
"typescript": "^5.3.2"
},
"dependencies": {
"typia": "../typia-6.5.5.tgz"
"typia": "../typia-6.6.0.tgz"
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typia",
"version": "6.5.5",
"version": "6.6.0",
"description": "Superfast runtime validators with only one line",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down
4 changes: 2 additions & 2 deletions packages/typescript-json/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typescript-json",
"version": "6.5.5",
"version": "6.6.0",
"description": "Superfast runtime validators with only one line",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down Expand Up @@ -63,7 +63,7 @@
},
"homepage": "https://typia.io",
"dependencies": {
"typia": "6.5.5"
"typia": "6.6.0"
},
"peerDependencies": {
"typescript": ">=4.8.0 <5.6.0"
Expand Down
13 changes: 13 additions & 0 deletions src/factories/internal/metadata/iterate_metadata_constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { MetadataConstantValue } from "../../../schemas/metadata/MetadataConstan

import { ArrayUtil } from "../../../utils/ArrayUtil";

import { CommentFactory } from "../../CommentFactory";
import { MetadataFactory } from "../../MetadataFactory";

export const iterate_metadata_constant =
Expand All @@ -15,6 +16,15 @@ export const iterate_metadata_constant =
if (!options.constant) return false;

const filter = (flag: ts.TypeFlags) => (type.getFlags() & flag) !== 0;
const comment = () => {
if (!filter(ts.TypeFlags.EnumLiteral)) return {};
return {
jsDocTags: type.symbol?.getJsDocTags(),
description: type.symbol
? CommentFactory.description(type.symbol) ?? null
: undefined,
};
};
if (type.isLiteral()) {
const value: string | number | bigint =
typeof type.value === "object"
Expand All @@ -34,11 +44,13 @@ export const iterate_metadata_constant =
MetadataConstantValue.create({
value,
tags: [],
...comment(),
}),
(a, b) => a.value === b.value,
);
return true;
} else if (filter(ts.TypeFlags.BooleanLiteral)) {
comment();
const value: boolean = checker.typeToString(type) === "true";
const constant: MetadataConstant = ArrayUtil.take(
meta.constants,
Expand All @@ -54,6 +66,7 @@ export const iterate_metadata_constant =
MetadataConstantValue.create({
value,
tags: [],
...comment(),
}),
(a, b) => a.value === b.value,
);
Expand Down
6 changes: 3 additions & 3 deletions src/programmers/internal/application_description.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { IJsDocTagInfo } from "../../module";

export const application_description = (props: {
description: string | null | undefined;
jsDocTags: IJsDocTagInfo[];
description?: string | null | undefined;
jsDocTags?: IJsDocTagInfo[];
}): string | undefined =>
props.jsDocTags
.find((tag) => tag.name === "description")
?.find((tag) => tag.name === "description")
?.text?.[0]?.text?.split("\r\n")
.join("\n") ??
props.description ??
Expand Down
20 changes: 20 additions & 0 deletions src/programmers/internal/application_title.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { CommentFactory } from "../../factories/CommentFactory";

import { IJsDocTagInfo } from "../../module";

export const application_title = (schema: {
description?: string | null | undefined;
jsDocTags?: IJsDocTagInfo[] | undefined;
}): string | undefined => {
const info: IJsDocTagInfo | undefined = schema.jsDocTags?.find(
(tag) => tag.name === "title",
);
if (info?.text?.length) return CommentFactory.merge(info.text);
else if (!schema.description?.length) return undefined;

const index: number = schema.description.indexOf("\n");
const top: string = (
index === -1 ? schema.description : schema.description.substring(0, index)
).trim();
return top.endsWith(".") ? top.substring(0, top.length - 1) : undefined;
};
13 changes: 2 additions & 11 deletions src/programmers/internal/application_v31_alias.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { OpenApi } from "@samchon/openapi";

import { CommentFactory } from "../../factories/CommentFactory";

import { IJsDocTagInfo } from "../../schemas/metadata/IJsDocTagInfo";
import { MetadataAlias } from "../../schemas/metadata/MetadataAlias";

import { application_description } from "./application_description";
import { application_title } from "./application_title";
import { application_v31_object } from "./application_v31_object";
import { application_v31_schema } from "./application_v31_schema";

Expand Down Expand Up @@ -33,14 +31,7 @@ export const application_v31_alias =
)(components)({
deprecated:
alias.jsDocTags.some((tag) => tag.name === "deprecated") || undefined,
title: (() => {
const info: IJsDocTagInfo | undefined = alias.jsDocTags.find(
(tag) => tag.name === "title",
);
return info?.text?.length
? CommentFactory.merge(info.text)
: undefined;
})(),
title: application_title(alias),
description: application_description(alias),
})(alias.value);
if (schema !== null)
Expand Down
4 changes: 4 additions & 0 deletions src/programmers/internal/application_v31_constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { OpenApi } from "@samchon/openapi";

import { MetadataConstant } from "../../schemas/metadata/MetadataConstant";

import { application_description } from "./application_description";
import { application_plugin } from "./application_plugin";
import { application_title } from "./application_title";

/**
* @internal
Expand All @@ -15,6 +17,8 @@ export const application_v31_constant = (
application_plugin(
{
const: value.value as boolean | number | string,
title: application_title(value),
description: application_description(value),
} satisfies OpenApi.IJsonSchema.IConstant,
value.tags ?? [],
),
Expand Down
19 changes: 2 additions & 17 deletions src/programmers/internal/application_v31_object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { MetadataObject } from "../../schemas/metadata/MetadataObject";
import { PatternUtil } from "../../utils/PatternUtil";

import { application_description } from "./application_description";
import { application_title } from "./application_title";
import { application_v31_schema } from "./application_v31_schema";
import { metadata_to_pattern } from "./metadata_to_pattern";

Expand Down Expand Up @@ -67,23 +68,7 @@ const create_object_schema =
deprecated:
property.jsDocTags.some((tag) => tag.name === "deprecated") ||
undefined,
title: (() => {
const info: IJsDocTagInfo | undefined = property.jsDocTags.find(
(tag) => tag.name === "title",
);
if (info?.text?.length) return CommentFactory.merge(info.text);
else if (!property.description?.length) return undefined;

const index: number = property.description.indexOf("\n");
const top: string = (
index === -1
? property.description
: property.description.substring(0, index)
).trim();
return top.endsWith(".")
? top.substring(0, top.length - 1)
: undefined;
})(),
title: application_title(property),
description: application_description(property),
})(property.value);

Expand Down
3 changes: 3 additions & 0 deletions src/schemas/metadata/IMetadataConstantValue.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Atomic } from "../../typings/Atomic";

import { IJsDocTagInfo } from "./IJsDocTagInfo";
import { IMetadataTypeTag } from "./IMetadataTypeTag";

export interface IMetadataConstantValue<T extends Atomic.Type> {
value: T;
tags: IMetadataTypeTag[][] | undefined;
description?: string | null;
jsDocTags?: IJsDocTagInfo[];
}
5 changes: 5 additions & 0 deletions src/schemas/metadata/MetadataConstantValue.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { ClassProperties } from "../../typings/ClassProperties";

import { IJsDocTagInfo } from "./IJsDocTagInfo";
import { IMetadataConstantValue } from "./IMetadataConstantValue";
import { IMetadataTypeTag } from "./IMetadataTypeTag";

export class MetadataConstantValue {
public readonly value: boolean | bigint | number | string;
public tags: IMetadataTypeTag[][] | undefined;
public readonly description?: string | null;
public readonly jsDocTags?: IJsDocTagInfo[];
private name_?: string;

private constructor(props: ClassProperties<MetadataConstantValue>) {
this.value = props.value;
this.tags = props.tags;
this.description = props.description;
this.jsDocTags = props.jsDocTags;
}

public static create(
Expand Down
2 changes: 1 addition & 1 deletion test-esm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@
"typescript": "^5.4.5"
},
"dependencies": {
"typia": "../typia-6.5.5.tgz"
"typia": "../typia-6.6.0.tgz"
}
}
2 changes: 1 addition & 1 deletion test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@
"suppress-warnings": "^1.0.2",
"tstl": "^3.0.0",
"uuid": "^9.0.1",
"typia": "../typia-6.5.5.tgz"
"typia": "../typia-6.6.0.tgz"
}
}
2 changes: 2 additions & 0 deletions test/schemas/json/v3_1/UltimateUnion.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"$ref": "#/components/schemas/OpenApi.IJsonSchema.IUnknown"
}
],
"title": "Type schema info",
"description": "Type schema info.\n\n`OpenApi.IJsonSchema` is a type schema info of the OpenAPI.\n\n`OpenApi.IJsonSchema` basically follows the JSON schema definition of\nOpenAPI v3.1, but a little bit shrinked to remove ambiguous and duplicated\nexpressions of OpenAPI v3.1 for the convenience and clarity.\n\n- Decompose mixed type: {@link OpenApiV3_1.IJsonSchema.IMixed}\n- Resolve nullable property: {@link OpenApiV3_1.IJsonSchema.__ISignificant.nullable}\n- Array type utilizes only single {@link OpenAPI.IJsonSchema.IArray.items}\n- Tuple type utilizes only {@link OpenApi.IJsonSchema.ITuple.prefixItems}\n- Merge {@link OpenApiV3_1.IJsonSchema.IAnyOf} to {@link OpenApi.IJsonSchema.IOneOf}\n- Merge {@link OpenApiV3_1.IJsonSchema.IRecursiveReference} to {@link OpenApi.IJsonSchema.IReference}\n- Merge {@link OpenApiV3_1.IJsonSchema.IAllOf} to {@link OpenApi.IJsonSchema.IObject}"
},
"OpenApi.IJsonSchema.IConstant": {
Expand Down Expand Up @@ -787,6 +788,7 @@
"$ref": "#/components/schemas/OpenApi.ISecurityScheme.IOpenId"
}
],
"title": "Security scheme of Swagger Documents",
"description": "Security scheme of Swagger Documents.\n\n`OpenApi.ISecurityScheme` is a data structure representing content of\n`securitySchemes` in `swagger.json` file. It is composed with 5 types of\nsecurity schemes as an union type like below."
},
"OpenApi.ISecurityScheme.IApiKey": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import typia from "typia";

export const test_issue_1179_tuple_type_in_dynamic_object = (): void => {
new Array(100).forEach(() => {
const foo: Foo = typia.random<Foo>();
typia.assert(foo);
});
const foo: Foo = typia.random<Foo>();
typia.assert(foo);

const bar: Foo = {
paths: {
"/": ["home"],
"/about": ["about"],
},
};
typia.assert(bar);
};

interface Foo {
Expand Down
Loading

0 comments on commit 1668fe8

Please sign in to comment.