typia 4.2.0 → 4.2.1-dev.20230808
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/programmers/RandomProgrammer.js.map +1 -1
- package/lib/programmers/internal/stringify_dynamic_properties.js +1 -0
- package/lib/programmers/internal/stringify_dynamic_properties.js.map +1 -1
- package/lib/utils/PatternUtil.d.ts +1 -1
- package/lib/utils/PatternUtil.js +3 -1
- package/lib/utils/PatternUtil.js.map +1 -1
- package/package.json +1 -1
- package/src/factories/MetadataTagFactory.ts +355 -355
- package/src/factories/TypeFactory.ts +124 -124
- package/src/functional/$string.ts +50 -50
- package/src/programmers/RandomProgrammer.ts +579 -581
- package/src/programmers/TypiaProgrammer.ts +129 -129
- package/src/programmers/helpers/OptionPredicator.ts +15 -15
- package/src/programmers/internal/application_object.ts +165 -165
- package/src/programmers/internal/stringify_dynamic_properties.ts +4 -0
- package/src/transform.ts +27 -27
- package/src/transformers/ITransformOptions.ts +62 -62
- package/src/transformers/NodeTransformer.ts +13 -13
- package/src/utils/PatternUtil.ts +4 -1
|
@@ -1,129 +1,129 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import ts from "typescript";
|
|
4
|
-
|
|
5
|
-
import { ImportTransformer } from "../transformers/ImportTransformer";
|
|
6
|
-
|
|
7
|
-
import transform from "../transform";
|
|
8
|
-
|
|
9
|
-
export namespace TypiaProgrammer {
|
|
10
|
-
export interface IProps {
|
|
11
|
-
input: string;
|
|
12
|
-
output: string;
|
|
13
|
-
project: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const build = async (
|
|
17
|
-
props: TypiaProgrammer.IProps,
|
|
18
|
-
): Promise<void> => {
|
|
19
|
-
props.input = path.resolve(props.input);
|
|
20
|
-
props.output = path.resolve(props.output);
|
|
21
|
-
|
|
22
|
-
if ((await is_directory(props.input)) === false)
|
|
23
|
-
throw new Error(
|
|
24
|
-
"Error on TypiaGenerator.generate(): input path is not a directory.",
|
|
25
|
-
);
|
|
26
|
-
else if (fs.existsSync(props.output) === false)
|
|
27
|
-
await fs.promises.mkdir(props.output, { recursive: true });
|
|
28
|
-
else if ((await is_directory(props.output)) === false) {
|
|
29
|
-
const parent: string = path.join(props.output, "..");
|
|
30
|
-
if ((await is_directory(parent)) === false)
|
|
31
|
-
throw new Error(
|
|
32
|
-
"Error on TypiaGenerator.generate(): output path is not a directory.",
|
|
33
|
-
);
|
|
34
|
-
await fs.promises.mkdir(props.output);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// CREATE PROGRAM
|
|
38
|
-
const { options: compilerOptions } = ts.parseJsonConfigFileContent(
|
|
39
|
-
ts.readConfigFile(props.project, ts.sys.readFile).config,
|
|
40
|
-
{
|
|
41
|
-
fileExists: ts.sys.fileExists,
|
|
42
|
-
readFile: ts.sys.readFile,
|
|
43
|
-
readDirectory: ts.sys.readDirectory,
|
|
44
|
-
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
|
|
45
|
-
},
|
|
46
|
-
path.dirname(props.project),
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
const program: ts.Program = ts.createProgram(
|
|
50
|
-
await (async () => {
|
|
51
|
-
const container: string[] = [];
|
|
52
|
-
await gather(props)(container)(props.input)(props.output);
|
|
53
|
-
return container;
|
|
54
|
-
})(),
|
|
55
|
-
compilerOptions,
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
// DO TRANSFORM
|
|
59
|
-
const result: ts.TransformationResult<ts.SourceFile> = ts.transform(
|
|
60
|
-
program
|
|
61
|
-
.getSourceFiles()
|
|
62
|
-
.filter(
|
|
63
|
-
(file) =>
|
|
64
|
-
!file.isDeclarationFile &&
|
|
65
|
-
path.resolve(file.fileName).indexOf(props.input) !== -1,
|
|
66
|
-
),
|
|
67
|
-
[
|
|
68
|
-
ImportTransformer.transform(props.input)(props.output),
|
|
69
|
-
transform(
|
|
70
|
-
program,
|
|
71
|
-
((compilerOptions.plugins as any[]) ?? []).find(
|
|
72
|
-
(p: any) =>
|
|
73
|
-
p.transform === "typia/lib/transform" ||
|
|
74
|
-
p.transform === "../src/transform.ts",
|
|
75
|
-
) ?? {},
|
|
76
|
-
),
|
|
77
|
-
],
|
|
78
|
-
program.getCompilerOptions(),
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
// ARCHIVE TRANSFORMED FILES
|
|
82
|
-
const printer: ts.Printer = ts.createPrinter({
|
|
83
|
-
newLine: ts.NewLineKind.LineFeed,
|
|
84
|
-
});
|
|
85
|
-
for (const file of result.transformed) {
|
|
86
|
-
const to: string = path
|
|
87
|
-
.resolve(file.fileName)
|
|
88
|
-
.replace(props.input, props.output);
|
|
89
|
-
|
|
90
|
-
const content: string = printer.printFile(file);
|
|
91
|
-
await fs.promises.writeFile(to, emend(content), "utf8");
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const emend = (content: string): string => {
|
|
96
|
-
if (
|
|
97
|
-
content.indexOf("typia.") === -1 ||
|
|
98
|
-
content.indexOf("import typia") !== -1 ||
|
|
99
|
-
content.indexOf("import * as typia") !== -1
|
|
100
|
-
)
|
|
101
|
-
return content;
|
|
102
|
-
return `import typia from "typia";\n\n${content}`;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const is_directory = async (current: string): Promise<boolean> => {
|
|
106
|
-
const stat: fs.Stats = await fs.promises.stat(current);
|
|
107
|
-
return stat.isDirectory();
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const gather =
|
|
111
|
-
(props: IProps) =>
|
|
112
|
-
(container: string[]) =>
|
|
113
|
-
(from: string) =>
|
|
114
|
-
async (to: string) => {
|
|
115
|
-
if (from === props.output) return;
|
|
116
|
-
else if (fs.existsSync(to) === false) await fs.promises.mkdir(to);
|
|
117
|
-
|
|
118
|
-
for (const file of await fs.promises.readdir(from)) {
|
|
119
|
-
const next: string = path.join(from, file);
|
|
120
|
-
const stat: fs.Stats = await fs.promises.stat(next);
|
|
121
|
-
|
|
122
|
-
if (stat.isDirectory()) {
|
|
123
|
-
await gather(props)(container)(next)(path.join(to, file));
|
|
124
|
-
continue;
|
|
125
|
-
} else if (file.substring(file.length - 3) === ".ts")
|
|
126
|
-
container.push(next);
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
}
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
|
|
5
|
+
import { ImportTransformer } from "../transformers/ImportTransformer";
|
|
6
|
+
|
|
7
|
+
import transform from "../transform";
|
|
8
|
+
|
|
9
|
+
export namespace TypiaProgrammer {
|
|
10
|
+
export interface IProps {
|
|
11
|
+
input: string;
|
|
12
|
+
output: string;
|
|
13
|
+
project: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const build = async (
|
|
17
|
+
props: TypiaProgrammer.IProps,
|
|
18
|
+
): Promise<void> => {
|
|
19
|
+
props.input = path.resolve(props.input);
|
|
20
|
+
props.output = path.resolve(props.output);
|
|
21
|
+
|
|
22
|
+
if ((await is_directory(props.input)) === false)
|
|
23
|
+
throw new Error(
|
|
24
|
+
"Error on TypiaGenerator.generate(): input path is not a directory.",
|
|
25
|
+
);
|
|
26
|
+
else if (fs.existsSync(props.output) === false)
|
|
27
|
+
await fs.promises.mkdir(props.output, { recursive: true });
|
|
28
|
+
else if ((await is_directory(props.output)) === false) {
|
|
29
|
+
const parent: string = path.join(props.output, "..");
|
|
30
|
+
if ((await is_directory(parent)) === false)
|
|
31
|
+
throw new Error(
|
|
32
|
+
"Error on TypiaGenerator.generate(): output path is not a directory.",
|
|
33
|
+
);
|
|
34
|
+
await fs.promises.mkdir(props.output);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// CREATE PROGRAM
|
|
38
|
+
const { options: compilerOptions } = ts.parseJsonConfigFileContent(
|
|
39
|
+
ts.readConfigFile(props.project, ts.sys.readFile).config,
|
|
40
|
+
{
|
|
41
|
+
fileExists: ts.sys.fileExists,
|
|
42
|
+
readFile: ts.sys.readFile,
|
|
43
|
+
readDirectory: ts.sys.readDirectory,
|
|
44
|
+
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
|
|
45
|
+
},
|
|
46
|
+
path.dirname(props.project),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const program: ts.Program = ts.createProgram(
|
|
50
|
+
await (async () => {
|
|
51
|
+
const container: string[] = [];
|
|
52
|
+
await gather(props)(container)(props.input)(props.output);
|
|
53
|
+
return container;
|
|
54
|
+
})(),
|
|
55
|
+
compilerOptions,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// DO TRANSFORM
|
|
59
|
+
const result: ts.TransformationResult<ts.SourceFile> = ts.transform(
|
|
60
|
+
program
|
|
61
|
+
.getSourceFiles()
|
|
62
|
+
.filter(
|
|
63
|
+
(file) =>
|
|
64
|
+
!file.isDeclarationFile &&
|
|
65
|
+
path.resolve(file.fileName).indexOf(props.input) !== -1,
|
|
66
|
+
),
|
|
67
|
+
[
|
|
68
|
+
ImportTransformer.transform(props.input)(props.output),
|
|
69
|
+
transform(
|
|
70
|
+
program,
|
|
71
|
+
((compilerOptions.plugins as any[]) ?? []).find(
|
|
72
|
+
(p: any) =>
|
|
73
|
+
p.transform === "typia/lib/transform" ||
|
|
74
|
+
p.transform === "../src/transform.ts",
|
|
75
|
+
) ?? {},
|
|
76
|
+
),
|
|
77
|
+
],
|
|
78
|
+
program.getCompilerOptions(),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// ARCHIVE TRANSFORMED FILES
|
|
82
|
+
const printer: ts.Printer = ts.createPrinter({
|
|
83
|
+
newLine: ts.NewLineKind.LineFeed,
|
|
84
|
+
});
|
|
85
|
+
for (const file of result.transformed) {
|
|
86
|
+
const to: string = path
|
|
87
|
+
.resolve(file.fileName)
|
|
88
|
+
.replace(props.input, props.output);
|
|
89
|
+
|
|
90
|
+
const content: string = printer.printFile(file);
|
|
91
|
+
await fs.promises.writeFile(to, emend(content), "utf8");
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const emend = (content: string): string => {
|
|
96
|
+
if (
|
|
97
|
+
content.indexOf("typia.") === -1 ||
|
|
98
|
+
content.indexOf("import typia") !== -1 ||
|
|
99
|
+
content.indexOf("import * as typia") !== -1
|
|
100
|
+
)
|
|
101
|
+
return content;
|
|
102
|
+
return `import typia from "typia";\n\n${content}`;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const is_directory = async (current: string): Promise<boolean> => {
|
|
106
|
+
const stat: fs.Stats = await fs.promises.stat(current);
|
|
107
|
+
return stat.isDirectory();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const gather =
|
|
111
|
+
(props: IProps) =>
|
|
112
|
+
(container: string[]) =>
|
|
113
|
+
(from: string) =>
|
|
114
|
+
async (to: string) => {
|
|
115
|
+
if (from === props.output) return;
|
|
116
|
+
else if (fs.existsSync(to) === false) await fs.promises.mkdir(to);
|
|
117
|
+
|
|
118
|
+
for (const file of await fs.promises.readdir(from)) {
|
|
119
|
+
const next: string = path.join(from, file);
|
|
120
|
+
const stat: fs.Stats = await fs.promises.stat(next);
|
|
121
|
+
|
|
122
|
+
if (stat.isDirectory()) {
|
|
123
|
+
await gather(props)(container)(next)(path.join(to, file));
|
|
124
|
+
continue;
|
|
125
|
+
} else if (file.substring(file.length - 3) === ".ts")
|
|
126
|
+
container.push(next);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { ITransformOptions } from "../../transformers/ITransformOptions";
|
|
2
|
-
|
|
3
|
-
export namespace OptionPredicator {
|
|
4
|
-
export const numeric = (options: ITransformOptions): boolean =>
|
|
5
|
-
finite(options) || options.numeric === true;
|
|
6
|
-
|
|
7
|
-
export const functional = (options: ITransformOptions): boolean =>
|
|
8
|
-
options.functional === true;
|
|
9
|
-
|
|
10
|
-
export const finite = (options: ITransformOptions): boolean =>
|
|
11
|
-
options.finite === true;
|
|
12
|
-
|
|
13
|
-
export const undefined = (options: ITransformOptions): boolean =>
|
|
14
|
-
options.undefined !== false;
|
|
15
|
-
}
|
|
1
|
+
import { ITransformOptions } from "../../transformers/ITransformOptions";
|
|
2
|
+
|
|
3
|
+
export namespace OptionPredicator {
|
|
4
|
+
export const numeric = (options: ITransformOptions): boolean =>
|
|
5
|
+
finite(options) || options.numeric === true;
|
|
6
|
+
|
|
7
|
+
export const functional = (options: ITransformOptions): boolean =>
|
|
8
|
+
options.functional === true;
|
|
9
|
+
|
|
10
|
+
export const finite = (options: ITransformOptions): boolean =>
|
|
11
|
+
options.finite === true;
|
|
12
|
+
|
|
13
|
+
export const undefined = (options: ITransformOptions): boolean =>
|
|
14
|
+
options.undefined !== false;
|
|
15
|
+
}
|
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
import { CommentFactory } from "../../factories/CommentFactory";
|
|
2
|
-
|
|
3
|
-
import { IJsDocTagInfo } from "../../metadata/IJsDocTagInfo";
|
|
4
|
-
import { Metadata } from "../../metadata/Metadata";
|
|
5
|
-
import { MetadataObject } from "../../metadata/MetadataObject";
|
|
6
|
-
import { IJsonComponents } from "../../schemas/IJsonComponents";
|
|
7
|
-
|
|
8
|
-
import { PatternUtil } from "../../utils/PatternUtil";
|
|
9
|
-
|
|
10
|
-
import { IJsonSchema } from "../../module";
|
|
11
|
-
import { ApplicationProgrammer } from "../ApplicationProgrammer";
|
|
12
|
-
import { JSON_COMPONENTS_PREFIX } from "./JSON_SCHEMA_PREFIX";
|
|
13
|
-
import { application_schema } from "./application_schema";
|
|
14
|
-
import { metadata_to_pattern } from "./metadata_to_pattern";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @internal
|
|
18
|
-
*/
|
|
19
|
-
export const application_object =
|
|
20
|
-
(options: ApplicationProgrammer.IOptions) =>
|
|
21
|
-
(components: IJsonComponents) =>
|
|
22
|
-
(obj: MetadataObject) =>
|
|
23
|
-
(nullable: boolean): IJsonSchema.IReference => {
|
|
24
|
-
const key: string =
|
|
25
|
-
options.purpose === "ajv"
|
|
26
|
-
? obj.name
|
|
27
|
-
: `${obj.name}${nullable ? ".Nullable" : ""}`;
|
|
28
|
-
const $id: string = `${JSON_COMPONENTS_PREFIX}/schemas/${key}`;
|
|
29
|
-
const output = { $ref: $id };
|
|
30
|
-
|
|
31
|
-
// TEMPORARY ASSIGNMENT
|
|
32
|
-
if (components.schemas?.[key] !== undefined) return output;
|
|
33
|
-
components.schemas ??= {};
|
|
34
|
-
components.schemas[key] = {} as any;
|
|
35
|
-
|
|
36
|
-
// ITERATE PROPERTIES
|
|
37
|
-
const properties: Record<string, any> = {};
|
|
38
|
-
const extraMeta: ISuperfluous = {
|
|
39
|
-
patternProperties: {},
|
|
40
|
-
additionalProperties: undefined,
|
|
41
|
-
};
|
|
42
|
-
const required: string[] = [];
|
|
43
|
-
|
|
44
|
-
for (const property of obj.properties) {
|
|
45
|
-
if (
|
|
46
|
-
// FUNCTIONAL TYPE
|
|
47
|
-
property.value.functional === true &&
|
|
48
|
-
property.value.nullable === false &&
|
|
49
|
-
property.value.isRequired() === true &&
|
|
50
|
-
property.value.size() === 0
|
|
51
|
-
)
|
|
52
|
-
continue;
|
|
53
|
-
else if (property.jsDocTags.find((tag) => tag.name === "hidden"))
|
|
54
|
-
continue; // THE HIDDEN TAG
|
|
55
|
-
|
|
56
|
-
const key: string | null = property.key.getSoleLiteral();
|
|
57
|
-
const schema: IJsonSchema | null = application_schema(options)(
|
|
58
|
-
true,
|
|
59
|
-
)(components)(property.value)({
|
|
60
|
-
deprecated:
|
|
61
|
-
property.jsDocTags.some(
|
|
62
|
-
(tag) => tag.name === "deprecated",
|
|
63
|
-
) || undefined,
|
|
64
|
-
title: (() => {
|
|
65
|
-
const info: IJsDocTagInfo | undefined =
|
|
66
|
-
property.jsDocTags.find((tag) => tag.name === "title");
|
|
67
|
-
return info?.text?.length
|
|
68
|
-
? CommentFactory.merge(info.text)
|
|
69
|
-
: undefined;
|
|
70
|
-
})(),
|
|
71
|
-
description: property.description ?? undefined,
|
|
72
|
-
"x-typia-metaTags": property.tags.length
|
|
73
|
-
? property.tags
|
|
74
|
-
: undefined,
|
|
75
|
-
"x-typia-jsDocTags": property.jsDocTags.length
|
|
76
|
-
? property.jsDocTags
|
|
77
|
-
: undefined,
|
|
78
|
-
"x-typia-required": property.value.required,
|
|
79
|
-
"x-typia-optional": property.value.optional,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
if (schema === null) continue;
|
|
83
|
-
else if (key !== null) {
|
|
84
|
-
properties[key] = schema;
|
|
85
|
-
if (property.value.isRequired() === true) required.push(key);
|
|
86
|
-
} else {
|
|
87
|
-
const pattern: string = metadata_to_pattern(true)(property.key);
|
|
88
|
-
if (pattern === PatternUtil.STRING)
|
|
89
|
-
extraMeta.additionalProperties = [property.value, schema];
|
|
90
|
-
else
|
|
91
|
-
extraMeta.patternProperties[pattern] = [
|
|
92
|
-
property.value,
|
|
93
|
-
schema,
|
|
94
|
-
];
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const extraProps = {
|
|
99
|
-
additionalProperties: extraMeta.additionalProperties?.[1],
|
|
100
|
-
patternProperties: (() => {
|
|
101
|
-
if (Object.keys(extraMeta.patternProperties).length === 0)
|
|
102
|
-
return undefined;
|
|
103
|
-
const output: Record<string, IJsonSchema> = {};
|
|
104
|
-
for (const [key, value] of Object.entries(
|
|
105
|
-
extraMeta.patternProperties,
|
|
106
|
-
))
|
|
107
|
-
output[key] = value[1];
|
|
108
|
-
return output;
|
|
109
|
-
})(),
|
|
110
|
-
};
|
|
111
|
-
const schema: IJsonComponents.IObject = {
|
|
112
|
-
$id: options.purpose === "ajv" ? $id : undefined,
|
|
113
|
-
// $recursiveAnchor:
|
|
114
|
-
// (options.purpose === "ajv" && obj.recursive) || undefined,
|
|
115
|
-
type: "object",
|
|
116
|
-
properties,
|
|
117
|
-
nullable: options.purpose === "swagger" ? nullable : undefined,
|
|
118
|
-
required: required.length ? required : undefined,
|
|
119
|
-
description: obj.description,
|
|
120
|
-
"x-typia-jsDocTags": obj.jsDocTags,
|
|
121
|
-
...(options.purpose === "ajv"
|
|
122
|
-
? extraProps
|
|
123
|
-
: {
|
|
124
|
-
// swagger can't express patternProperties
|
|
125
|
-
"x-typia-additionalProperties":
|
|
126
|
-
extraProps.additionalProperties,
|
|
127
|
-
"x-typia-patternProperties": extraProps.patternProperties,
|
|
128
|
-
additionalProperties:
|
|
129
|
-
join(options)(components)(extraMeta),
|
|
130
|
-
}),
|
|
131
|
-
};
|
|
132
|
-
components.schemas[key] = schema;
|
|
133
|
-
return output;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const join =
|
|
137
|
-
(options: ApplicationProgrammer.IOptions) =>
|
|
138
|
-
(components: IJsonComponents) =>
|
|
139
|
-
(extra: ISuperfluous): IJsonSchema | undefined => {
|
|
140
|
-
// LIST UP METADATA
|
|
141
|
-
const elements: [Metadata, IJsonSchema][] = Object.values(
|
|
142
|
-
extra.patternProperties || {},
|
|
143
|
-
);
|
|
144
|
-
if (extra.additionalProperties)
|
|
145
|
-
elements.push(extra.additionalProperties);
|
|
146
|
-
|
|
147
|
-
// SHORT RETURN
|
|
148
|
-
if (elements.length === 0) return undefined;
|
|
149
|
-
else if (elements.length === 1) return elements[0]![1]!;
|
|
150
|
-
|
|
151
|
-
// MERGE METADATA AND GENERATE VULNERABLE SCHEMA
|
|
152
|
-
const meta: Metadata = elements
|
|
153
|
-
.map((tuple) => tuple[0])
|
|
154
|
-
.reduce((x, y) => Metadata.merge(x, y));
|
|
155
|
-
return (
|
|
156
|
-
application_schema(options)(true)(components)(meta)({
|
|
157
|
-
"x-typia-required": false,
|
|
158
|
-
}) ?? undefined
|
|
159
|
-
);
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
interface ISuperfluous {
|
|
163
|
-
additionalProperties?: [Metadata, IJsonSchema];
|
|
164
|
-
patternProperties: Record<string, [Metadata, IJsonSchema]>;
|
|
165
|
-
}
|
|
1
|
+
import { CommentFactory } from "../../factories/CommentFactory";
|
|
2
|
+
|
|
3
|
+
import { IJsDocTagInfo } from "../../metadata/IJsDocTagInfo";
|
|
4
|
+
import { Metadata } from "../../metadata/Metadata";
|
|
5
|
+
import { MetadataObject } from "../../metadata/MetadataObject";
|
|
6
|
+
import { IJsonComponents } from "../../schemas/IJsonComponents";
|
|
7
|
+
|
|
8
|
+
import { PatternUtil } from "../../utils/PatternUtil";
|
|
9
|
+
|
|
10
|
+
import { IJsonSchema } from "../../module";
|
|
11
|
+
import { ApplicationProgrammer } from "../ApplicationProgrammer";
|
|
12
|
+
import { JSON_COMPONENTS_PREFIX } from "./JSON_SCHEMA_PREFIX";
|
|
13
|
+
import { application_schema } from "./application_schema";
|
|
14
|
+
import { metadata_to_pattern } from "./metadata_to_pattern";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export const application_object =
|
|
20
|
+
(options: ApplicationProgrammer.IOptions) =>
|
|
21
|
+
(components: IJsonComponents) =>
|
|
22
|
+
(obj: MetadataObject) =>
|
|
23
|
+
(nullable: boolean): IJsonSchema.IReference => {
|
|
24
|
+
const key: string =
|
|
25
|
+
options.purpose === "ajv"
|
|
26
|
+
? obj.name
|
|
27
|
+
: `${obj.name}${nullable ? ".Nullable" : ""}`;
|
|
28
|
+
const $id: string = `${JSON_COMPONENTS_PREFIX}/schemas/${key}`;
|
|
29
|
+
const output = { $ref: $id };
|
|
30
|
+
|
|
31
|
+
// TEMPORARY ASSIGNMENT
|
|
32
|
+
if (components.schemas?.[key] !== undefined) return output;
|
|
33
|
+
components.schemas ??= {};
|
|
34
|
+
components.schemas[key] = {} as any;
|
|
35
|
+
|
|
36
|
+
// ITERATE PROPERTIES
|
|
37
|
+
const properties: Record<string, any> = {};
|
|
38
|
+
const extraMeta: ISuperfluous = {
|
|
39
|
+
patternProperties: {},
|
|
40
|
+
additionalProperties: undefined,
|
|
41
|
+
};
|
|
42
|
+
const required: string[] = [];
|
|
43
|
+
|
|
44
|
+
for (const property of obj.properties) {
|
|
45
|
+
if (
|
|
46
|
+
// FUNCTIONAL TYPE
|
|
47
|
+
property.value.functional === true &&
|
|
48
|
+
property.value.nullable === false &&
|
|
49
|
+
property.value.isRequired() === true &&
|
|
50
|
+
property.value.size() === 0
|
|
51
|
+
)
|
|
52
|
+
continue;
|
|
53
|
+
else if (property.jsDocTags.find((tag) => tag.name === "hidden"))
|
|
54
|
+
continue; // THE HIDDEN TAG
|
|
55
|
+
|
|
56
|
+
const key: string | null = property.key.getSoleLiteral();
|
|
57
|
+
const schema: IJsonSchema | null = application_schema(options)(
|
|
58
|
+
true,
|
|
59
|
+
)(components)(property.value)({
|
|
60
|
+
deprecated:
|
|
61
|
+
property.jsDocTags.some(
|
|
62
|
+
(tag) => tag.name === "deprecated",
|
|
63
|
+
) || undefined,
|
|
64
|
+
title: (() => {
|
|
65
|
+
const info: IJsDocTagInfo | undefined =
|
|
66
|
+
property.jsDocTags.find((tag) => tag.name === "title");
|
|
67
|
+
return info?.text?.length
|
|
68
|
+
? CommentFactory.merge(info.text)
|
|
69
|
+
: undefined;
|
|
70
|
+
})(),
|
|
71
|
+
description: property.description ?? undefined,
|
|
72
|
+
"x-typia-metaTags": property.tags.length
|
|
73
|
+
? property.tags
|
|
74
|
+
: undefined,
|
|
75
|
+
"x-typia-jsDocTags": property.jsDocTags.length
|
|
76
|
+
? property.jsDocTags
|
|
77
|
+
: undefined,
|
|
78
|
+
"x-typia-required": property.value.required,
|
|
79
|
+
"x-typia-optional": property.value.optional,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (schema === null) continue;
|
|
83
|
+
else if (key !== null) {
|
|
84
|
+
properties[key] = schema;
|
|
85
|
+
if (property.value.isRequired() === true) required.push(key);
|
|
86
|
+
} else {
|
|
87
|
+
const pattern: string = metadata_to_pattern(true)(property.key);
|
|
88
|
+
if (pattern === PatternUtil.STRING)
|
|
89
|
+
extraMeta.additionalProperties = [property.value, schema];
|
|
90
|
+
else
|
|
91
|
+
extraMeta.patternProperties[pattern] = [
|
|
92
|
+
property.value,
|
|
93
|
+
schema,
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const extraProps = {
|
|
99
|
+
additionalProperties: extraMeta.additionalProperties?.[1],
|
|
100
|
+
patternProperties: (() => {
|
|
101
|
+
if (Object.keys(extraMeta.patternProperties).length === 0)
|
|
102
|
+
return undefined;
|
|
103
|
+
const output: Record<string, IJsonSchema> = {};
|
|
104
|
+
for (const [key, value] of Object.entries(
|
|
105
|
+
extraMeta.patternProperties,
|
|
106
|
+
))
|
|
107
|
+
output[key] = value[1];
|
|
108
|
+
return output;
|
|
109
|
+
})(),
|
|
110
|
+
};
|
|
111
|
+
const schema: IJsonComponents.IObject = {
|
|
112
|
+
$id: options.purpose === "ajv" ? $id : undefined,
|
|
113
|
+
// $recursiveAnchor:
|
|
114
|
+
// (options.purpose === "ajv" && obj.recursive) || undefined,
|
|
115
|
+
type: "object",
|
|
116
|
+
properties,
|
|
117
|
+
nullable: options.purpose === "swagger" ? nullable : undefined,
|
|
118
|
+
required: required.length ? required : undefined,
|
|
119
|
+
description: obj.description,
|
|
120
|
+
"x-typia-jsDocTags": obj.jsDocTags,
|
|
121
|
+
...(options.purpose === "ajv"
|
|
122
|
+
? extraProps
|
|
123
|
+
: {
|
|
124
|
+
// swagger can't express patternProperties
|
|
125
|
+
"x-typia-additionalProperties":
|
|
126
|
+
extraProps.additionalProperties,
|
|
127
|
+
"x-typia-patternProperties": extraProps.patternProperties,
|
|
128
|
+
additionalProperties:
|
|
129
|
+
join(options)(components)(extraMeta),
|
|
130
|
+
}),
|
|
131
|
+
};
|
|
132
|
+
components.schemas[key] = schema;
|
|
133
|
+
return output;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const join =
|
|
137
|
+
(options: ApplicationProgrammer.IOptions) =>
|
|
138
|
+
(components: IJsonComponents) =>
|
|
139
|
+
(extra: ISuperfluous): IJsonSchema | undefined => {
|
|
140
|
+
// LIST UP METADATA
|
|
141
|
+
const elements: [Metadata, IJsonSchema][] = Object.values(
|
|
142
|
+
extra.patternProperties || {},
|
|
143
|
+
);
|
|
144
|
+
if (extra.additionalProperties)
|
|
145
|
+
elements.push(extra.additionalProperties);
|
|
146
|
+
|
|
147
|
+
// SHORT RETURN
|
|
148
|
+
if (elements.length === 0) return undefined;
|
|
149
|
+
else if (elements.length === 1) return elements[0]![1]!;
|
|
150
|
+
|
|
151
|
+
// MERGE METADATA AND GENERATE VULNERABLE SCHEMA
|
|
152
|
+
const meta: Metadata = elements
|
|
153
|
+
.map((tuple) => tuple[0])
|
|
154
|
+
.reduce((x, y) => Metadata.merge(x, y));
|
|
155
|
+
return (
|
|
156
|
+
application_schema(options)(true)(components)(meta)({
|
|
157
|
+
"x-typia-required": false,
|
|
158
|
+
}) ?? undefined
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
interface ISuperfluous {
|
|
163
|
+
additionalProperties?: [Metadata, IJsonSchema];
|
|
164
|
+
patternProperties: Record<string, [Metadata, IJsonSchema]>;
|
|
165
|
+
}
|