@goast/kotlin 0.6.0-beta.2 → 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.
Files changed (80) hide show
  1. package/package.json +2 -2
  2. package/src/mod.ts +8 -0
  3. package/src/src/assets.ts +9 -0
  4. package/src/src/ast/_index.ts +66 -0
  5. package/src/src/ast/common.ts +1 -0
  6. package/src/src/ast/index.ts +1 -0
  7. package/src/src/ast/node.ts +10 -0
  8. package/src/src/ast/nodes/annotation.ts +79 -0
  9. package/src/src/ast/nodes/argument.ts +62 -0
  10. package/src/src/ast/nodes/call.ts +75 -0
  11. package/src/src/ast/nodes/class.ts +178 -0
  12. package/src/src/ast/nodes/collection-literal.ts +49 -0
  13. package/src/src/ast/nodes/constructor.ts +126 -0
  14. package/src/src/ast/nodes/doc-tag.ts +138 -0
  15. package/src/src/ast/nodes/doc.ts +111 -0
  16. package/src/src/ast/nodes/enum-value.ts +100 -0
  17. package/src/src/ast/nodes/enum.ts +163 -0
  18. package/src/src/ast/nodes/function.ts +178 -0
  19. package/src/src/ast/nodes/generic-parameter.ts +54 -0
  20. package/src/src/ast/nodes/init-block.ts +38 -0
  21. package/src/src/ast/nodes/interface.ts +133 -0
  22. package/src/src/ast/nodes/lambda-type.ts +73 -0
  23. package/src/src/ast/nodes/lambda.ts +74 -0
  24. package/src/src/ast/nodes/object.ts +102 -0
  25. package/src/src/ast/nodes/parameter.ts +118 -0
  26. package/src/src/ast/nodes/property.ts +225 -0
  27. package/src/src/ast/nodes/reference.ts +178 -0
  28. package/src/src/ast/nodes/string.ts +114 -0
  29. package/src/src/ast/nodes/types.ts +23 -0
  30. package/src/src/ast/references/index.ts +10 -0
  31. package/src/src/ast/references/jackson.ts +70 -0
  32. package/src/src/ast/references/jakarta.ts +14 -0
  33. package/src/src/ast/references/java.ts +27 -0
  34. package/src/src/ast/references/kotlin.ts +41 -0
  35. package/src/src/ast/references/kotlinx.ts +14 -0
  36. package/src/src/ast/references/okhttp3.ts +5 -0
  37. package/src/src/ast/references/reactor.ts +5 -0
  38. package/src/src/ast/references/spring-reactive.ts +37 -0
  39. package/src/src/ast/references/spring.ts +86 -0
  40. package/src/src/ast/references/swagger.ts +23 -0
  41. package/src/src/ast/utils/get-kotlin-builder-options.ts +19 -0
  42. package/src/src/ast/utils/to-kt-node.ts +31 -0
  43. package/src/src/ast/utils/write-kt-annotations.ts +15 -0
  44. package/src/src/ast/utils/write-kt-arguments.ts +45 -0
  45. package/src/src/ast/utils/write-kt-enum-values.ts +27 -0
  46. package/src/src/ast/utils/write-kt-generic-parameters.ts +12 -0
  47. package/src/src/ast/utils/write-kt-members.ts +25 -0
  48. package/src/src/ast/utils/write-kt-node.ts +37 -0
  49. package/src/src/ast/utils/write-kt-parameters.ts +25 -0
  50. package/src/src/common-results.ts +4 -0
  51. package/src/src/config.ts +61 -0
  52. package/src/src/file-builder.ts +112 -0
  53. package/src/src/generators/file-generator.ts +71 -0
  54. package/src/src/generators/index.ts +5 -0
  55. package/src/src/generators/models/args.ts +142 -0
  56. package/src/src/generators/models/index.ts +4 -0
  57. package/src/src/generators/models/model-generator.ts +850 -0
  58. package/src/src/generators/models/models-generator.ts +65 -0
  59. package/src/src/generators/models/models.ts +95 -0
  60. package/src/src/generators/services/okhttp3-clients/args.ts +90 -0
  61. package/src/src/generators/services/okhttp3-clients/index.ts +4 -0
  62. package/src/src/generators/services/okhttp3-clients/models.ts +73 -0
  63. package/src/src/generators/services/okhttp3-clients/okhttp3-client-generator.ts +639 -0
  64. package/src/src/generators/services/okhttp3-clients/okhttp3-clients-generator.ts +190 -0
  65. package/src/src/generators/services/okhttp3-clients/refs.ts +59 -0
  66. package/src/src/generators/services/spring-controllers/args.ts +95 -0
  67. package/src/src/generators/services/spring-controllers/index.ts +4 -0
  68. package/src/src/generators/services/spring-controllers/models.ts +76 -0
  69. package/src/src/generators/services/spring-controllers/refs.ts +17 -0
  70. package/src/src/generators/services/spring-controllers/spring-controller-generator.ts +1129 -0
  71. package/src/src/generators/services/spring-controllers/spring-controllers-generator.ts +140 -0
  72. package/src/src/generators/services/spring-reactive-web-clients/args.ts +114 -0
  73. package/src/src/generators/services/spring-reactive-web-clients/index.ts +4 -0
  74. package/src/src/generators/services/spring-reactive-web-clients/models.ts +78 -0
  75. package/src/src/generators/services/spring-reactive-web-clients/refs.ts +11 -0
  76. package/src/src/generators/services/spring-reactive-web-clients/spring-reactive-web-client-generator.ts +657 -0
  77. package/src/src/generators/services/spring-reactive-web-clients/spring-reactive-web-clients-generator.ts +125 -0
  78. package/src/src/import-collection.ts +98 -0
  79. package/src/src/types.ts +3 -0
  80. package/src/src/utils.ts +55 -0
@@ -0,0 +1,1129 @@
1
+ import {
2
+ type ApiEndpoint,
3
+ type ApiParameter,
4
+ type AppendValueGroup,
5
+ appendValueGroup,
6
+ type BasicAppendValue,
7
+ builderTemplate as s,
8
+ createOverwriteProxy,
9
+ getSourceDisplayName,
10
+ type MaybePromise,
11
+ notNullish,
12
+ resolveAnyOfAndAllOf,
13
+ SourceBuilder,
14
+ toCasing,
15
+ } from '@goast/core';
16
+
17
+ import { getReasonPhrase } from 'http-status-codes';
18
+ import { kt } from '../../../ast/index.js';
19
+ import type { KotlinImport } from '../../../common-results.js';
20
+ import { KotlinFileBuilder } from '../../../file-builder.js';
21
+ import type { ApiParameterWithMultipartInfo } from '../../../types.js';
22
+ import { getArrayItemStringEnumSchema, getSourceDocLine, getStringEnumSchema, modifyString } from '../../../utils.js';
23
+ import { KotlinFileGenerator } from '../../file-generator.js';
24
+ import type { DefaultKotlinSpringControllerGeneratorArgs as Args } from './index.js';
25
+ import type { KotlinServiceGeneratorContext, KotlinServiceGeneratorOutput } from './models.js';
26
+
27
+ type Context = KotlinServiceGeneratorContext;
28
+ type Output = KotlinServiceGeneratorOutput;
29
+ type Builder = KotlinFileBuilder;
30
+
31
+ const strictResponseCustomProperty = 'strict-response';
32
+
33
+ export interface KotlinSpringControllerGenerator<
34
+ TOutput extends Output = Output,
35
+ > {
36
+ generate(ctx: Context): MaybePromise<TOutput>;
37
+ }
38
+
39
+ export class DefaultKotlinSpringControllerGenerator extends KotlinFileGenerator<Context, Output>
40
+ implements KotlinSpringControllerGenerator {
41
+ public generate(
42
+ ctx: KotlinServiceGeneratorContext,
43
+ ): MaybePromise<KotlinServiceGeneratorOutput> {
44
+ const packageName = this.getPackageName(ctx, {});
45
+ const dirPath = this.getDirectoryPath(ctx, { packageName });
46
+
47
+ console.log(`Generating service ${ctx.service.id} to ${dirPath}...`);
48
+ console.log(` Endpoints:`);
49
+ ctx.service.endpoints.forEach((endpoint) => {
50
+ console.log(
51
+ ` - ${getSourceDisplayName(ctx.data, endpoint)} [${toCasing(endpoint.name, ctx.config.functionNameCasing)}]`,
52
+ );
53
+ });
54
+ return {
55
+ apiInterface: this.generateApiInterfaceFile(ctx, {
56
+ dirPath,
57
+ packageName,
58
+ }),
59
+ apiController: this.generateApiControllerFile(ctx, {
60
+ dirPath,
61
+ packageName,
62
+ }),
63
+ apiDelegate: this.generateApiDelegateInterfaceFile(ctx, {
64
+ dirPath,
65
+ packageName,
66
+ }),
67
+ };
68
+ }
69
+
70
+ // #region API Interface
71
+ protected generateApiInterfaceFile(
72
+ ctx: Context,
73
+ args: Args.GenerateApiInterfaceFile,
74
+ ): KotlinImport {
75
+ const { dirPath, packageName } = args;
76
+ const typeName = this.getApiInterfaceName(ctx, {});
77
+ const fileName = `${typeName}.kt`;
78
+ const filePath = `${dirPath}/${fileName}`;
79
+ console.log(` Generating API interface ${typeName} to ${fileName}...`);
80
+
81
+ const builder = new KotlinFileBuilder(packageName, ctx.config);
82
+ builder.append(
83
+ this.getApiInterfaceFileContent(ctx, { interfaceName: typeName }),
84
+ );
85
+ builder.writeToFile(filePath);
86
+
87
+ return { typeName, packageName };
88
+ }
89
+
90
+ protected getApiInterfaceFileContent(
91
+ ctx: Context,
92
+ args: Args.GetApiInterfaceFileContent,
93
+ ): AppendValueGroup<Builder> {
94
+ const { interfaceName } = args;
95
+ return appendValueGroup<Builder>(
96
+ [this.getApiInterface(ctx, { interfaceName })],
97
+ '\n',
98
+ );
99
+ }
100
+
101
+ protected getApiInterface(
102
+ ctx: Context,
103
+ args: Args.GetApiInterface,
104
+ ): kt.Interface<Builder> {
105
+ const { interfaceName } = args;
106
+
107
+ return kt.interface(interfaceName, {
108
+ annotations: this.getApiInterfaceAnnotations(ctx),
109
+ members: this.getApiInterfaceMembers(ctx),
110
+ companionObject: kt.object({
111
+ members: ctx.service.endpoints.map((endpoint) =>
112
+ kt.property(this.getPathConstantName(ctx, { endpoint }), {
113
+ const: true,
114
+ default: kt.string(this.getEndpointPath(ctx, { endpoint })),
115
+ })
116
+ ),
117
+ }),
118
+ });
119
+ }
120
+
121
+ protected getApiInterfaceAnnotations(ctx: Context): kt.Annotation<Builder>[] {
122
+ const validated = kt.annotation(kt.refs.spring.validated());
123
+ const requestMapping = kt.annotation(kt.refs.spring.requestMapping(), [
124
+ kt.argument(this.getControllerRequestMapping(ctx, { prefix: 'api' })),
125
+ ]);
126
+ return [validated, requestMapping];
127
+ }
128
+
129
+ protected getApiInterfaceMembers(ctx: Context): kt.InterfaceMember<Builder>[] {
130
+ const members: kt.InterfaceMember<Builder>[] = [];
131
+ const delegateInterfaceName = this.getApiDelegateInterfaceName(ctx, {});
132
+
133
+ members.push(
134
+ kt.function('getDelegate', {
135
+ returnType: delegateInterfaceName,
136
+ singleExpression: true,
137
+ body: kt.object({
138
+ implements: [delegateInterfaceName],
139
+ }),
140
+ }),
141
+ kt.function('getExceptionHandler', {
142
+ returnType: ctx.refs.apiExceptionHandler({ nullable: true }),
143
+ singleExpression: true,
144
+ body: kt.toNode(null),
145
+ }),
146
+ );
147
+
148
+ ctx.service.endpoints.forEach((endpoint) => {
149
+ members.push(this.getApiInterfaceEndpointMethod(ctx, { endpoint }));
150
+ });
151
+
152
+ members.push(
153
+ ...ctx.service.endpoints
154
+ .filter((endpoint) => endpoint.custom[strictResponseCustomProperty] ?? ctx.config.strictResponseEntities)
155
+ .map((endpoint) => this.getApiResponseEntityClass(ctx, { endpoint })),
156
+ );
157
+
158
+ return members;
159
+ }
160
+
161
+ protected getApiInterfaceEndpointMethod(
162
+ ctx: Context,
163
+ args: Args.GetApiInterfaceEndpointMethod,
164
+ ): kt.Function<Builder> {
165
+ const { endpoint } = args;
166
+ const parameters = this.getAllParameters(ctx, { endpoint });
167
+
168
+ return kt.function(toCasing(endpoint.name, ctx.config.functionNameCasing), {
169
+ doc: kt.doc(this.getEndpointDocDescription(ctx, { endpoint })),
170
+ suspend: ctx.config.suspendingFunctions,
171
+ annotations: this.getApiInterfaceEndpointMethodAnnnotations(
172
+ ctx,
173
+ endpoint,
174
+ ),
175
+ parameters: parameters.map((parameter) => this.getApiInterfaceEndpointMethodParameter(ctx, endpoint, parameter)),
176
+ returnType: kt.refs.spring.responseEntity(['*']),
177
+ body: this.getApiInterfaceEndpointMethodBody(ctx, endpoint, parameters),
178
+ });
179
+ }
180
+
181
+ protected getApiInterfaceEndpointMethodAnnnotations(
182
+ ctx: Context,
183
+ endpoint: ApiEndpoint,
184
+ ): kt.Annotation<Builder>[] {
185
+ const annotations: kt.Annotation<Builder>[] = [];
186
+
187
+ if (ctx.config.addSwaggerAnnotations) {
188
+ annotations.push(
189
+ kt.annotation(kt.refs.swagger.operation(), [
190
+ endpoint.summary ? kt.argument.named('summary', kt.string(endpoint.summary?.trim())) : null,
191
+ kt.argument.named('operationId', kt.string(endpoint.name)),
192
+ endpoint.description
193
+ ? kt.argument.named(
194
+ 'description',
195
+ kt.string(endpoint.description?.trim()),
196
+ )
197
+ : null,
198
+ endpoint.deprecated !== undefined ? kt.argument.named('deprecated', kt.toNode(endpoint.deprecated)) : null,
199
+ ]),
200
+ );
201
+
202
+ if (endpoint.responses.length > 0) {
203
+ annotations.push(
204
+ kt.annotation(kt.refs.swagger.apiResponses(), [
205
+ kt.argument.named(
206
+ 'value',
207
+ kt.collectionLiteral(
208
+ endpoint.responses.map((response) =>
209
+ kt.call(kt.refs.swagger.apiResponse(), [
210
+ kt.argument.named(
211
+ 'responseCode',
212
+ // The spec's own response key, not `statusCode`: that field is `undefined` for
213
+ // `default` and every range code, and `kt.string(undefined)` renders the bare token
214
+ // `null`, which is not assignable to this non-nullable annotation element.
215
+ kt.string(response.statusKey),
216
+ ),
217
+ response.description
218
+ ? kt.argument.named(
219
+ 'description',
220
+ kt.string(response.description?.trim()),
221
+ )
222
+ : null,
223
+ kt.argument.named(
224
+ 'content',
225
+ kt.collectionLiteral(
226
+ (response.contentOptions.length === 0
227
+ ? [{ schema: undefined, type: undefined }]
228
+ : response.contentOptions).map(
229
+ (content) => {
230
+ let schemaType: kt.Type<Builder> = this.getSchemaType(ctx, {
231
+ schema: content.schema,
232
+ }) ?? kt.refs.any();
233
+ let isArray = false;
234
+
235
+ if (kt.refs.list.matches(schemaType)) {
236
+ isArray = true;
237
+ schemaType = schemaType.generics[0];
238
+ }
239
+
240
+ let ktSchema = kt.call(kt.refs.swagger.schema(), [
241
+ kt.argument.named(
242
+ 'implementation',
243
+ s<Builder>`${schemaType}::class`,
244
+ ),
245
+ ]);
246
+ if (isArray) {
247
+ ktSchema = kt.call(kt.refs.swagger.arraySchema(), [
248
+ kt.argument.named('schema', ktSchema),
249
+ ]);
250
+ }
251
+
252
+ return kt.call(kt.refs.swagger.content(), [
253
+ content.type
254
+ ? kt.argument.named(
255
+ 'mediaType',
256
+ kt.string(content.type),
257
+ )
258
+ : null,
259
+ content.schema
260
+ ? kt.argument.named(
261
+ isArray ? 'array' : 'schema',
262
+ ktSchema,
263
+ )
264
+ : null,
265
+ ]);
266
+ },
267
+ ),
268
+ ),
269
+ ),
270
+ ])
271
+ ),
272
+ ),
273
+ ),
274
+ ]),
275
+ );
276
+ }
277
+ }
278
+
279
+ const requestMapping = kt.annotation(kt.refs.spring.requestMapping(), [
280
+ kt.argument.named(
281
+ 'method',
282
+ kt.collectionLiteral([
283
+ kt.call([
284
+ kt.refs.spring.requestMethod(),
285
+ endpoint.method.toUpperCase(),
286
+ ]),
287
+ ]),
288
+ ),
289
+ kt.argument.named(
290
+ 'value',
291
+ kt.collectionLiteral([this.getPathConstantName(ctx, { endpoint })]),
292
+ ),
293
+ ]);
294
+ if (endpoint.requestBody && endpoint.requestBody.content.length > 0) {
295
+ requestMapping.arguments.push(
296
+ kt.argument.named(
297
+ 'consumes',
298
+ kt.collectionLiteral(
299
+ endpoint.requestBody?.content.map((x) => kt.string(x.type)),
300
+ ),
301
+ ),
302
+ );
303
+ }
304
+ annotations.push(requestMapping);
305
+
306
+ return annotations;
307
+ }
308
+
309
+ protected getApiInterfaceEndpointMethodParameter(
310
+ ctx: Context,
311
+ endpoint: ApiEndpoint,
312
+ parameter: ApiParameterWithMultipartInfo,
313
+ ): kt.Parameter<Builder> {
314
+ const enumItemSchema = parameter.multipart ? undefined : getArrayItemStringEnumSchema(parameter.schema);
315
+ const enumSchema = parameter.multipart ? undefined : getStringEnumSchema(parameter.schema);
316
+ const enumValueSchema = enumSchema ?? enumItemSchema;
317
+ const actualType = this.getSchemaType(ctx, { schema: parameter.schema });
318
+ const isEnumSchema = !!enumValueSchema && !!actualType &&
319
+ !!this.getSchemaType(ctx, { schema: enumValueSchema });
320
+ const schemaType = !isEnumSchema
321
+ ? actualType
322
+ : enumItemSchema
323
+ ? kt.refs.list([kt.refs.string()], { nullable: actualType?.nullable })
324
+ : kt.refs.string({ nullable: actualType?.nullable });
325
+ const result = kt.parameter(
326
+ toCasing(parameter.name, ctx.config.parameterNameCasing),
327
+ this.getParameterType(ctx, {
328
+ endpoint,
329
+ parameter,
330
+ type: isEnumSchema ? schemaType : undefined,
331
+ }),
332
+ {
333
+ default: parameter.multipart && parameter.schema?.default !== undefined
334
+ ? kt.toNode(parameter.schema?.default)
335
+ : null,
336
+ },
337
+ );
338
+
339
+ if (ctx.config.addSwaggerAnnotations) {
340
+ const annotation = kt.annotation(kt.refs.swagger.parameter(), [
341
+ parameter.multipart ? kt.argument.named('name', kt.string(parameter.multipart.name)) : null,
342
+ parameter.description
343
+ ? kt.argument.named(
344
+ 'description',
345
+ kt.string(parameter.description?.trim()),
346
+ )
347
+ : null,
348
+ kt.argument.named('required', parameter.required),
349
+ // Only emitted when set, matching how the model generator marks a deprecated property on `@Schema`.
350
+ // `@Operation` always states `deprecated`, but doing that for every parameter of every endpoint
351
+ // would add `deprecated = false` noise far beyond what the annotation is documenting.
352
+ parameter.deprecated ? kt.argument.named('deprecated', kt.toNode(true)) : null,
353
+ parameter.target === 'header' ? kt.argument.named('hidden', kt.toNode(true)) : null,
354
+ ]);
355
+
356
+ const schemaArgs: kt.Argument<SourceBuilder>[] = [];
357
+ if (parameter.schema?.default !== undefined) {
358
+ schemaArgs.push(
359
+ kt.argument.named(
360
+ 'defaultValue',
361
+ kt.string(String(parameter.schema?.default)),
362
+ ),
363
+ );
364
+ }
365
+ if (isEnumSchema) {
366
+ schemaArgs.push(
367
+ kt.argument.named(
368
+ 'allowableValues',
369
+ kt.collectionLiteral(
370
+ enumValueSchema?.enum?.map((x) => kt.string(x?.toString())),
371
+ ),
372
+ ),
373
+ );
374
+ }
375
+ if (schemaArgs.length) {
376
+ annotation.arguments.push(
377
+ kt.argument.named(
378
+ 'schema',
379
+ kt.call([kt.refs.swagger.schema()], schemaArgs),
380
+ ),
381
+ );
382
+ }
383
+ result.annotations.push(annotation);
384
+ }
385
+
386
+ const isCorePackage = !schemaType?.packageName ||
387
+ /^(kotlin|java)(\..*|$)/.test(schemaType.packageName);
388
+ if (!isCorePackage && ctx.config.addJakartaValidationAnnotations) {
389
+ result.annotations.push(kt.annotation(kt.refs.jakarta.valid()));
390
+ }
391
+
392
+ if (parameter.target === 'body' && !parameter.multipart) {
393
+ // A bare @RequestBody is required as far as Spring is concerned, so an optional body has to say so in the
394
+ // annotation too - the nullable parameter type alone still answers 400 when the body is missing.
395
+ result.annotations.push(
396
+ kt.annotation(
397
+ kt.refs.spring.requestBody(),
398
+ parameter.required ? [] : [kt.argument.named('required', parameter.required)],
399
+ ),
400
+ );
401
+ }
402
+
403
+ if (parameter.target === 'query') {
404
+ const annotation = kt.annotation(kt.refs.spring.requestParam(), [
405
+ kt.argument.named('value', kt.string(parameter.name)),
406
+ kt.argument.named('required', parameter.required),
407
+ ]);
408
+ if (parameter.schema?.default !== undefined) {
409
+ annotation.arguments.push(
410
+ kt.argument.named(
411
+ 'defaultValue',
412
+ kt.string(String(parameter.schema?.default)),
413
+ ),
414
+ );
415
+ }
416
+ result.annotations.push(annotation);
417
+ }
418
+
419
+ if (parameter.target === 'path') {
420
+ result.annotations.push(
421
+ kt.annotation(kt.refs.spring.pathVariable(), [
422
+ kt.string(parameter.name),
423
+ ]),
424
+ );
425
+ }
426
+
427
+ if (parameter.target === 'header') {
428
+ result.annotations.push(
429
+ kt.annotation(kt.refs.spring.requestHeader(), [
430
+ kt.string(parameter.name),
431
+ ]),
432
+ );
433
+ }
434
+
435
+ if (parameter.multipart) {
436
+ result.annotations.push(
437
+ kt.annotation(kt.refs.spring.requestPart(), [
438
+ kt.argument.named('value', kt.string(parameter.multipart.name)),
439
+ kt.argument.named('required', parameter.required),
440
+ ]),
441
+ );
442
+ }
443
+
444
+ return result;
445
+ }
446
+
447
+ protected getApiInterfaceEndpointMethodBody(
448
+ ctx: Context,
449
+ endpoint: ApiEndpoint,
450
+ parameters: ApiParameterWithMultipartInfo[],
451
+ ): AppendValueGroup<Builder> {
452
+ const body = appendValueGroup<Builder>([], '\n');
453
+
454
+ parameters.forEach((x) => {
455
+ const paramName = toCasing(x.name, ctx.config.parameterNameCasing);
456
+ if (x.multipart) return;
457
+
458
+ const enumItemSchema = getArrayItemStringEnumSchema(x.schema);
459
+ const enumValueSchema = getStringEnumSchema(x.schema) ?? enumItemSchema;
460
+ if (!enumValueSchema) return;
461
+
462
+ const schemaType = this.getSchemaType(ctx, { schema: x.schema });
463
+ const enumType = this.getSchemaType(ctx, { schema: enumValueSchema });
464
+ if (schemaType && enumType) {
465
+ body.values.push(
466
+ s`val ${paramName} = ${paramName}${schemaType.nullable || (!x.required && !x.schema?.default) ? '?' : ''}.${
467
+ enumItemSchema ? 'map' : 'let'
468
+ } { ${enumType}.fromValue(it) ?: return ${kt.refs.spring.responseEntity.infer()}.status(${kt.refs.spring.httpStatus()}.BAD_REQUEST).body(${
469
+ kt.string(
470
+ `Invalid value for parameter ${x.name}`,
471
+ )
472
+ }) }`,
473
+ );
474
+ }
475
+ });
476
+
477
+ body.values.push(
478
+ s`try {${s.indent`
479
+ return ${
480
+ kt.call(
481
+ [
482
+ kt.call(kt.reference('getDelegate'), []),
483
+ toCasing(endpoint.name, ctx.config.functionNameCasing),
484
+ ],
485
+ parameters.map((x) => toCasing(x.name, ctx.config.parameterNameCasing)),
486
+ )
487
+ }`}
488
+ } catch (e: ${kt.refs.throwable()}) {${s.indent`
489
+ return getExceptionHandler()?.handleApiException(e) ?: throw e`}
490
+ }`,
491
+ );
492
+
493
+ return body;
494
+ }
495
+
496
+ protected getApiResponseEntityClass(
497
+ ctx: Context,
498
+ args: Args.GetApiResponseEntityClass,
499
+ ): kt.Class<Builder> {
500
+ const { endpoint } = args;
501
+ const name = this.getApiResponseEntityName(ctx, { endpoint });
502
+ // Spring 7's `ResponseEntity<T>` carries an `Any` upper bound (JSpecify), so the subclass must bound `T`.
503
+ // The body parameter is then made nullable (`T?`) to keep null-body ("no response body") responses legal
504
+ // while staying wire-identical, since Spring's super constructor accepts a `@Nullable T body`.
505
+ const springBoot4 = ctx.config.springBootVersion === 4;
506
+ return kt.class(name, {
507
+ doc: kt.doc(`Response entity for ${endpoint.name}.`),
508
+ generics: [kt.genericParameter('T', springBoot4 ? { constraint: kt.refs.any() } : undefined)],
509
+ primaryConstructor: kt.constructor(
510
+ [
511
+ kt.parameter.class('body', kt.reference('T', null, springBoot4 ? { nullable: true } : undefined)),
512
+ kt.parameter.class('rawStatus', kt.refs.int()),
513
+ kt.parameter.class(
514
+ 'headers',
515
+ kt.refs.spring.multiValueMap([kt.refs.string(), kt.refs.string()], {
516
+ nullable: true,
517
+ }),
518
+ { default: kt.toNode(null) },
519
+ ),
520
+ ],
521
+ null,
522
+ {
523
+ accessModifier: endpoint.responses.length > 0 ? 'private' : null,
524
+ delegateTarget: 'super',
525
+ delegateArguments: [
526
+ kt.argument('body'),
527
+ kt.argument('headers'),
528
+ kt.argument('rawStatus'),
529
+ ],
530
+ },
531
+ ),
532
+ extends: kt.refs.spring.responseEntity([kt.reference('T')]),
533
+ companionObject: kt.object({
534
+ members: Array.from(
535
+ new Set(
536
+ [
537
+ ...ctx.config.defaultStatusCodes,
538
+ 501,
539
+ ...endpoint.responses.map((x) => x.statusCode),
540
+ ].filter(notNullish),
541
+ ),
542
+ ).map((code) => {
543
+ const fnName = toCasing(
544
+ getReasonPhrase(code),
545
+ ctx.config.functionNameCasing,
546
+ );
547
+ const response = endpoint.responses.find(
548
+ (x) => x.statusCode === code,
549
+ );
550
+ const responseType = response ? this.getResponseType(ctx, { endpoint, response }) : undefined;
551
+ const contentType = response?.contentOptions.find(
552
+ (x) => x.schema,
553
+ )?.type;
554
+
555
+ const hasResponseBody = responseType && !kt.refs.unit.matches(responseType);
556
+ return kt.function(fnName, {
557
+ parameters: [
558
+ hasResponseBody ? kt.parameter.class('body', responseType) : null,
559
+ kt.parameter.class(
560
+ 'headers',
561
+ kt.refs.spring.multiValueMap(
562
+ [kt.refs.string(), kt.refs.string()],
563
+ { nullable: true },
564
+ ),
565
+ { default: kt.toNode(null) },
566
+ ),
567
+ ],
568
+ singleExpression: true,
569
+ body: kt.call(
570
+ [
571
+ kt.reference(
572
+ this.getApiResponseEntityName(ctx, { endpoint }),
573
+ null,
574
+ {
575
+ generics: [
576
+ hasResponseBody
577
+ ? responseType
578
+ // `Unit?` violates the `T : Any` bound under Spring Boot 4; the `null` body argument
579
+ // below stays (now legal because the subclass body parameter is nullable).
580
+ : (springBoot4 ? kt.refs.unit() : kt.refs.unit({ nullable: true })),
581
+ ],
582
+ },
583
+ ),
584
+ ],
585
+ [
586
+ hasResponseBody ? kt.argument('body') : kt.toNode(null),
587
+ kt.toNode(code),
588
+ contentType
589
+ ? s`${kt.refs.spring.linkedMultiValueMap([kt.refs.string(), kt.refs.string()])}().also {
590
+ if (headers != null) {
591
+ it.putAll(headers)
592
+ }
593
+ it.addIfAbsent("Content-Type", ${kt.string(contentType)})
594
+ }`
595
+ : kt.argument('headers'),
596
+ ],
597
+ ),
598
+ });
599
+ }),
600
+ }),
601
+ });
602
+ }
603
+ // #endregion
604
+
605
+ // #region API Controller
606
+ protected generateApiControllerFile(
607
+ ctx: Context,
608
+ args: Args.GenerateApiControllerFile,
609
+ ): KotlinImport {
610
+ const { dirPath, packageName } = args;
611
+ const typeName = this.getApiControllerName(ctx, {});
612
+ const fileName = `${typeName}.kt`;
613
+ const filePath = `${dirPath}/${fileName}`;
614
+ console.log(` Generating API controller ${typeName} to ${fileName}...`);
615
+
616
+ const builder = new KotlinFileBuilder(packageName, ctx.config);
617
+ builder.append(
618
+ this.getApiControllerFileContent(ctx, { controllerName: typeName }),
619
+ );
620
+ builder.writeToFile(filePath);
621
+
622
+ return { typeName, packageName };
623
+ }
624
+
625
+ protected getApiControllerFileContent(
626
+ ctx: Context,
627
+ args: Args.GetApiControllerFileContent,
628
+ ): AppendValueGroup<Builder> {
629
+ const { controllerName } = args;
630
+
631
+ return appendValueGroup(
632
+ [this.getApiController(ctx, { controllerName })],
633
+ '\n',
634
+ );
635
+ }
636
+
637
+ protected getApiController(
638
+ ctx: Context,
639
+ args: Args.GetApiController,
640
+ ): kt.Class<Builder> {
641
+ const { controllerName } = args;
642
+
643
+ return kt.class(controllerName, {
644
+ annotations: this.getApiControllerAnnotations(ctx),
645
+ primaryConstructor: kt.constructor([
646
+ kt.parameter.class(
647
+ 'delegate',
648
+ kt.reference(this.getApiDelegateInterfaceName(ctx, {}), null, {
649
+ nullable: true,
650
+ }),
651
+ {
652
+ annotations: [
653
+ kt.annotation(kt.refs.spring.autowired(), [
654
+ kt.argument.named('required', 'false'),
655
+ ]),
656
+ ],
657
+ },
658
+ ),
659
+ kt.parameter.class(
660
+ 'exceptionHandler',
661
+ ctx.refs.apiExceptionHandler({ nullable: true }),
662
+ {
663
+ annotations: [
664
+ kt.annotation(kt.refs.spring.autowired(), [
665
+ kt.argument.named('required', 'false'),
666
+ ]),
667
+ ],
668
+ accessModifier: 'private',
669
+ property: 'readonly',
670
+ },
671
+ ),
672
+ ]),
673
+ implements: [this.getApiInterfaceName(ctx, {})],
674
+ members: this.getApiControllerMembers(ctx),
675
+ });
676
+ }
677
+
678
+ protected getApiControllerAnnotations(ctx: Context): kt.Annotation<Builder>[] {
679
+ const annotations: kt.Annotation<Builder>[] = [];
680
+ if (ctx.config.addJakartaValidationAnnotations) {
681
+ annotations.push(
682
+ kt.annotation(kt.refs.jakarta.generated(), [
683
+ kt.argument.named(
684
+ 'value',
685
+ kt.collectionLiteral([
686
+ kt.string('com.goast.kotlin.spring-service-generator'),
687
+ ]),
688
+ ),
689
+ ]),
690
+ );
691
+ }
692
+ annotations.push(kt.annotation(kt.refs.spring.controller()));
693
+ annotations.push(
694
+ kt.annotation(kt.refs.spring.requestMapping(), [
695
+ this.getControllerRequestMapping(ctx, {}),
696
+ ]),
697
+ );
698
+ return annotations;
699
+ }
700
+
701
+ protected getApiControllerMembers(ctx: Context): kt.ClassMember<Builder>[] {
702
+ const delegateInterfaceName = this.getApiDelegateInterfaceName(ctx, {});
703
+
704
+ const delegateProp = kt.property<Builder>('delegate', {
705
+ accessModifier: 'private',
706
+ default: s`delegate ?: ${kt.object({ implements: [delegateInterfaceName] })}`,
707
+ });
708
+
709
+ const getDelegateFun = kt.function<Builder>('getDelegate', {
710
+ override: true,
711
+ returnType: delegateInterfaceName,
712
+ singleExpression: true,
713
+ body: kt.reference('delegate'),
714
+ });
715
+
716
+ const getExceptionHandlerFun = kt.function<Builder>('getExceptionHandler', {
717
+ override: true,
718
+ returnType: ctx.refs.apiExceptionHandler({ nullable: true }),
719
+ singleExpression: true,
720
+ body: kt.reference('exceptionHandler'),
721
+ });
722
+
723
+ return [delegateProp, getDelegateFun, getExceptionHandlerFun];
724
+ }
725
+ // #endregion
726
+
727
+ // #region API Delegate Interface
728
+ protected generateApiDelegateInterfaceFile(
729
+ ctx: Context,
730
+ args: Args.GenerateApiDelegateInterfaceFile,
731
+ ): KotlinImport {
732
+ const { dirPath, packageName } = args;
733
+ const typeName = this.getApiDelegateInterfaceName(ctx, {});
734
+ const fileName = `${typeName}.kt`;
735
+ const filePath = `${dirPath}/${fileName}`;
736
+ console.log(` Generating API delegate ${typeName} to ${fileName}...`);
737
+
738
+ const builder = new KotlinFileBuilder(packageName, ctx.config);
739
+ builder.append(
740
+ this.getApiDelegateInterfaceFileContent(ctx, {
741
+ delegateInterfaceName: typeName,
742
+ }),
743
+ );
744
+ builder.writeToFile(filePath);
745
+
746
+ return { typeName, packageName };
747
+ }
748
+
749
+ protected getApiDelegateInterfaceFileContent(
750
+ ctx: Context,
751
+ args: Args.GetApiDelegateInterfaceFileContent,
752
+ ): AppendValueGroup<Builder> {
753
+ const { delegateInterfaceName } = args;
754
+
755
+ return appendValueGroup(
756
+ [this.getApiDelegateInterface(ctx, { delegateInterfaceName })],
757
+ '\n',
758
+ );
759
+ }
760
+
761
+ protected getApiDelegateInterface(
762
+ ctx: Context,
763
+ args: Args.GetApiDelegateInterface,
764
+ ): kt.Interface<Builder> {
765
+ const { delegateInterfaceName } = args;
766
+
767
+ return kt.interface(delegateInterfaceName, {
768
+ annotations: this.getApiDelegateInterfaceAnnotations(ctx),
769
+ members: this.getApiDelegateInterfaceMembers(ctx),
770
+ });
771
+ }
772
+
773
+ protected getApiDelegateInterfaceAnnotations(
774
+ ctx: Context,
775
+ ): kt.Annotation<Builder>[] {
776
+ const annotations: kt.Annotation<Builder>[] = [];
777
+ if (ctx.config.addJakartaValidationAnnotations) {
778
+ annotations.push(
779
+ kt.annotation(kt.refs.jakarta.generated(), [
780
+ kt.argument.named(
781
+ 'value',
782
+ kt.collectionLiteral([
783
+ kt.string('com.goast.kotlin.spring-service-generator'),
784
+ ]),
785
+ ),
786
+ ]),
787
+ );
788
+ }
789
+ return annotations;
790
+ }
791
+
792
+ protected getApiDelegateInterfaceMembers(
793
+ ctx: Context,
794
+ ): kt.InterfaceMember<Builder>[] {
795
+ const members: kt.InterfaceMember<Builder>[] = [];
796
+
797
+ members.push(
798
+ kt.function('getRequest', {
799
+ returnType: kt.refs.java.optional([kt.refs.spring.nativeWebRequest()]),
800
+ singleExpression: true,
801
+ body: kt.call([kt.refs.java.optional.infer(), 'empty'], []),
802
+ }),
803
+ );
804
+
805
+ ctx.service.endpoints.forEach((endpoint) => {
806
+ members.push(
807
+ this.getApiDelegateInterfaceEndpointMethod(ctx, { endpoint }),
808
+ );
809
+ });
810
+
811
+ return members;
812
+ }
813
+
814
+ protected getApiDelegateInterfaceEndpointMethod(
815
+ ctx: Context,
816
+ args: Args.GetApiDelegateInterfaceEndpointMethod,
817
+ ): kt.Function<Builder> {
818
+ const { endpoint } = args;
819
+ const parameters = this.getAllParameters(ctx, { endpoint });
820
+
821
+ const fn = kt.function<Builder>(
822
+ toCasing(endpoint.name, ctx.config.functionNameCasing),
823
+ {
824
+ doc: kt.doc(this.getEndpointDocDescription(ctx, { endpoint })),
825
+ suspend: ctx.config.suspendingFunctions,
826
+ parameters: parameters.map((parameter) => {
827
+ return kt.parameter(
828
+ toCasing(parameter.name, ctx.config.parameterNameCasing),
829
+ this.getParameterType(ctx, { endpoint, parameter }),
830
+ );
831
+ }),
832
+ },
833
+ );
834
+
835
+ if (endpoint.custom[strictResponseCustomProperty] ?? ctx.config.strictResponseEntities) {
836
+ const responseEntity = kt.reference.genericFactory(
837
+ this.getApiResponseEntityName(ctx, { endpoint }),
838
+ `${this.getPackageName(ctx, {})}.${this.getApiInterfaceName(ctx, {})}`,
839
+ );
840
+ fn.returnType = responseEntity(['*']);
841
+ fn.body = appendValueGroup(
842
+ [
843
+ s`return ${
844
+ kt.call(
845
+ [
846
+ responseEntity.infer(),
847
+ toCasing(getReasonPhrase(501), ctx.config.functionNameCasing),
848
+ ],
849
+ [],
850
+ )
851
+ }`,
852
+ ],
853
+ '\n',
854
+ );
855
+ } else {
856
+ fn.returnType = kt.refs.spring.responseEntity([
857
+ this.getResponseType(ctx, { endpoint }),
858
+ ]);
859
+ fn.body = appendValueGroup(
860
+ [
861
+ s`return ${kt.refs.spring.responseEntity.infer()}(${kt.refs.spring.httpStatus()}.NOT_IMPLEMENTED)`,
862
+ ],
863
+ '\n',
864
+ );
865
+ }
866
+
867
+ return fn;
868
+ }
869
+ // #endregion
870
+
871
+ protected getParameterType(
872
+ ctx: Context,
873
+ args: Args.GetParameterType,
874
+ ): kt.Type<Builder> {
875
+ const { parameter } = args;
876
+ if (parameter.multipart?.isFile) {
877
+ return kt.refs.spring.filePart();
878
+ }
879
+ const type = this.getTypeUsage(ctx, {
880
+ schema: parameter.schema,
881
+ nullable: (!parameter.required && parameter.schema?.default === undefined) ||
882
+ undefined,
883
+ type: args.type,
884
+ });
885
+ return parameter.target === 'body' ? adjustListType(ctx, type) : type;
886
+ }
887
+
888
+ protected getResponseType(
889
+ ctx: Context,
890
+ args: Args.GetResponseType,
891
+ ): kt.Type<Builder> {
892
+ const { endpoint, response } = args;
893
+ const responseSchemas = (response ? [response] : endpoint.responses)
894
+ .flatMap((x) => x.contentOptions.flatMap((x) => x.schema))
895
+ .filter(notNullish)
896
+ .filter(
897
+ (x, i, a) =>
898
+ a.findIndex((y) => {
899
+ const xType = this.getSchemaType(ctx, { schema: x });
900
+ const yType = this.getSchemaType(ctx, { schema: y });
901
+ return (
902
+ xType?.name === yType?.name &&
903
+ xType?.packageName === yType?.packageName
904
+ );
905
+ }) === i,
906
+ );
907
+
908
+ if (responseSchemas.length === 1) {
909
+ return adjustListType(
910
+ ctx,
911
+ this.getTypeUsage(ctx, {
912
+ schema: responseSchemas[0],
913
+ fallback: kt.refs.unit(),
914
+ }),
915
+ );
916
+ } else if (responseSchemas.length === 0) {
917
+ return kt.refs.unit();
918
+ } else {
919
+ // Under Spring Boot 4, this type flows into `ResponseEntity<T : Any>` (e.g. the delegate default methods),
920
+ // so the ambiguous/unknown-body fallback must be non-null `Any` rather than `Any?`.
921
+ return ctx.config.springBootVersion === 4 ? kt.refs.any() : kt.refs.any({ nullable: true });
922
+ }
923
+ }
924
+
925
+ protected getTypeUsage(
926
+ ctx: Context,
927
+ args: Args.GetTypeUsage<Builder>,
928
+ ): kt.Type<Builder> {
929
+ const { schema, nullable, fallback } = args;
930
+ const type = args.type ?? this.getSchemaType(ctx, { schema });
931
+ return type
932
+ ? createOverwriteProxy(type, { nullable: nullable ?? type.nullable })
933
+ : (fallback ?? kt.refs.any({ nullable }));
934
+ }
935
+
936
+ protected getSchemaType(
937
+ ctx: Context,
938
+ args: Args.GetSchemaType,
939
+ ): kt.Reference<SourceBuilder> | undefined {
940
+ const { schema } = args;
941
+ return schema && ctx.input.kotlin.models[schema.id].type;
942
+ }
943
+
944
+ protected getEndpointDocDescription(ctx: Context, args: Args.GetEndpointDocDescription): BasicAppendValue<Builder> {
945
+ const docSegments = [args.endpoint.description?.trim()];
946
+
947
+ if (ctx.config.includeSourceInDocs) {
948
+ docSegments.push(getSourceDocLine(args.endpoint));
949
+ }
950
+
951
+ return docSegments.filter(notNullish).join('\n\n');
952
+ }
953
+
954
+ protected getControllerRequestMapping(
955
+ ctx: Context,
956
+ args: Args.GetControllerRequestMapping,
957
+ ): kt.String<Builder> {
958
+ let { prefix } = args;
959
+ const basePath = this.getBasePath(ctx, {});
960
+ prefix ??= `openapi.${toCasing(ctx.service.name, ctx.config.propertyNameCasing)}`;
961
+ return kt.string(`\${${prefix}.base-path:${basePath}}`);
962
+ }
963
+
964
+ protected getBasePath(ctx: Context, _args: Args.GetBasePath): string {
965
+ return modifyString(
966
+ (ctx.service.$src ?? ctx.service.endpoints[0]?.$src)?.document
967
+ .servers?.[0]?.url ?? '/',
968
+ ctx.config.basePath,
969
+ ctx.service,
970
+ );
971
+ }
972
+
973
+ protected getEndpointPath(ctx: Context, args: Args.GetEndpointPath): string {
974
+ const { endpoint } = args;
975
+ return modifyString(endpoint.path, ctx.config.pathModifier, endpoint);
976
+ }
977
+
978
+ protected getDirectoryPath(
979
+ ctx: Context,
980
+ args: Args.GetDirectoryPath,
981
+ ): string {
982
+ const { packageName } = args;
983
+ return `${ctx.config.outputDir}/${packageName.replace(/\./g, '/')}`;
984
+ }
985
+
986
+ protected getPathConstantName(
987
+ ctx: Context,
988
+ args: Args.GetPathConstantName,
989
+ ): string {
990
+ const { endpoint } = args;
991
+ return toCasing(`${endpoint.name}_path`, ctx.config.constantNameCasing);
992
+ }
993
+
994
+ protected getPackageName(ctx: Context, _args: Args.GetPackageName): string {
995
+ const packageSuffix = typeof ctx.config.packageSuffix === 'string'
996
+ ? ctx.config.packageSuffix
997
+ : ctx.config.packageSuffix(ctx.service);
998
+ return ctx.config.packageName + packageSuffix;
999
+ }
1000
+
1001
+ protected getApiResponseEntityName(
1002
+ ctx: Context,
1003
+ args: Args.GetApiResponseEntityName,
1004
+ ): string {
1005
+ const { endpoint } = args;
1006
+ return toCasing(
1007
+ `${endpoint.name}_ResponseEntity`,
1008
+ ctx.config.typeNameCasing,
1009
+ );
1010
+ }
1011
+
1012
+ protected getApiInterfaceName(
1013
+ ctx: Context,
1014
+ _args: Args.GetApiInterfaceName,
1015
+ ): string {
1016
+ return toCasing(ctx.service.name + '_Api', ctx.config.typeNameCasing);
1017
+ }
1018
+
1019
+ protected getApiControllerName(
1020
+ ctx: Context,
1021
+ _args: Args.GetApiControllerName,
1022
+ ): string {
1023
+ return toCasing(
1024
+ ctx.service.name + '_ApiController',
1025
+ ctx.config.typeNameCasing,
1026
+ );
1027
+ }
1028
+
1029
+ protected getApiDelegateInterfaceName(
1030
+ ctx: Context,
1031
+ _args: Args.GetApiDelegateInterfaceName,
1032
+ ): string {
1033
+ return toCasing(
1034
+ ctx.service.name + '_ApiDelegate',
1035
+ ctx.config.typeNameCasing,
1036
+ );
1037
+ }
1038
+
1039
+ protected getAllParameters(
1040
+ ctx: Context,
1041
+ args: Args.GetAllParameters,
1042
+ ): ApiParameterWithMultipartInfo[] {
1043
+ const { endpoint } = args;
1044
+ const parameters = endpoint.parameters.filter(
1045
+ (parameter) =>
1046
+ parameter.target === 'query' ||
1047
+ parameter.target === 'path' ||
1048
+ parameter.target === 'header',
1049
+ );
1050
+ if (endpoint.requestBody) {
1051
+ const content = endpoint.requestBody.content[0];
1052
+ let schema = content.schema;
1053
+
1054
+ if (content.type === 'multipart/form-data') {
1055
+ if (schema && schema.kind === 'object') {
1056
+ schema = resolveAnyOfAndAllOf(schema, true) ?? schema;
1057
+ const properties = schema.properties ?? {};
1058
+ for (const [name, property] of properties.entries()) {
1059
+ parameters.push(
1060
+ Object.assign(
1061
+ this.createApiParameter({
1062
+ id: `multipart-${name}`,
1063
+ name,
1064
+ target: 'body',
1065
+ schema: property.schema,
1066
+ required: schema.required.has(name),
1067
+ description: property.schema.description,
1068
+ }),
1069
+ {
1070
+ multipart: {
1071
+ name,
1072
+ isFile: property.schema.kind === 'string' &&
1073
+ property.schema.format === 'binary',
1074
+ },
1075
+ },
1076
+ ),
1077
+ );
1078
+ }
1079
+ }
1080
+ } else {
1081
+ const schemaType = this.getSchemaType(ctx, { schema });
1082
+ const name = !schemaType || /^Any\??$/.test(schemaType.name)
1083
+ ? 'body'
1084
+ : SourceBuilder.build((b) => kt.reference.write(b, schemaType));
1085
+ parameters.push(
1086
+ this.createApiParameter({
1087
+ id: 'body',
1088
+ name,
1089
+ target: 'body',
1090
+ schema,
1091
+ required: endpoint.requestBody.required,
1092
+ description: endpoint.requestBody.description,
1093
+ }),
1094
+ );
1095
+ }
1096
+ }
1097
+
1098
+ return parameters;
1099
+ }
1100
+
1101
+ private createApiParameter(
1102
+ data: Partial<ApiParameter> & Pick<ApiParameter, 'id' | 'name' | 'target'>,
1103
+ ): ApiParameter {
1104
+ return {
1105
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1106
+ $src: undefined!,
1107
+ $ref: undefined,
1108
+ schema: undefined,
1109
+ required: false,
1110
+ description: undefined,
1111
+ allowEmptyValue: undefined,
1112
+ allowReserved: undefined,
1113
+ deprecated: false,
1114
+ explode: undefined,
1115
+ style: undefined,
1116
+ ...data,
1117
+ };
1118
+ }
1119
+ }
1120
+
1121
+ export function adjustListType<T>(ctx: Context, type: T): T {
1122
+ if (!kt.refs.list.matches(type)) return type;
1123
+ switch (ctx.config.arrayType) {
1124
+ case 'flux':
1125
+ return kt.refs.reactor.flux([type.generics[0]]) as T;
1126
+ default:
1127
+ return type;
1128
+ }
1129
+ }