@goast/kotlin 0.5.9-beta.2 → 0.5.10

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