@goast/kotlin 0.6.0-beta.1 → 0.6.0
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/LICENSE +21 -21
- package/README.md +45 -45
- package/assets/client/okhttp3/ApiAbstractions.kt +30 -30
- package/assets/client/okhttp3/ApiClient.kt +252 -252
- package/assets/client/okhttp3/ApiResponse.kt +43 -43
- package/assets/client/okhttp3/Errors.kt +21 -21
- package/assets/client/okhttp3/PartConfig.kt +11 -11
- package/assets/client/okhttp3/RequestConfig.kt +18 -18
- package/assets/client/okhttp3/RequestMethod.kt +8 -8
- package/assets/client/okhttp3/ResponseExtensions.kt +24 -24
- package/assets/client/spring-reactive-web-clients/ApiRequestFile.kt +33 -33
- package/esm/src/ast/references/jackson.d.ts +8 -0
- package/esm/src/ast/references/jackson.d.ts.map +1 -1
- package/esm/src/ast/references/jackson.js +8 -0
- package/esm/src/generators/file-generator.d.ts +21 -1
- package/esm/src/generators/file-generator.d.ts.map +1 -1
- package/esm/src/generators/file-generator.js +41 -2
- package/esm/src/generators/models/args.d.ts +5 -0
- package/esm/src/generators/models/args.d.ts.map +1 -1
- package/esm/src/generators/models/model-generator.d.ts +23 -0
- package/esm/src/generators/models/model-generator.d.ts.map +1 -1
- package/esm/src/generators/models/model-generator.js +130 -13
- package/esm/src/generators/services/okhttp3-clients/okhttp3-client-generator.d.ts +9 -0
- package/esm/src/generators/services/okhttp3-clients/okhttp3-client-generator.d.ts.map +1 -1
- package/esm/src/generators/services/okhttp3-clients/okhttp3-client-generator.js +59 -41
- package/esm/src/generators/services/okhttp3-clients/okhttp3-clients-generator.d.ts.map +1 -1
- package/esm/src/generators/services/okhttp3-clients/okhttp3-clients-generator.js +11 -9
- package/esm/src/generators/services/spring-controllers/spring-controller-generator.d.ts.map +1 -1
- package/esm/src/generators/services/spring-controllers/spring-controller-generator.js +53 -35
- package/esm/src/generators/services/spring-reactive-web-clients/args.d.ts +3 -0
- package/esm/src/generators/services/spring-reactive-web-clients/args.d.ts.map +1 -1
- package/esm/src/generators/services/spring-reactive-web-clients/spring-reactive-web-client-generator.d.ts +7 -0
- package/esm/src/generators/services/spring-reactive-web-clients/spring-reactive-web-client-generator.d.ts.map +1 -1
- package/esm/src/generators/services/spring-reactive-web-clients/spring-reactive-web-client-generator.js +41 -20
- package/esm/src/import-collection.d.ts.map +1 -1
- package/esm/src/import-collection.js +2 -2
- package/esm/src/utils.d.ts +3 -1
- package/esm/src/utils.d.ts.map +1 -1
- package/esm/src/utils.js +7 -0
- package/package.json +2 -2
- package/script/src/ast/references/jackson.d.ts +8 -0
- package/script/src/ast/references/jackson.d.ts.map +1 -1
- package/script/src/ast/references/jackson.js +9 -1
- package/script/src/generators/file-generator.d.ts +21 -1
- package/script/src/generators/file-generator.d.ts.map +1 -1
- package/script/src/generators/file-generator.js +41 -2
- package/script/src/generators/models/args.d.ts +5 -0
- package/script/src/generators/models/args.d.ts.map +1 -1
- package/script/src/generators/models/model-generator.d.ts +23 -0
- package/script/src/generators/models/model-generator.d.ts.map +1 -1
- package/script/src/generators/models/model-generator.js +129 -12
- package/script/src/generators/services/okhttp3-clients/okhttp3-client-generator.d.ts +9 -0
- package/script/src/generators/services/okhttp3-clients/okhttp3-client-generator.d.ts.map +1 -1
- package/script/src/generators/services/okhttp3-clients/okhttp3-client-generator.js +58 -40
- package/script/src/generators/services/okhttp3-clients/okhttp3-clients-generator.d.ts.map +1 -1
- package/script/src/generators/services/okhttp3-clients/okhttp3-clients-generator.js +11 -9
- package/script/src/generators/services/spring-controllers/spring-controller-generator.d.ts.map +1 -1
- package/script/src/generators/services/spring-controllers/spring-controller-generator.js +52 -34
- package/script/src/generators/services/spring-reactive-web-clients/args.d.ts +3 -0
- package/script/src/generators/services/spring-reactive-web-clients/args.d.ts.map +1 -1
- package/script/src/generators/services/spring-reactive-web-clients/spring-reactive-web-client-generator.d.ts +7 -0
- package/script/src/generators/services/spring-reactive-web-clients/spring-reactive-web-client-generator.d.ts.map +1 -1
- package/script/src/generators/services/spring-reactive-web-clients/spring-reactive-web-client-generator.js +40 -19
- package/script/src/import-collection.d.ts.map +1 -1
- package/script/src/import-collection.js +1 -1
- package/script/src/utils.d.ts +3 -1
- package/script/src/utils.d.ts.map +1 -1
- package/script/src/utils.js +9 -0
- package/src/mod.ts +8 -0
- package/src/src/assets.ts +9 -0
- package/src/src/ast/_index.ts +66 -0
- package/src/src/ast/common.ts +1 -0
- package/src/src/ast/index.ts +1 -0
- package/src/src/ast/node.ts +10 -0
- package/src/src/ast/nodes/annotation.ts +79 -0
- package/src/src/ast/nodes/argument.ts +62 -0
- package/src/src/ast/nodes/call.ts +75 -0
- package/src/src/ast/nodes/class.ts +178 -0
- package/src/src/ast/nodes/collection-literal.ts +49 -0
- package/src/src/ast/nodes/constructor.ts +126 -0
- package/src/src/ast/nodes/doc-tag.ts +138 -0
- package/src/src/ast/nodes/doc.ts +111 -0
- package/src/src/ast/nodes/enum-value.ts +100 -0
- package/src/src/ast/nodes/enum.ts +163 -0
- package/src/src/ast/nodes/function.ts +178 -0
- package/src/src/ast/nodes/generic-parameter.ts +54 -0
- package/src/src/ast/nodes/init-block.ts +38 -0
- package/src/src/ast/nodes/interface.ts +133 -0
- package/src/src/ast/nodes/lambda-type.ts +73 -0
- package/src/src/ast/nodes/lambda.ts +74 -0
- package/src/src/ast/nodes/object.ts +102 -0
- package/src/src/ast/nodes/parameter.ts +118 -0
- package/src/src/ast/nodes/property.ts +225 -0
- package/src/src/ast/nodes/reference.ts +178 -0
- package/src/src/ast/nodes/string.ts +114 -0
- package/src/src/ast/nodes/types.ts +23 -0
- package/src/src/ast/references/index.ts +10 -0
- package/src/src/ast/references/jackson.ts +70 -0
- package/src/src/ast/references/jakarta.ts +14 -0
- package/src/src/ast/references/java.ts +27 -0
- package/src/src/ast/references/kotlin.ts +41 -0
- package/src/src/ast/references/kotlinx.ts +14 -0
- package/src/src/ast/references/okhttp3.ts +5 -0
- package/src/src/ast/references/reactor.ts +5 -0
- package/src/src/ast/references/spring-reactive.ts +37 -0
- package/src/src/ast/references/spring.ts +86 -0
- package/src/src/ast/references/swagger.ts +23 -0
- package/src/src/ast/utils/get-kotlin-builder-options.ts +19 -0
- package/src/src/ast/utils/to-kt-node.ts +31 -0
- package/src/src/ast/utils/write-kt-annotations.ts +15 -0
- package/src/src/ast/utils/write-kt-arguments.ts +45 -0
- package/src/src/ast/utils/write-kt-enum-values.ts +27 -0
- package/src/src/ast/utils/write-kt-generic-parameters.ts +12 -0
- package/src/src/ast/utils/write-kt-members.ts +25 -0
- package/src/src/ast/utils/write-kt-node.ts +37 -0
- package/src/src/ast/utils/write-kt-parameters.ts +25 -0
- package/src/src/common-results.ts +4 -0
- package/src/src/config.ts +61 -0
- package/src/src/file-builder.ts +112 -0
- package/src/src/generators/file-generator.ts +71 -0
- package/src/src/generators/index.ts +5 -0
- package/src/src/generators/models/args.ts +142 -0
- package/src/src/generators/models/index.ts +4 -0
- package/src/src/generators/models/model-generator.ts +850 -0
- package/src/src/generators/models/models-generator.ts +65 -0
- package/src/src/generators/models/models.ts +95 -0
- package/src/src/generators/services/okhttp3-clients/args.ts +90 -0
- package/src/src/generators/services/okhttp3-clients/index.ts +4 -0
- package/src/src/generators/services/okhttp3-clients/models.ts +73 -0
- package/src/src/generators/services/okhttp3-clients/okhttp3-client-generator.ts +639 -0
- package/src/src/generators/services/okhttp3-clients/okhttp3-clients-generator.ts +190 -0
- package/src/src/generators/services/okhttp3-clients/refs.ts +59 -0
- package/src/src/generators/services/spring-controllers/args.ts +95 -0
- package/src/src/generators/services/spring-controllers/index.ts +4 -0
- package/src/src/generators/services/spring-controllers/models.ts +76 -0
- package/src/src/generators/services/spring-controllers/refs.ts +17 -0
- package/src/src/generators/services/spring-controllers/spring-controller-generator.ts +1129 -0
- package/src/src/generators/services/spring-controllers/spring-controllers-generator.ts +140 -0
- package/src/src/generators/services/spring-reactive-web-clients/args.ts +114 -0
- package/src/src/generators/services/spring-reactive-web-clients/index.ts +4 -0
- package/src/src/generators/services/spring-reactive-web-clients/models.ts +78 -0
- package/src/src/generators/services/spring-reactive-web-clients/refs.ts +11 -0
- package/src/src/generators/services/spring-reactive-web-clients/spring-reactive-web-client-generator.ts +657 -0
- package/src/src/generators/services/spring-reactive-web-clients/spring-reactive-web-clients-generator.ts +125 -0
- package/src/src/import-collection.ts +98 -0
- package/src/src/types.ts +3 -0
- package/src/src/utils.ts +55 -0
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ApiSchema,
|
|
3
|
+
type ApiSchemaKind,
|
|
4
|
+
type ApiSchemaProperty,
|
|
5
|
+
type AppendValue,
|
|
6
|
+
type AppendValueGroup,
|
|
7
|
+
appendValueGroup,
|
|
8
|
+
type BasicAppendValue,
|
|
9
|
+
builderTemplate as s,
|
|
10
|
+
createOverwriteProxy,
|
|
11
|
+
DEFAULT_IGNORED_SCHEMA_PROPERTIES,
|
|
12
|
+
getSchemaReference,
|
|
13
|
+
getSourceDisplayName,
|
|
14
|
+
type MaybePromise,
|
|
15
|
+
modify,
|
|
16
|
+
modifyEach,
|
|
17
|
+
notNullish,
|
|
18
|
+
resolveAnyOfAndAllOf,
|
|
19
|
+
type SourceBuilder,
|
|
20
|
+
toCasing,
|
|
21
|
+
} from '@goast/core';
|
|
22
|
+
|
|
23
|
+
import { kt } from '../../ast/index.js';
|
|
24
|
+
import { KotlinFileBuilder } from '../../file-builder.js';
|
|
25
|
+
import { getSourceDocLine, getStringEnumSchema } from '../../utils.js';
|
|
26
|
+
import { KotlinFileGenerator } from '../file-generator.js';
|
|
27
|
+
import type { DefaultKotlinModelGeneratorArgs as Args } from './index.js';
|
|
28
|
+
import type { KotlinModelGeneratorContext, KotlinModelGeneratorOutput } from './models.js';
|
|
29
|
+
|
|
30
|
+
type Context = KotlinModelGeneratorContext;
|
|
31
|
+
type Output = KotlinModelGeneratorOutput;
|
|
32
|
+
type Builder = KotlinFileBuilder;
|
|
33
|
+
|
|
34
|
+
export interface KotlinModelGenerator<TOutput extends Output = Output> {
|
|
35
|
+
generate(ctx: Context): MaybePromise<TOutput>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class DefaultKotlinModelGenerator extends KotlinFileGenerator<Context, Output> implements KotlinModelGenerator {
|
|
39
|
+
public generate(ctx: Context): MaybePromise<KotlinModelGeneratorOutput> {
|
|
40
|
+
if (/\/(anyOf|allOf)(\/[0-9]+)?$/.test(ctx.schema.$src.path)) {
|
|
41
|
+
// Do not generate types that are only used for anyOf and/or allOf
|
|
42
|
+
return { type: kt.refs.any({ nullable: true }) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const schemaReference = getSchemaReference(ctx.schema, DEFAULT_IGNORED_SCHEMA_PROPERTIES);
|
|
46
|
+
if (schemaReference !== ctx.schema) {
|
|
47
|
+
const typeName = this.getDeclarationTypeName(ctx, { schema: schemaReference });
|
|
48
|
+
const packageName = this.getPackageName(ctx, { schema: schemaReference });
|
|
49
|
+
return { type: kt.reference(typeName, packageName) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (this.shouldGenerateTypeDeclaration(ctx, { schema: ctx.schema })) {
|
|
53
|
+
const typeName = this.getDeclarationTypeName(ctx, { schema: ctx.schema });
|
|
54
|
+
const packageName = this.getPackageName(ctx, { schema: ctx.schema });
|
|
55
|
+
const filePath = `${ctx.config.outputDir}/${packageName.replace(/\./g, '/')}/${typeName}.kt`;
|
|
56
|
+
|
|
57
|
+
console.log(
|
|
58
|
+
`Generating model ${getSourceDisplayName(ctx.data, ctx.schema)} [${packageName}.${typeName}] to ${filePath}...`,
|
|
59
|
+
);
|
|
60
|
+
const builder = new KotlinFileBuilder(packageName, ctx.config);
|
|
61
|
+
builder.append(this.getFileContent(ctx, {}));
|
|
62
|
+
builder.writeToFile(filePath);
|
|
63
|
+
|
|
64
|
+
return { type: kt.reference(typeName, packageName) };
|
|
65
|
+
} else {
|
|
66
|
+
return { type: this.getType(ctx, { schema: ctx.schema }) };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
protected getFileContent(ctx: Context, _args: Args.GetFileContent): AppendValueGroup<Builder> {
|
|
71
|
+
return appendValueGroup<Builder>(
|
|
72
|
+
[this.getSchemaDeclaration(ctx, { schema: this.normalizeSchema(ctx, { schema: ctx.schema }) })],
|
|
73
|
+
'\n\n',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
protected getSchemaDeclaration(ctx: Context, args: Args.GetSchemaDeclaration): AppendValue<Builder> {
|
|
78
|
+
const { schema } = args;
|
|
79
|
+
|
|
80
|
+
if (schema.kind === 'object') {
|
|
81
|
+
return schema.discriminator ? this.getInterface(ctx, { schema }) : this.getClass(ctx, { schema });
|
|
82
|
+
} else if (schema.enum !== undefined && schema.enum.length > 0) {
|
|
83
|
+
return this.getEnum(ctx, { schema });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return '// The generator was not able to generate this schema.\n// This should not happend. If you see this comment, please open an Issue on Github.';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
protected getClass(ctx: Context, args: Args.GetClass): kt.Class<Builder> {
|
|
90
|
+
const { schema } = args;
|
|
91
|
+
const inheritedSchemas = this.getInheritedSchemas(ctx, { schema });
|
|
92
|
+
const parameters = this.getClassProperties(ctx, { schema });
|
|
93
|
+
|
|
94
|
+
return kt.class(this.getDeclarationTypeName(ctx, { schema }), {
|
|
95
|
+
doc: kt.doc(this.getSchemaDocDescription(ctx, { schema })),
|
|
96
|
+
annotations: [
|
|
97
|
+
this.getJacksonJsonClassDescriptionAnnotation(ctx, { schema }),
|
|
98
|
+
schema.deprecated ? kt.annotation(kt.refs.deprecated(), [kt.argument(kt.string(''))]) : null,
|
|
99
|
+
],
|
|
100
|
+
classKind: parameters.length === 0 ? null : 'data',
|
|
101
|
+
implements: inheritedSchemas.map((schema) => this.getType(ctx, { schema, nullable: false })),
|
|
102
|
+
primaryConstructor: kt.constructor(
|
|
103
|
+
parameters.map((property) => this.getClassParameter(ctx, { ...args, inheritedSchemas, parameters, property })),
|
|
104
|
+
),
|
|
105
|
+
members: [
|
|
106
|
+
...(schema.additionalProperties !== undefined && schema.additionalProperties !== false
|
|
107
|
+
? [
|
|
108
|
+
this.getAdditionalPropertiesProperty(ctx, { schema }),
|
|
109
|
+
this.getAdditionalPropertiesSetter(ctx, { schema }),
|
|
110
|
+
this.getAdditionalPropertiesGetter(ctx, { schema }),
|
|
111
|
+
]
|
|
112
|
+
: []),
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
protected getInterface(ctx: Context, args: Args.GetInterface): kt.Interface<Builder> {
|
|
118
|
+
const { schema } = args;
|
|
119
|
+
|
|
120
|
+
return kt.interface(this.getDeclarationTypeName(ctx, { schema }), {
|
|
121
|
+
doc: kt.doc(this.getSchemaDocDescription(ctx, { schema })),
|
|
122
|
+
annotations: [
|
|
123
|
+
this.getJacksonJsonTypeInfoAnnotation(ctx, { schema }),
|
|
124
|
+
this.getJacksonJsonSubTypesAnnotation(ctx, { schema }),
|
|
125
|
+
this.getJacksonJsonClassDescriptionAnnotation(ctx, { schema }),
|
|
126
|
+
schema.deprecated ? kt.annotation(kt.refs.deprecated(), [kt.argument(kt.string(''))]) : null,
|
|
127
|
+
].filter(notNullish),
|
|
128
|
+
members: this.sortProperties(ctx, { schema, properties: schema.properties.values() }).map((property) =>
|
|
129
|
+
this.getInterfaceProperty(ctx, { schema, property })
|
|
130
|
+
),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
protected getEnum(ctx: Context, args: Args.GetEnum): kt.Enum<Builder> {
|
|
135
|
+
const { schema } = args;
|
|
136
|
+
|
|
137
|
+
const name = this.getDeclarationTypeName(ctx, { schema });
|
|
138
|
+
// One set of names for both generation sites below, so the constant and its `when` branch agree.
|
|
139
|
+
const valueNames = this.toEnumValueNames(ctx, schema.enum);
|
|
140
|
+
return kt.enum(
|
|
141
|
+
name,
|
|
142
|
+
schema.enum?.map((x, i) =>
|
|
143
|
+
kt.enumValue(valueNames[i], {
|
|
144
|
+
annotations: [kt.annotation(kt.refs.jackson.jsonProperty(), [kt.argument(kt.string(String(x)))])],
|
|
145
|
+
arguments: [kt.argument(kt.string(String(x)))],
|
|
146
|
+
})
|
|
147
|
+
) ?? [],
|
|
148
|
+
{
|
|
149
|
+
doc: kt.doc(this.getSchemaDocDescription(ctx, { schema })),
|
|
150
|
+
annotations: [schema.deprecated ? kt.annotation(kt.refs.deprecated(), [kt.argument(kt.string(''))]) : null],
|
|
151
|
+
primaryConstructor: kt.constructor([
|
|
152
|
+
kt.parameter.class(toCasing('value', ctx.config.propertyNameCasing), kt.refs.string(), {
|
|
153
|
+
property: 'readonly',
|
|
154
|
+
}),
|
|
155
|
+
]),
|
|
156
|
+
companionObject: kt.object({
|
|
157
|
+
members: [
|
|
158
|
+
kt.function('fromValue', {
|
|
159
|
+
parameters: [kt.parameter('value', kt.refs.string())],
|
|
160
|
+
returnType: kt.reference(name, null, { nullable: true }),
|
|
161
|
+
singleExpression: true,
|
|
162
|
+
body: !schema.enum?.length ? 'null' : s`\nwhen(value) {${s.indent`${
|
|
163
|
+
appendValueGroup([
|
|
164
|
+
schema.enum.map((x, i) => s`\n${kt.string(String(x))} -> ${valueNames[i]}`),
|
|
165
|
+
])
|
|
166
|
+
}
|
|
167
|
+
else -> null`}
|
|
168
|
+
}`,
|
|
169
|
+
}),
|
|
170
|
+
],
|
|
171
|
+
}),
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
protected getSchemaDocDescription(ctx: Context, args: Args.GetSchemaDocDescription): BasicAppendValue<Builder> {
|
|
177
|
+
const docSegments = [args.schema.description?.trim()];
|
|
178
|
+
|
|
179
|
+
if (ctx.config.includeSourceInDocs) {
|
|
180
|
+
docSegments.push(getSourceDocLine(args.schema));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return docSegments.filter(notNullish).join('\n\n');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
protected getType(ctx: Context, args: Args.GetType): kt.Reference<SourceBuilder> {
|
|
187
|
+
let { schema, nullable } = args;
|
|
188
|
+
|
|
189
|
+
if (schema.kind === 'oneOf' && schema.oneOf.length === 2 && schema.oneOf.some((x) => x.kind === 'null')) {
|
|
190
|
+
const nonNullSchema = schema.oneOf.find((x) => x.kind !== 'null')!;
|
|
191
|
+
if (nonNullSchema) {
|
|
192
|
+
schema = nonNullSchema;
|
|
193
|
+
nullable ??= true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const generatedType = this.getGeneratedType(ctx, { schema, nullable });
|
|
198
|
+
if (generatedType) {
|
|
199
|
+
return generatedType;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
nullable ??= schema.nullable;
|
|
203
|
+
switch (schema.kind) {
|
|
204
|
+
case 'boolean':
|
|
205
|
+
return kt.refs.boolean({ nullable });
|
|
206
|
+
case 'integer':
|
|
207
|
+
case 'number':
|
|
208
|
+
switch (schema.format) {
|
|
209
|
+
case 'int32':
|
|
210
|
+
return kt.refs.int({ nullable });
|
|
211
|
+
case 'int64':
|
|
212
|
+
return kt.refs.long({ nullable });
|
|
213
|
+
case 'float':
|
|
214
|
+
return kt.refs.float({ nullable });
|
|
215
|
+
case 'double':
|
|
216
|
+
return kt.refs.double({ nullable });
|
|
217
|
+
default:
|
|
218
|
+
return schema.kind === 'integer' ? kt.refs.int({ nullable }) : kt.refs.double({ nullable });
|
|
219
|
+
}
|
|
220
|
+
case 'string':
|
|
221
|
+
switch (schema.format) {
|
|
222
|
+
case 'date-time':
|
|
223
|
+
return kt.refs.java.offsetDateTime({ nullable });
|
|
224
|
+
case 'time':
|
|
225
|
+
return kt.refs.java.offsetTime({ nullable });
|
|
226
|
+
case 'date':
|
|
227
|
+
return kt.refs.java.localDate({ nullable });
|
|
228
|
+
case 'local-date-time':
|
|
229
|
+
return kt.refs.java.localDateTime({ nullable });
|
|
230
|
+
case 'local-time':
|
|
231
|
+
return kt.refs.java.localTime({ nullable });
|
|
232
|
+
case 'uri':
|
|
233
|
+
return kt.refs.java.uri({ nullable });
|
|
234
|
+
default:
|
|
235
|
+
return kt.refs.string({ nullable });
|
|
236
|
+
}
|
|
237
|
+
case 'null':
|
|
238
|
+
return kt.refs.nothing({ nullable });
|
|
239
|
+
case 'unknown':
|
|
240
|
+
return kt.refs.any({ nullable });
|
|
241
|
+
case 'array':
|
|
242
|
+
return kt.refs.list(
|
|
243
|
+
[schema.items ? this.getType(ctx, { schema: schema.items }) : kt.refs.any({ nullable: true })],
|
|
244
|
+
{ nullable },
|
|
245
|
+
);
|
|
246
|
+
case 'object':
|
|
247
|
+
return schema.properties.size === 0 && schema.additionalProperties
|
|
248
|
+
? kt.refs.map([kt.refs.string(), this.getAdditionalPropertiesType(ctx, { schema })], { nullable })
|
|
249
|
+
: kt.refs.any({ nullable });
|
|
250
|
+
default:
|
|
251
|
+
return kt.refs.any({ nullable });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
protected getGeneratedType(ctx: Context, args: Args.GetGeneratedType): kt.Reference<SourceBuilder> | null {
|
|
256
|
+
const schema = getSchemaReference(args.schema, DEFAULT_IGNORED_SCHEMA_PROPERTIES);
|
|
257
|
+
if (this.shouldGenerateTypeDeclaration(ctx, { schema })) {
|
|
258
|
+
return kt.reference(this.getDeclarationTypeName(ctx, { schema }), this.getPackageName(ctx, { schema }), {
|
|
259
|
+
nullable: args.nullable ?? schema.nullable,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
protected getAdditionalPropertiesType(
|
|
266
|
+
ctx: Context,
|
|
267
|
+
args: Args.GetAdditionalPropertiesType,
|
|
268
|
+
): kt.Reference<SourceBuilder> {
|
|
269
|
+
const { schema } = args;
|
|
270
|
+
|
|
271
|
+
return typeof schema.additionalProperties === 'object'
|
|
272
|
+
? this.getType(ctx, { schema: schema.additionalProperties })
|
|
273
|
+
: kt.refs.any({ nullable: true });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
protected getDefaultValue(ctx: Context, args: Args.GetDefaultValue): kt.Value<Builder> {
|
|
277
|
+
const { schema } = args;
|
|
278
|
+
|
|
279
|
+
if (schema.default === null || schema.default === undefined) {
|
|
280
|
+
return 'null';
|
|
281
|
+
} else {
|
|
282
|
+
switch (schema.kind) {
|
|
283
|
+
case 'boolean':
|
|
284
|
+
return Boolean(schema.default) || String(schema.default).toLowerCase() === 'true' ? 'true' : 'false';
|
|
285
|
+
case 'integer':
|
|
286
|
+
case 'number':
|
|
287
|
+
return String(schema.default);
|
|
288
|
+
case 'string':
|
|
289
|
+
return schema.enum && schema.enum.length > 0
|
|
290
|
+
? kt.call([this.getType(ctx, { schema }), this.toEnumValueName(ctx, schema.enum, schema.default)])
|
|
291
|
+
: kt.string(String(schema.default));
|
|
292
|
+
case 'array': {
|
|
293
|
+
const itemEnumSchema = getStringEnumSchema(schema.items);
|
|
294
|
+
return kt.call(
|
|
295
|
+
kt.refs.listOf.infer(),
|
|
296
|
+
Array.isArray(schema.default)
|
|
297
|
+
? schema.default.map((x) =>
|
|
298
|
+
itemEnumSchema
|
|
299
|
+
? kt.call([
|
|
300
|
+
this.getType(ctx, { schema: itemEnumSchema }),
|
|
301
|
+
this.toEnumValueName(ctx, itemEnumSchema.enum, x),
|
|
302
|
+
])
|
|
303
|
+
: kt.toNode(x)
|
|
304
|
+
)
|
|
305
|
+
: [],
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
default:
|
|
309
|
+
return 'null';
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// #region Members
|
|
315
|
+
protected getClassParameter(ctx: Context, args: Args.GetClassParameter): kt.Parameter<Builder> {
|
|
316
|
+
const { schema, inheritedSchemas, property } = args;
|
|
317
|
+
|
|
318
|
+
return kt.parameter.class(
|
|
319
|
+
toCasing(property.name, ctx.config.propertyNameCasing),
|
|
320
|
+
this.getType(ctx, { schema: property.schema, nullable: schema.required.has(property.name) ? undefined : true }),
|
|
321
|
+
{
|
|
322
|
+
description: property.schema.description?.trim(),
|
|
323
|
+
annotations: [
|
|
324
|
+
...this.getJakartaValidationAnnotations(ctx, { schema, property }),
|
|
325
|
+
this.getSwaggerSchemaAnnotation(ctx, { schema, property }),
|
|
326
|
+
modify(this.getJacksonJsonPropertyAnnotation(ctx, { schema, property }), (x) => (x.target = 'param')),
|
|
327
|
+
...modifyEach(
|
|
328
|
+
[
|
|
329
|
+
this.getJacksonJsonPropertyAnnotation(ctx, { schema, property }),
|
|
330
|
+
this.getJacksonJsonPropertyDescriptionAnnotation(ctx, { schema, property }),
|
|
331
|
+
this.getJacksonJsonIncludeAnnotation(ctx, { schema, property }),
|
|
332
|
+
].filter(notNullish),
|
|
333
|
+
(x) => (x.target = 'get'),
|
|
334
|
+
),
|
|
335
|
+
property.schema.deprecated ? kt.annotation(kt.refs.deprecated(), [kt.argument(kt.string(''))]) : null,
|
|
336
|
+
].filter(notNullish),
|
|
337
|
+
override: inheritedSchemas.some((schema) => this.hasProperty(ctx, { schema, propertyName: property.name })),
|
|
338
|
+
property: 'readonly',
|
|
339
|
+
default: property.schema.default !== undefined || !schema.required.has(property.name)
|
|
340
|
+
? this.getDefaultValue(ctx, { schema: property.schema })
|
|
341
|
+
: null,
|
|
342
|
+
},
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
protected getInterfaceProperty(ctx: Context, args: Args.GetInterfaceProperty): kt.Property<Builder> {
|
|
347
|
+
const { schema, property } = args;
|
|
348
|
+
|
|
349
|
+
return kt.property(toCasing(property.name, ctx.config.propertyNameCasing), {
|
|
350
|
+
doc: kt.doc(this.getSchemaDocDescription(ctx, { schema: property.schema })),
|
|
351
|
+
annotations: modifyEach(
|
|
352
|
+
[
|
|
353
|
+
this.getJacksonJsonPropertyAnnotation(ctx, { schema, property }),
|
|
354
|
+
this.getJacksonJsonPropertyDescriptionAnnotation(ctx, { schema, property }),
|
|
355
|
+
this.getJacksonJsonIncludeAnnotation(ctx, { schema, property }),
|
|
356
|
+
property.schema.deprecated ? kt.annotation(kt.refs.deprecated(), [kt.argument(kt.string(''))]) : null,
|
|
357
|
+
].filter(notNullish),
|
|
358
|
+
(x) => (x.target = 'get'),
|
|
359
|
+
),
|
|
360
|
+
type: this.getType(ctx, {
|
|
361
|
+
schema: property.schema,
|
|
362
|
+
nullable: schema.required.has(property.name) ? undefined : true,
|
|
363
|
+
}),
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
protected getAdditionalPropertiesProperty(
|
|
368
|
+
ctx: Context,
|
|
369
|
+
args: Args.GetAdditionalPropertiesProperty,
|
|
370
|
+
): kt.Property<Builder> {
|
|
371
|
+
const { schema } = args;
|
|
372
|
+
|
|
373
|
+
return kt.property(toCasing('additionalProperties', ctx.config.propertyNameCasing), {
|
|
374
|
+
annotations: [kt.annotation(kt.refs.jackson.jsonIgnore())],
|
|
375
|
+
type: kt.reference('MutableMap', null, {
|
|
376
|
+
generics: ['String', this.getAdditionalPropertiesType(ctx, { schema })],
|
|
377
|
+
}),
|
|
378
|
+
default: 'mutableMapOf()',
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
protected getAdditionalPropertiesSetter(
|
|
383
|
+
ctx: Context,
|
|
384
|
+
args: Args.GetAdditionalPropertiesSetter,
|
|
385
|
+
): kt.Function<Builder> {
|
|
386
|
+
const { schema } = args;
|
|
387
|
+
|
|
388
|
+
return kt.function(toCasing('set', ctx.config.functionNameCasing), {
|
|
389
|
+
annotations: [kt.annotation(kt.refs.jackson.jsonAnySetter())],
|
|
390
|
+
parameters: [
|
|
391
|
+
kt.parameter(toCasing('name', ctx.config.parameterNameCasing), 'String'),
|
|
392
|
+
kt.parameter(
|
|
393
|
+
toCasing('value', ctx.config.parameterNameCasing),
|
|
394
|
+
this.getAdditionalPropertiesType(ctx, { schema }),
|
|
395
|
+
),
|
|
396
|
+
],
|
|
397
|
+
body: `this.${toCasing('additionalProperties', ctx.config.propertyNameCasing)}[name] = value`,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
protected getAdditionalPropertiesGetter(
|
|
402
|
+
ctx: Context,
|
|
403
|
+
args: Args.GetAdditionalPropertiesGetter,
|
|
404
|
+
): kt.Function<Builder> {
|
|
405
|
+
const { schema } = args;
|
|
406
|
+
|
|
407
|
+
return kt.function(toCasing('getMap', ctx.config.functionNameCasing), {
|
|
408
|
+
annotations: [kt.annotation(kt.refs.jackson.jsonAnyGetter())],
|
|
409
|
+
returnType: kt.refs.map([kt.refs.string(), this.getAdditionalPropertiesType(ctx, { schema })]),
|
|
410
|
+
body: `return this.${toCasing('additionalProperties', ctx.config.propertyNameCasing)}`,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
// #endregion
|
|
414
|
+
|
|
415
|
+
// #region Annotations
|
|
416
|
+
protected getJacksonJsonTypeInfoAnnotation(
|
|
417
|
+
ctx: Context,
|
|
418
|
+
args: Args.GetJacksonJsonTypeInfoAnnotation,
|
|
419
|
+
): kt.Annotation<Builder> | null {
|
|
420
|
+
const { schema } = args;
|
|
421
|
+
|
|
422
|
+
return ctx.config.addJacksonAnnotations && schema.discriminator
|
|
423
|
+
? kt.annotation(kt.refs.jackson.jsonTypeInfo(), [
|
|
424
|
+
kt.argument.named('use', 'JsonTypeInfo.Id.NAME'),
|
|
425
|
+
kt.argument.named(
|
|
426
|
+
'include',
|
|
427
|
+
'properties' in schema && schema.properties.has(schema.discriminator.propertyName)
|
|
428
|
+
? 'JsonTypeInfo.As.EXISTING_PROPERTY'
|
|
429
|
+
: 'JsonTypeInfo.As.PROPERTY',
|
|
430
|
+
),
|
|
431
|
+
kt.argument.named('property', kt.string(schema.discriminator.propertyName)),
|
|
432
|
+
kt.argument.named('visible', 'true'),
|
|
433
|
+
])
|
|
434
|
+
: null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
protected getJacksonJsonSubTypesAnnotation(
|
|
438
|
+
ctx: Context,
|
|
439
|
+
args: Args.GetJacksonJsonSubTypesAnnotation,
|
|
440
|
+
): kt.Annotation<Builder> | null {
|
|
441
|
+
const { schema } = args;
|
|
442
|
+
|
|
443
|
+
if (!ctx.config.addJacksonAnnotations || !schema.discriminator) return null;
|
|
444
|
+
const entries = Object.entries(schema.discriminator.mapping);
|
|
445
|
+
return entries.length > 0
|
|
446
|
+
? kt.annotation(
|
|
447
|
+
kt.refs.jackson.jsonSubTypes(),
|
|
448
|
+
entries.map(([value, schema]) =>
|
|
449
|
+
kt.argument(
|
|
450
|
+
kt.call(
|
|
451
|
+
[kt.refs.jackson.jsonSubTypes(), 'Type'],
|
|
452
|
+
[
|
|
453
|
+
kt.argument.named(
|
|
454
|
+
'value',
|
|
455
|
+
modify(this.getType(ctx, { schema }), (x) => (x.classReference = true)),
|
|
456
|
+
),
|
|
457
|
+
kt.argument.named('name', kt.string(value)),
|
|
458
|
+
],
|
|
459
|
+
),
|
|
460
|
+
)
|
|
461
|
+
),
|
|
462
|
+
)
|
|
463
|
+
: null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
protected getJacksonJsonClassDescriptionAnnotation(
|
|
467
|
+
ctx: Context,
|
|
468
|
+
args: Args.GetJacksonJsonClassDescriptionAnnotation,
|
|
469
|
+
): kt.Annotation<Builder> | null {
|
|
470
|
+
const { schema } = args;
|
|
471
|
+
|
|
472
|
+
return ctx.config.addJacksonAnnotations && schema.description
|
|
473
|
+
? kt.annotation(kt.refs.jackson.jsonClassDescription(), [
|
|
474
|
+
kt.argument(kt.string(schema.description)),
|
|
475
|
+
])
|
|
476
|
+
: null;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
protected getJacksonJsonPropertyAnnotation(
|
|
480
|
+
ctx: Context,
|
|
481
|
+
args: Args.GetJacksonJsonPropertyAnnotation,
|
|
482
|
+
): kt.Annotation<Builder> | null {
|
|
483
|
+
const { schema, property } = args;
|
|
484
|
+
|
|
485
|
+
return ctx.config.addJacksonAnnotations
|
|
486
|
+
? kt.annotation(kt.refs.jackson.jsonProperty(), [
|
|
487
|
+
kt.argument(kt.string(property.name)),
|
|
488
|
+
schema.required.has(property.name) ? kt.argument.named('required', 'true') : null,
|
|
489
|
+
])
|
|
490
|
+
: null;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
protected getJacksonJsonPropertyDescriptionAnnotation(
|
|
494
|
+
ctx: Context,
|
|
495
|
+
args: Args.GetJacksonJsonPropertyDescriptionAnnotation,
|
|
496
|
+
): kt.Annotation<Builder> | null {
|
|
497
|
+
const { property } = args;
|
|
498
|
+
|
|
499
|
+
return ctx.config.addJacksonAnnotations && property.schema.description
|
|
500
|
+
? kt.annotation(kt.refs.jackson.jsonPropertyDescription(), [
|
|
501
|
+
kt.argument(kt.string(property.schema.description)),
|
|
502
|
+
])
|
|
503
|
+
: null;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
protected getJacksonJsonIncludeAnnotation(
|
|
507
|
+
ctx: Context,
|
|
508
|
+
args: Args.GetJacksonJsonIncludeAnnotation,
|
|
509
|
+
): kt.Annotation<Builder> | null {
|
|
510
|
+
const { property } = args;
|
|
511
|
+
|
|
512
|
+
return ctx.config.addJacksonAnnotations && property.schema.custom['exclude-when-null'] === true
|
|
513
|
+
? kt.annotation(kt.refs.jackson.jsonInclude(), [
|
|
514
|
+
kt.argument(kt.call([kt.refs.jackson.jsonInclude(), 'Include', 'NON_NULL'])),
|
|
515
|
+
])
|
|
516
|
+
: null;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
protected getJakartaValidationAnnotations(
|
|
520
|
+
ctx: Context,
|
|
521
|
+
args: Args.GetJakartaValidationAnnotations,
|
|
522
|
+
): kt.Annotation<Builder>[] {
|
|
523
|
+
const { property } = args;
|
|
524
|
+
const annotations: kt.Annotation<Builder>[] = [];
|
|
525
|
+
if (!ctx.config.addJakartaValidationAnnotations) {
|
|
526
|
+
return annotations;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (property.schema.kind === 'string') {
|
|
530
|
+
if (property.schema.pattern) {
|
|
531
|
+
annotations.push(
|
|
532
|
+
kt.annotation(kt.refs.jakarta.pattern(), [kt.argument.named('regexp', kt.string(property.schema.pattern))], {
|
|
533
|
+
target: 'get',
|
|
534
|
+
}),
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
if (property.schema.minLength === 1 && property.schema.maxLength === undefined) {
|
|
538
|
+
annotations.push(kt.annotation(kt.refs.jakarta.notEmpty(), [], { target: 'get' }));
|
|
539
|
+
} else if (property.schema.minLength !== undefined || property.schema.maxLength !== undefined) {
|
|
540
|
+
annotations.push(kt.annotation(kt.refs.jakarta.size(), [
|
|
541
|
+
property.schema.minLength !== undefined ? kt.argument.named('min', property.schema.minLength) : null,
|
|
542
|
+
property.schema.maxLength !== undefined ? kt.argument.named('max', property.schema.maxLength) : null,
|
|
543
|
+
], { target: 'get' }));
|
|
544
|
+
}
|
|
545
|
+
} else if (property.schema.kind === 'number' || property.schema.kind === 'integer') {
|
|
546
|
+
if (property.schema.minimum !== undefined) {
|
|
547
|
+
annotations.push(
|
|
548
|
+
kt.annotation(kt.refs.jakarta.min(), [kt.argument.named('value', property.schema.minimum)], {
|
|
549
|
+
target: 'get',
|
|
550
|
+
}),
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
if (property.schema.maximum !== undefined) {
|
|
554
|
+
annotations.push(
|
|
555
|
+
kt.annotation(kt.refs.jakarta.max(), [kt.argument.named('value', property.schema.maximum)], {
|
|
556
|
+
target: 'get',
|
|
557
|
+
}),
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
} else if (property.schema.kind === 'array') {
|
|
561
|
+
if (property.schema.minItems !== undefined || property.schema.maxItems !== undefined) {
|
|
562
|
+
annotations.push(kt.annotation(kt.refs.jakarta.size(), [
|
|
563
|
+
property.schema.minItems !== undefined ? kt.argument.named('min', property.schema.minItems) : null,
|
|
564
|
+
property.schema.maxItems !== undefined ? kt.argument.named('max', property.schema.maxItems) : null,
|
|
565
|
+
], { target: 'get' }));
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (this.shouldGenerateTypeDeclaration(ctx, { schema: property.schema })) {
|
|
570
|
+
annotations.push(kt.annotation(kt.refs.jakarta.valid(), [], { target: 'field' }));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return annotations;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
protected getSwaggerSchemaAnnotation(
|
|
577
|
+
ctx: Context,
|
|
578
|
+
args: Args.GetSwaggerSchemaAnnotation,
|
|
579
|
+
): kt.Annotation<Builder> | null {
|
|
580
|
+
const { schema, property } = args;
|
|
581
|
+
|
|
582
|
+
return ctx.config.addSwaggerAnnotations
|
|
583
|
+
? kt.annotation(kt.refs.swagger.schema(), [
|
|
584
|
+
property.schema.example !== undefined
|
|
585
|
+
? kt.argument.named('example', kt.string(String(property.schema.example)))
|
|
586
|
+
: null,
|
|
587
|
+
schema.required.has(property.name) ? kt.argument.named('required', 'true') : null,
|
|
588
|
+
property.schema.description !== undefined
|
|
589
|
+
? kt.argument.named('description', kt.string(property.schema.description))
|
|
590
|
+
: null,
|
|
591
|
+
property.schema.deprecated ? kt.argument.named('deprecated', kt.toNode(property.schema.deprecated)) : null,
|
|
592
|
+
])
|
|
593
|
+
: null;
|
|
594
|
+
}
|
|
595
|
+
// #endregion
|
|
596
|
+
|
|
597
|
+
protected getPackageName(ctx: Context, args: Args.GetPackageName): string {
|
|
598
|
+
const { schema } = args;
|
|
599
|
+
|
|
600
|
+
const packageSuffix = typeof ctx.config.packageSuffix === 'string'
|
|
601
|
+
? ctx.config.packageSuffix
|
|
602
|
+
: ctx.config.packageSuffix(schema);
|
|
603
|
+
return ctx.config.packageName + packageSuffix;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
protected shouldGenerateTypeDeclaration(ctx: Context, args: Args.ShouldGenerateTypeDeclaration): boolean {
|
|
607
|
+
let { schema } = args;
|
|
608
|
+
|
|
609
|
+
// All enum types should have its own type declaration
|
|
610
|
+
if (schema.enum !== undefined && schema.enum.length > 0) {
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// All primitive types already exist and do not need its own type declaration
|
|
615
|
+
if (
|
|
616
|
+
schema.kind !== 'combined' &&
|
|
617
|
+
schema.kind !== 'multi-type' &&
|
|
618
|
+
schema.kind !== 'object' &&
|
|
619
|
+
schema.kind !== 'oneOf'
|
|
620
|
+
) {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Too complex types cannot be represented in Kotlin, so they fallback to Any
|
|
625
|
+
if (schema.kind === 'multi-type') {
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
schema = this.normalizeSchema(ctx, { schema });
|
|
629
|
+
if (schema.kind === 'combined' || schema.kind === 'oneOf') {
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// A discriminated schema is the supertype its subtypes implement and the carrier of the
|
|
634
|
+
// `@JsonTypeInfo`/`@JsonSubTypes` pair, so it always needs a declaration of its own. The two rules below
|
|
635
|
+
// simplify a schema that nothing refers to as a type; degrading a polymorphic base to `Any` or to a `Map`
|
|
636
|
+
// instead drops the supertype from every subtype and the polymorphism with it. This matters for a base
|
|
637
|
+
// whose `discriminator.propertyName` is declared on the mapping targets rather than on the base, which is
|
|
638
|
+
// legal and leaves the base with no properties of its own.
|
|
639
|
+
const isDiscriminatedBase = schema.kind === 'object' && schema.discriminator !== undefined;
|
|
640
|
+
|
|
641
|
+
// Schemas representable by a simple Map type do not need its own type declaration
|
|
642
|
+
if (
|
|
643
|
+
!isDiscriminatedBase && schema.kind === 'object' && schema.properties.size === 0 && schema.additionalProperties
|
|
644
|
+
) {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (
|
|
649
|
+
!isDiscriminatedBase && schema.kind === 'object' &&
|
|
650
|
+
ctx.config.emptyObjectTypeBehavior === 'use-any' && schema.properties.size === 0
|
|
651
|
+
) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Dynamically generated schemas do not have its own type declaration
|
|
656
|
+
if (!ctx.data.schemas.some((x) => x.id === schema.id)) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// multipart schemas should not have its own type declaration
|
|
661
|
+
if (schema.$src.path.endsWith('/requestBody/content/multipart/form-data/schema')) {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
protected getDeclarationTypeName(ctx: Context, args: Args.GetDeclarationTypeName): string {
|
|
669
|
+
return toCasing(args.schema.name, ctx.config.typeNameCasing);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
protected getInheritedSchemas(
|
|
673
|
+
ctx: Context,
|
|
674
|
+
args: Args.GetInheritedSchemas,
|
|
675
|
+
): (ApiSchema<ApiSchemaKind> & { discriminator: NonNullable<ApiSchema['discriminator']> })[] {
|
|
676
|
+
return args.schema.inheritedSchemas
|
|
677
|
+
.filter((schema) => this.shouldGenerateTypeDeclaration(ctx, { schema }) && !schema.isNameGenerated)
|
|
678
|
+
.filter((item, index, self) => self.indexOf(item) === index);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
protected getClassProperties(ctx: Context, args: Args.GetClassProperties): ApiSchemaProperty[] {
|
|
682
|
+
const { schema } = args;
|
|
683
|
+
|
|
684
|
+
const inheritedSchemas = this.getInheritedSchemas(ctx, { schema });
|
|
685
|
+
const properties: ApiSchemaProperty[] = [];
|
|
686
|
+
const appendedProperties: ApiSchemaProperty[] = [];
|
|
687
|
+
for (const property of schema.properties.values()) {
|
|
688
|
+
const discriminator = inheritedSchemas.find(
|
|
689
|
+
(x) => x.discriminator?.propertyName === property.name,
|
|
690
|
+
)?.discriminator;
|
|
691
|
+
if (discriminator) {
|
|
692
|
+
const schemaMappings = Object.entries(discriminator.mapping).filter(([_, v]) => v.id === schema.id);
|
|
693
|
+
if (schemaMappings.length === 1) {
|
|
694
|
+
const p = createOverwriteProxy(property);
|
|
695
|
+
const s = createOverwriteProxy(p.schema);
|
|
696
|
+
p.schema = s;
|
|
697
|
+
s.default = schemaMappings[0][0];
|
|
698
|
+
appendedProperties.push(p);
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
properties.push(property);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
return [...this.sortProperties(ctx, { schema, properties }), ...appendedProperties];
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
protected sortProperties(_ctx: Context, args: Args.SortProperties): ApiSchemaProperty[] {
|
|
710
|
+
return [...args.properties].sort((a, b) => classify(a) - classify(b));
|
|
711
|
+
|
|
712
|
+
function classify(p: ApiSchemaProperty) {
|
|
713
|
+
if (p.schema.default !== undefined) return 1;
|
|
714
|
+
if (args.schema.required.has(p.name)) return 0;
|
|
715
|
+
return 2;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
protected normalizeSchema(ctx: Context, args: Args.NormalizeSchema): ApiSchema {
|
|
720
|
+
let { schema } = args;
|
|
721
|
+
|
|
722
|
+
schema = this.normalizeDiscriminatedBases(ctx, { schema });
|
|
723
|
+
if (schema.kind === 'oneOf') {
|
|
724
|
+
schema = ctx.config.oneOfBehavior === 'treat-as-any-of'
|
|
725
|
+
// deno-lint-ignore no-explicit-any
|
|
726
|
+
? { ...(schema as any), kind: 'combined', anyOf: schema.oneOf, allOf: [], oneOf: undefined }
|
|
727
|
+
// deno-lint-ignore no-explicit-any
|
|
728
|
+
: { ...(schema as any), kind: 'combined', allOf: schema.oneOf, anyOf: [], oneOf: undefined };
|
|
729
|
+
ctx.schema = schema;
|
|
730
|
+
}
|
|
731
|
+
if (schema.kind === 'object' || schema.kind === 'combined') {
|
|
732
|
+
const mergedSchema = resolveAnyOfAndAllOf(schema, true);
|
|
733
|
+
if (mergedSchema) {
|
|
734
|
+
schema = mergedSchema;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return schema;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Rewrites every discriminated `oneOf` base reachable from the schema through a composition into the
|
|
743
|
+
* `object` it describes, so that the merge in `normalizeSchema` treats it as one.
|
|
744
|
+
*
|
|
745
|
+
* A schema that declares a `oneOf` is of kind `oneOf` even when it also declares `type: object` and
|
|
746
|
+
* properties of its own — `determineSchemaKind` looks at `oneOf` first. For a *discriminated* `oneOf` that
|
|
747
|
+
* loses the very information the Kotlin declarations need, because the branches of such a `oneOf` are the
|
|
748
|
+
* schema's subtypes rather than parts of it (the same distinction `resolveAnyOfAndAllOf` draws for a `oneOf`
|
|
749
|
+
* nested inside a composition):
|
|
750
|
+
*
|
|
751
|
+
* - the base is an object in its own right, described by its own `properties`, `required` and any
|
|
752
|
+
* `allOf`/`anyOf` it composes — so its interface must declare those and nothing else, not the union of
|
|
753
|
+
* what its subtypes declare;
|
|
754
|
+
* - a subtype's `allOf: [Base]` is a composition with that object — so the subtype must inherit the base's
|
|
755
|
+
* own properties, including the discriminator, while inheriting nothing from its siblings.
|
|
756
|
+
*
|
|
757
|
+
* Both follow from turning the base into an `object` before the merge runs: the merge already collects the
|
|
758
|
+
* properties of an `object` branch and already declines to collect the branches of a discriminated `oneOf`.
|
|
759
|
+
* This is why `AllOfInheritanceDiscriminator` in test/specs/v3/discriminator-variants.yml has always been
|
|
760
|
+
* correct — with no `oneOf` keyword it is of kind `object` already — while every `oneOf`-holder base in the
|
|
761
|
+
* same document was not.
|
|
762
|
+
*/
|
|
763
|
+
protected normalizeDiscriminatedBases(_ctx: Context, args: Args.NormalizeDiscriminatedBases): ApiSchema {
|
|
764
|
+
// `normalizeSchema` runs once per property type via `shouldGenerateTypeDeclaration`, and almost no schema
|
|
765
|
+
// has a discriminated base anywhere below it. Answering that first costs one traversal and one set, and
|
|
766
|
+
// lets the overwhelmingly common case return the schema itself rather than a copy per composing schema.
|
|
767
|
+
//
|
|
768
|
+
// The question is asked once for the whole call rather than again per branch: a per-branch answer would
|
|
769
|
+
// have to be memoised to stay linear, and memoising it is unsound, because a walk truncated by the cycle
|
|
770
|
+
// guard can record `false` for a branch that reaches a base only by going back through an ancestor.
|
|
771
|
+
if (!containsDiscriminatedBase(args.schema, new Set())) return args.schema;
|
|
772
|
+
|
|
773
|
+
const rewritten = new Map<ApiSchema, ApiSchema>();
|
|
774
|
+
return rewrite(args.schema);
|
|
775
|
+
|
|
776
|
+
function rewrite(schema: ApiSchema): ApiSchema {
|
|
777
|
+
const existing = rewritten.get(schema);
|
|
778
|
+
if (existing) return existing;
|
|
779
|
+
|
|
780
|
+
const isBase = isDiscriminatedBase(schema);
|
|
781
|
+
const allOf = branches(schema, 'allOf');
|
|
782
|
+
const anyOf = branches(schema, 'anyOf');
|
|
783
|
+
// The branches of a discriminated `oneOf` are dropped rather than descended into: they are the schema's
|
|
784
|
+
// subtypes, and a subtype is generated from its own file where it is the root of this walk.
|
|
785
|
+
const oneOf = isBase ? [] : branches(schema, 'oneOf');
|
|
786
|
+
if (!isBase && allOf.length === 0 && anyOf.length === 0 && oneOf.length === 0) {
|
|
787
|
+
return schema;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Registered before descending, so a branch composing back to one of its own ancestors terminates.
|
|
791
|
+
// deno-lint-ignore no-explicit-any
|
|
792
|
+
const copy: any = { ...schema };
|
|
793
|
+
rewritten.set(schema, copy);
|
|
794
|
+
if (isBase) {
|
|
795
|
+
copy.kind = 'object';
|
|
796
|
+
copy.type = 'object';
|
|
797
|
+
}
|
|
798
|
+
copy.allOf = allOf.map(rewrite);
|
|
799
|
+
copy.anyOf = anyOf.map(rewrite);
|
|
800
|
+
copy.oneOf = oneOf.map(rewrite);
|
|
801
|
+
return copy as ApiSchema;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/** Whether the schema or anything it composes is a base this walk would rewrite. */
|
|
805
|
+
function containsDiscriminatedBase(schema: ApiSchema, visited: Set<ApiSchema>): boolean {
|
|
806
|
+
if (visited.has(schema)) return false;
|
|
807
|
+
visited.add(schema);
|
|
808
|
+
if (isDiscriminatedBase(schema)) return true;
|
|
809
|
+
|
|
810
|
+
const contains = (x: ApiSchema) => containsDiscriminatedBase(x, visited);
|
|
811
|
+
return branches(schema, 'allOf').some(contains) ||
|
|
812
|
+
branches(schema, 'anyOf').some(contains) ||
|
|
813
|
+
branches(schema, 'oneOf').some(contains);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function isDiscriminatedBase(schema: ApiSchema): boolean {
|
|
817
|
+
return schema.kind === 'oneOf' && schema.discriminator !== undefined;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* The branches a schema composes under one keyword. `normalizeSchema` leaves an explicit
|
|
822
|
+
* `oneOf: undefined` behind when it rewrites a `oneOf` holder, so a keyword can be present with no value.
|
|
823
|
+
*/
|
|
824
|
+
function branches(schema: ApiSchema, keyword: 'allOf' | 'anyOf' | 'oneOf'): ApiSchema[] {
|
|
825
|
+
const values = schema as unknown as Record<string, ApiSchema[] | undefined>;
|
|
826
|
+
return values[keyword] ?? [];
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
protected hasProperty(ctx: Context, args: Args.HasProperty): boolean {
|
|
831
|
+
// A composition branch may compose back to one of its own ancestors (see test/specs/v3/anyof-cycle.yml).
|
|
832
|
+
// Without this guard such a cycle recurses until the stack overflows. Re-visiting a schema can never
|
|
833
|
+
// change the answer, so returning `false` for one is safe: another path either already found the
|
|
834
|
+
// property or does not contain it at all.
|
|
835
|
+
const { schema, propertyName, visited = new Set<ApiSchema>() } = args;
|
|
836
|
+
if (visited.has(schema)) return false;
|
|
837
|
+
visited.add(schema);
|
|
838
|
+
|
|
839
|
+
return (
|
|
840
|
+
('properties' in schema && schema.properties.has(propertyName)) ||
|
|
841
|
+
('anyOf' in schema && schema.anyOf.some((schema) => this.hasProperty(ctx, { schema, propertyName, visited }))) ||
|
|
842
|
+
('allOf' in schema && schema.allOf.some((schema) => this.hasProperty(ctx, { schema, propertyName, visited }))) ||
|
|
843
|
+
// Only an undiscriminated `oneOf` composes into the schema that references it, matching which `oneOf`
|
|
844
|
+
// branches `resolveAnyOfAndAllOf` merges. A discriminated one lists subtypes, whose properties are not
|
|
845
|
+
// the referencing schema's, so an inherited property never comes from there.
|
|
846
|
+
('oneOf' in schema && !schema.discriminator &&
|
|
847
|
+
schema.oneOf.some((schema) => this.hasProperty(ctx, { schema, propertyName, visited })))
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
}
|