@goast/kotlin 0.5.8 → 0.5.9-beta.1

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