@goast/kotlin 0.0.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 (51) hide show
  1. package/README.md +7 -0
  2. package/cjs/index.js +9 -0
  3. package/cjs/lib/common-results.js +2 -0
  4. package/cjs/lib/config.js +9 -0
  5. package/cjs/lib/file-builder.js +63 -0
  6. package/cjs/lib/generators/file-generator.js +17 -0
  7. package/cjs/lib/generators/index.js +6 -0
  8. package/cjs/lib/generators/models/index.js +6 -0
  9. package/cjs/lib/generators/models/model-generator.js +337 -0
  10. package/cjs/lib/generators/models/models-generator.js +28 -0
  11. package/cjs/lib/generators/models/models.js +5 -0
  12. package/cjs/lib/generators/services/spring-controllers/index.js +6 -0
  13. package/cjs/lib/generators/services/spring-controllers/models.js +5 -0
  14. package/cjs/lib/generators/services/spring-controllers/spring-controller-generator.js +416 -0
  15. package/cjs/lib/generators/services/spring-controllers/spring-controllers-generator.js +30 -0
  16. package/cjs/lib/import-collection.js +70 -0
  17. package/cjs/lib/utils.js +22 -0
  18. package/cjs/package.json +1 -0
  19. package/esm/index.js +6 -0
  20. package/esm/lib/common-results.js +1 -0
  21. package/esm/lib/config.js +6 -0
  22. package/esm/lib/file-builder.js +59 -0
  23. package/esm/lib/generators/file-generator.js +13 -0
  24. package/esm/lib/generators/index.js +3 -0
  25. package/esm/lib/generators/models/index.js +3 -0
  26. package/esm/lib/generators/models/model-generator.js +333 -0
  27. package/esm/lib/generators/models/models-generator.js +24 -0
  28. package/esm/lib/generators/models/models.js +2 -0
  29. package/esm/lib/generators/services/spring-controllers/index.js +3 -0
  30. package/esm/lib/generators/services/spring-controllers/models.js +2 -0
  31. package/esm/lib/generators/services/spring-controllers/spring-controller-generator.js +412 -0
  32. package/esm/lib/generators/services/spring-controllers/spring-controllers-generator.js +26 -0
  33. package/esm/lib/import-collection.js +66 -0
  34. package/esm/lib/utils.js +17 -0
  35. package/package.json +29 -0
  36. package/types/index.d.ts +6 -0
  37. package/types/lib/common-results.d.ts +4 -0
  38. package/types/lib/config.d.ts +6 -0
  39. package/types/lib/file-builder.d.ts +14 -0
  40. package/types/lib/generators/file-generator.d.ts +8 -0
  41. package/types/lib/generators/index.d.ts +3 -0
  42. package/types/lib/generators/models/index.d.ts +3 -0
  43. package/types/lib/generators/models/model-generator.d.ts +31 -0
  44. package/types/lib/generators/models/models-generator.d.ts +17 -0
  45. package/types/lib/generators/models/models.d.ts +22 -0
  46. package/types/lib/generators/services/spring-controllers/index.d.ts +3 -0
  47. package/types/lib/generators/services/spring-controllers/models.d.ts +24 -0
  48. package/types/lib/generators/services/spring-controllers/spring-controller-generator.d.ts +63 -0
  49. package/types/lib/generators/services/spring-controllers/spring-controllers-generator.d.ts +17 -0
  50. package/types/lib/import-collection.d.ts +13 -0
  51. package/types/lib/utils.d.ts +3 -0
@@ -0,0 +1,333 @@
1
+ import { dirname } from 'path';
2
+ import { ensureDirSync, writeFileSync } from 'fs-extra';
3
+ import { resolveAnyOfAndAllOf, toCasing } from '@goast/core';
4
+ import { KotlinFileBuilder } from '../../file-builder';
5
+ import { KotlinFileGenerator } from '../file-generator';
6
+ export class DefaultKotlinModelGenerator extends KotlinFileGenerator {
7
+ generate(ctx) {
8
+ if (/\/(anyOf|allOf)(\/[0-9]+)?$/.test(ctx.schema.$src.path)) {
9
+ // Do not generate types that are only used for anyOf and/or allOf
10
+ return { typeName: 'Any?', packageName: undefined, additionalImports: [] };
11
+ }
12
+ if (ctx.schema.id === ctx.schema.name) {
13
+ // TODO: Add this to @goast/core
14
+ const match = ctx.schema.$src.path.match(/\/components\/responses\/([^/]+)\/content\/.+\/schema/);
15
+ if (match) {
16
+ ctx.schema.name = match[1].toLowerCase().endsWith('response') ? match[1] : match[1] + 'Response';
17
+ }
18
+ }
19
+ if (ctx.schema.isNameGenerated) {
20
+ // TODO: Change this in @goast/core
21
+ const match = ctx.schema.$src.path.match(/\/paths\/(?<path>.+)\/(?<method>.+)\/responses\/(?<status>\d+)\//);
22
+ if (match && match.groups) {
23
+ const { path, method, status } = match.groups;
24
+ const endpoint = ctx.data.endpoints.find((e) => e.path === path && e.method === method);
25
+ if (endpoint) {
26
+ ctx.schema.name = `${endpoint.name}${status}Response`;
27
+ }
28
+ }
29
+ }
30
+ if (this.shouldGenerateTypeDeclaration(ctx, ctx.schema)) {
31
+ const typeName = this.getDeclarationTypeName(ctx);
32
+ const packageName = ctx.config.packageName + ctx.config.packageSuffix;
33
+ const filePath = `${ctx.config.outputDir}/${packageName.replace(/\./g, '/')}/${typeName}.kt`;
34
+ console.log(`Generating model ${packageName}.${typeName} to ${filePath}...`);
35
+ ensureDirSync(dirname(filePath));
36
+ const builder = new KotlinFileBuilder(packageName, ctx.config);
37
+ this.generateFileContent(ctx, builder);
38
+ writeFileSync(filePath, builder.toString());
39
+ return { typeName, packageName, additionalImports: [] };
40
+ }
41
+ else {
42
+ const builder = new KotlinFileBuilder(undefined, ctx.config);
43
+ this.generateType(ctx, builder, ctx.schema);
44
+ const additionalImports = builder.imports.imports;
45
+ builder.imports.clear();
46
+ return { typeName: builder.toString(false), packageName: undefined, additionalImports };
47
+ }
48
+ }
49
+ generateFileContent(ctx, builder) {
50
+ let schema = ctx.schema;
51
+ if (schema.kind === 'object' || schema.kind === 'combined') {
52
+ const mergedSchema = resolveAnyOfAndAllOf(schema, true);
53
+ if (mergedSchema) {
54
+ schema = mergedSchema;
55
+ }
56
+ }
57
+ if (schema.kind === 'object') {
58
+ this.generateObjectType(ctx, builder, schema);
59
+ }
60
+ else if (schema.enum !== undefined && schema.enum.length > 0) {
61
+ this.generateEnum(ctx, builder, schema);
62
+ }
63
+ else {
64
+ this.generateType(ctx, builder, schema);
65
+ }
66
+ }
67
+ generateObjectType(ctx, builder, schema) {
68
+ if (schema.properties.size === 0) {
69
+ if (schema.additionalProperties) {
70
+ this.generateMapType(ctx, builder, schema);
71
+ }
72
+ else {
73
+ builder.append('Any');
74
+ }
75
+ }
76
+ else {
77
+ this.generateDataClass(ctx, builder, schema);
78
+ }
79
+ }
80
+ generateDataClass(ctx, builder, schema) {
81
+ builder
82
+ .apply((builder) => this.generateDocumentation(ctx, builder, schema))
83
+ .append('data class ')
84
+ .append(this.getDeclarationTypeName(ctx))
85
+ .parenthesizeIf(schema.properties.size > 0, '()', (builder) => builder
86
+ .appendLine()
87
+ .forEachSeparated(this.sortProperties(ctx, schema, schema.properties.values()), ',\n', (builder, property) => builder
88
+ .ensurePreviousLineEmpty()
89
+ .apply((builder) => this.generatePropertyAnnotations(ctx, builder, schema, property))
90
+ .append(`val ${toCasing(property.name, 'camel')}: `)
91
+ .apply((builder) => this.generateType(ctx, builder, property.schema))
92
+ .applyIf(!schema.required.has(property.name), (builder) => builder.appendIf(!property.schema.nullable, '?').append(' = null')))
93
+ .appendLineIf(schema.properties.size > 0))
94
+ .parenthesizeIf(schema.additionalProperties !== undefined && schema.additionalProperties !== false, '{}', (builder) => builder
95
+ .appendLine()
96
+ .applyIf(schema.additionalProperties !== undefined && schema.additionalProperties !== false, (builder) => builder
97
+ .appendLine('@JsonIgnore')
98
+ .addImport('JsonIgnore', 'com.fasterxml.jackson.annotation')
99
+ .append('val additionalProperties: Mutable')
100
+ .apply((builder) => this.generateMapType(ctx, builder, schema))
101
+ .appendLine(' = mutableMapOf()')
102
+ .appendLine()
103
+ .appendLine('@JsonAnySetter')
104
+ .addImport('JsonAnySetter', 'com.fasterxml.jackson.annotation')
105
+ .append('fun set')
106
+ .parenthesize('()', (builder) => builder.append('name: String, value: ').applyIfElse(schema.additionalProperties === true, (builder) => builder.append('Any?'), (builder) => this.generateType(ctx, builder, schema.additionalProperties)))
107
+ .append(' ')
108
+ .parenthesize('{}', (builder) => builder.appendLine().appendLine('this.additionalProperties[name] = value'))
109
+ .appendLine()
110
+ .appendLine()
111
+ .appendLine('@JsonAnyGetter')
112
+ .addImport('JsonAnyGetter', 'com.fasterxml.jackson.annotation')
113
+ .append('fun getMap(): ')
114
+ .apply((builder) => this.generateMapType(ctx, builder, schema))
115
+ .append(' ')
116
+ .parenthesize('{}', (builder) => builder.appendLine().appendLine('return this.additionalProperties'))
117
+ .appendLine()));
118
+ }
119
+ generatePropertyAnnotations(ctx, builder, schema, property) {
120
+ this.generatePropertyValidationAnnotations(ctx, builder, schema, property);
121
+ this.generatePropertySchemaAnnotation(ctx, builder, schema, property);
122
+ this.generateJsonPropertyAnnotation(ctx, builder, schema, property);
123
+ }
124
+ generatePropertyValidationAnnotations(ctx, builder, schema, property) {
125
+ if (property.schema.kind === 'string' && property.schema.pattern) {
126
+ builder
127
+ .append('@get:Pattern(regexp = ')
128
+ .append(this.toStringLiteral(ctx, property.schema.pattern))
129
+ .append(')')
130
+ .addImport('Pattern', 'jakarta.validation.constraints')
131
+ .appendLine();
132
+ }
133
+ if (this.shouldGenerateTypeDeclaration(ctx, property.schema)) {
134
+ builder.append('@field:Valid').addImport('Valid', 'jakarta.validation').appendLine();
135
+ }
136
+ }
137
+ generatePropertySchemaAnnotation(ctx, builder, schema, property) {
138
+ const parts = new Map();
139
+ if (property.schema.example !== undefined) {
140
+ parts.set('example', this.toStringLiteral(ctx, String(property.schema.example)));
141
+ }
142
+ if (schema.required.has(property.name)) {
143
+ parts.set('required', 'true');
144
+ }
145
+ if (property.schema.description !== undefined) {
146
+ parts.set('description', this.toStringLiteral(ctx, property.schema.description));
147
+ }
148
+ builder
149
+ .append('@Schema')
150
+ .addImport('Schema', 'io.swagger.v3.oas.annotations.media')
151
+ .parenthesizeIf(parts.size > 0, '()', (builder) => builder.forEachSeparated(parts.entries(), ', ', (builder, [key, value]) => builder.append(`${key} = ${value}`)))
152
+ .appendLine();
153
+ }
154
+ generateJsonPropertyAnnotation(ctx, builder, schema, property) {
155
+ builder
156
+ .append('@JsonProperty')
157
+ .addImport('JsonProperty', 'com.fasterxml.jackson.annotation')
158
+ .parenthesize('()', (builder) => builder
159
+ .append(this.toStringLiteral(ctx, property.name))
160
+ .appendIf(schema.required.has(property.name), ', required = true'))
161
+ .appendLine()
162
+ .applyIf(property.schema.custom['exclude-when-null'] === true, (builder) => builder
163
+ .append('@get:JsonInclude')
164
+ .addImport('JsonInclude', 'com.fasterxml.jackson.annotation')
165
+ .parenthesize('()', (builder) => builder.append('JsonInclude.Include.NON_NULL'))
166
+ .appendLine());
167
+ }
168
+ generateMapType(ctx, builder, schema) {
169
+ if (schema.additionalProperties === true) {
170
+ builder.append('Map<String, Any?>');
171
+ }
172
+ else if (typeof schema.additionalProperties === 'object') {
173
+ const propertiesType = schema.additionalProperties;
174
+ builder
175
+ .append('Map')
176
+ .parenthesize('<>', (builder) => builder.append('String, ').apply((builder) => this.generateType(ctx, builder, propertiesType)));
177
+ }
178
+ }
179
+ generateType(ctx, builder, schema) {
180
+ if (this.shouldGenerateTypeDeclaration(ctx, schema)) {
181
+ if (schema === ctx.schema) {
182
+ builder.append('Any?');
183
+ return;
184
+ }
185
+ const schemaResult = ctx.getSchemaResult(schema);
186
+ builder.append(schemaResult.typeName);
187
+ if (schemaResult.packageName) {
188
+ builder.addImport(schemaResult.typeName, schemaResult.packageName);
189
+ }
190
+ return;
191
+ }
192
+ switch (schema.kind) {
193
+ case 'boolean':
194
+ builder.append('Boolean');
195
+ break;
196
+ case 'integer':
197
+ case 'number':
198
+ this.generateNumberType(ctx, builder, schema);
199
+ break;
200
+ case 'string':
201
+ if (schema.enum !== undefined && schema.enum.length > 0) {
202
+ this.generateEnum(ctx, builder, schema);
203
+ }
204
+ else {
205
+ this.generateStringType(ctx, builder, schema);
206
+ }
207
+ break;
208
+ case 'null':
209
+ builder.append('null');
210
+ break;
211
+ case 'unknown':
212
+ builder.append('Any');
213
+ break;
214
+ case 'array':
215
+ this.generateArrayType(ctx, builder, schema);
216
+ break;
217
+ case 'object':
218
+ this.generateObjectType(ctx, builder, schema);
219
+ break;
220
+ case 'combined':
221
+ builder.append('Any');
222
+ break;
223
+ case 'multi-type':
224
+ builder.append('Any');
225
+ break;
226
+ case 'oneOf':
227
+ builder.append('Any');
228
+ break;
229
+ default:
230
+ builder.append('Any');
231
+ break;
232
+ }
233
+ if (schema.nullable) {
234
+ builder.append('?');
235
+ }
236
+ }
237
+ generateNumberType(ctx, builder, schema) {
238
+ switch (schema.format) {
239
+ case 'int32':
240
+ builder.append('Int');
241
+ break;
242
+ case 'int64':
243
+ builder.append('Long');
244
+ break;
245
+ case 'float':
246
+ builder.append('Float');
247
+ break;
248
+ case 'double':
249
+ builder.append('Double');
250
+ break;
251
+ default:
252
+ builder.append(schema.kind === 'integer' ? 'Int' : 'Double');
253
+ break;
254
+ }
255
+ }
256
+ generateStringType(ctx, builder, schema) {
257
+ switch (schema.format) {
258
+ case 'date-time':
259
+ builder.append('OffsetDateTime').addImport('OffsetDateTime', 'java.time');
260
+ break;
261
+ default:
262
+ builder.append('String');
263
+ break;
264
+ }
265
+ }
266
+ generateEnum(ctx, builder, schema) {
267
+ builder
268
+ .apply((builder) => this.generateDocumentation(ctx, builder, schema))
269
+ .append('enum class ')
270
+ .append(this.getDeclarationTypeName(ctx))
271
+ .append('(val value: String) ')
272
+ .parenthesize('{}', (builder) => {
273
+ var _a;
274
+ return builder
275
+ .appendLine()
276
+ .forEachSeparated((_a = schema.enum) !== null && _a !== void 0 ? _a : [], (builder) => builder.appendLine(',').appendLine(), (builder, value) => builder
277
+ .append('@JsonProperty')
278
+ .addImport('JsonProperty', 'com.fasterxml.jackson.annotation')
279
+ .parenthesize('()', (builder) => builder.append(this.toStringLiteral(ctx, String(value))))
280
+ .appendLine()
281
+ .append(toCasing(String(value), 'snake'))
282
+ .parenthesize('()', (builder) => builder.append(this.toStringLiteral(ctx, String(value)))))
283
+ .appendLine();
284
+ });
285
+ }
286
+ generateArrayType(ctx, builder, schema) {
287
+ builder.append('List').parenthesize('<>', (builder) => builder.applyIfElse(schema.items === undefined, (builder) => builder.append('Any?'), (builder) => this.generateType(ctx, builder, schema.items)));
288
+ }
289
+ generateDocumentation(ctx, builder, schema) {
290
+ var _a, _b;
291
+ const propertiesWithDescription = Array.from((_b = (_a = schema.properties) === null || _a === void 0 ? void 0 : _a.values()) !== null && _b !== void 0 ? _b : []).filter((p) => p.schema.description !== undefined);
292
+ if (schema.description !== undefined || propertiesWithDescription.length > 0) {
293
+ builder
294
+ .ensurePreviousLineEmpty()
295
+ .appendLine('/**')
296
+ .applyWithLinePrefix(' * ', (builder) => builder
297
+ .appendLineIf(!!schema.description, schema.description)
298
+ .forEach(propertiesWithDescription, (builder, property) => { var _a; return builder.appendLine(`@param ${toCasing(property.name, 'camel')} ${(_a = property.schema.description) === null || _a === void 0 ? void 0 : _a.trim()}`); }))
299
+ .appendLine(' */');
300
+ }
301
+ }
302
+ shouldGenerateTypeDeclaration(ctx, schema) {
303
+ // All enum types should have its own type declaration
304
+ if (schema.enum !== undefined && schema.enum.length > 0) {
305
+ return true;
306
+ }
307
+ // All primitive types already exist and do not need its own type declaration
308
+ if (schema.kind !== 'combined' &&
309
+ schema.kind !== 'multi-type' &&
310
+ schema.kind !== 'object' &&
311
+ schema.kind !== 'oneOf') {
312
+ return false;
313
+ }
314
+ // Only object types with properties should have its own type declaration
315
+ if (schema.kind === 'object' &&
316
+ schema.properties.size === 0 &&
317
+ schema.anyOf.length === 0 &&
318
+ schema.allOf.length === 0) {
319
+ return false;
320
+ }
321
+ return true;
322
+ }
323
+ getDeclarationTypeName(ctx) {
324
+ return toCasing(ctx.schema.name, 'pascal');
325
+ }
326
+ sortProperties(ctx, schema, properties) {
327
+ return [...properties].sort((a, b) => {
328
+ const aRequired = schema.required.has(a.name) ? 1 : 0;
329
+ const bRequired = schema.required.has(b.name) ? 1 : 0;
330
+ return bRequired - aRequired;
331
+ });
332
+ }
333
+ }
@@ -0,0 +1,24 @@
1
+ import { Factory, OpenApiSchemasGenerationProviderBase } from '@goast/core';
2
+ import { DefaultKotlinModelGenerator } from './model-generator';
3
+ import { defaultKotlinModelsGeneratorConfig, } from './models';
4
+ export class KotlinModelsGenerator extends OpenApiSchemasGenerationProviderBase {
5
+ constructor(modelGeneratorFactory) {
6
+ super();
7
+ this._modelGeneratorFactory = modelGeneratorFactory !== null && modelGeneratorFactory !== void 0 ? modelGeneratorFactory : Factory.fromValue(new DefaultKotlinModelGenerator());
8
+ }
9
+ initResult() {
10
+ return {
11
+ models: {},
12
+ };
13
+ }
14
+ buildContext(context, config) {
15
+ return this.getProviderContext(context, config, defaultKotlinModelsGeneratorConfig);
16
+ }
17
+ generateSchema(ctx, schema) {
18
+ const modelGenerator = this._modelGeneratorFactory.create();
19
+ return modelGenerator.generate(Object.assign(Object.assign({}, ctx), { schema, getSchemaResult: (schema) => this.getSchemaResult(ctx, schema) }));
20
+ }
21
+ addSchemaResult(ctx, schema, result) {
22
+ ctx.output.models[schema.id] = result;
23
+ }
24
+ }
@@ -0,0 +1,2 @@
1
+ import { defaultKotlinGeneratorConfig } from '../../config';
2
+ export const defaultKotlinModelsGeneratorConfig = Object.assign(Object.assign({}, defaultKotlinGeneratorConfig), { packageName: 'com.openapi.generated', packageSuffix: '.model' });
@@ -0,0 +1,3 @@
1
+ export * from './models';
2
+ export * from './spring-controller-generator';
3
+ export * from './spring-controllers-generator';
@@ -0,0 +1,2 @@
1
+ import { defaultKotlinGeneratorConfig } from '../../../config';
2
+ export const defaultKotlinServicesGeneratorConfig = Object.assign(Object.assign({}, defaultKotlinGeneratorConfig), { packageName: 'com.openapi.generated', packageSuffix: '.api' });