@arrirpc/codegen-kotlin 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +11 -0
- package/dist/index.cjs +1932 -0
- package/dist/index.d.cts +40 -0
- package/dist/index.d.mts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.mjs +1924 -0
- package/package.json +29 -0
package/dist/index.cjs
ADDED
@@ -0,0 +1,1932 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const fs = require('node:fs');
|
4
|
+
const codegenUtils = require('@arrirpc/codegen-utils');
|
5
|
+
|
6
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
7
|
+
|
8
|
+
const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
9
|
+
|
10
|
+
const reservedIdentifierKeywords = [
|
11
|
+
"as",
|
12
|
+
"as?",
|
13
|
+
"break",
|
14
|
+
"class",
|
15
|
+
"continue",
|
16
|
+
"do",
|
17
|
+
"else",
|
18
|
+
"false",
|
19
|
+
"for",
|
20
|
+
"fun",
|
21
|
+
"if",
|
22
|
+
"in",
|
23
|
+
"!in",
|
24
|
+
"interface",
|
25
|
+
"is",
|
26
|
+
"!is",
|
27
|
+
"null",
|
28
|
+
"object",
|
29
|
+
"package",
|
30
|
+
"return",
|
31
|
+
"super",
|
32
|
+
"this",
|
33
|
+
"throw",
|
34
|
+
"true",
|
35
|
+
"try",
|
36
|
+
"typealias",
|
37
|
+
"typeof",
|
38
|
+
"val",
|
39
|
+
"var",
|
40
|
+
"when",
|
41
|
+
"while"
|
42
|
+
];
|
43
|
+
const illegalCharacters = "+-*/%=&|!<>[]?:.@$;";
|
44
|
+
function kotlinIdentifier(input) {
|
45
|
+
const name = codegenUtils.removeDisallowedChars(codegenUtils.camelCase(input), illegalCharacters);
|
46
|
+
if (codegenUtils.stringStartsWithNumber(name) || reservedIdentifierKeywords.includes(name)) {
|
47
|
+
return `\`${name}\``;
|
48
|
+
}
|
49
|
+
return name;
|
50
|
+
}
|
51
|
+
function kotlinClassName(input) {
|
52
|
+
const name = codegenUtils.removeDisallowedChars(
|
53
|
+
codegenUtils.pascalCase(input, { normalize: true }),
|
54
|
+
illegalCharacters
|
55
|
+
);
|
56
|
+
if (codegenUtils.stringStartsWithNumber(name) || reservedIdentifierKeywords.includes(name)) {
|
57
|
+
return `_${name}`;
|
58
|
+
}
|
59
|
+
return name;
|
60
|
+
}
|
61
|
+
function getClassName(schema, context) {
|
62
|
+
if (schema.metadata?.id) {
|
63
|
+
const className2 = kotlinClassName(
|
64
|
+
codegenUtils.pascalCase(schema.metadata.id, {
|
65
|
+
normalize: true
|
66
|
+
})
|
67
|
+
);
|
68
|
+
return `${context.modelPrefix}${className2}`;
|
69
|
+
}
|
70
|
+
const depth = instanceDepth(context);
|
71
|
+
if (depth === 1 && !context.discriminatorKey) {
|
72
|
+
const className2 = kotlinClassName(
|
73
|
+
codegenUtils.pascalCase(context.instancePath.replace("/", ""), {
|
74
|
+
normalize: true
|
75
|
+
})
|
76
|
+
);
|
77
|
+
return `${context.modelPrefix}${className2}`;
|
78
|
+
}
|
79
|
+
if (context.discriminatorParentId && context.discriminatorKey && context.discriminatorValue) {
|
80
|
+
const className2 = kotlinClassName(
|
81
|
+
codegenUtils.pascalCase(
|
82
|
+
`${context.discriminatorParentId}_${context.discriminatorValue}`,
|
83
|
+
{ normalize: true }
|
84
|
+
)
|
85
|
+
);
|
86
|
+
return `${context.modelPrefix}${className2}`;
|
87
|
+
}
|
88
|
+
const className = kotlinClassName(
|
89
|
+
codegenUtils.pascalCase(
|
90
|
+
context.instancePath.split("/").join("_").split("[").join("_").split("]").join("_"),
|
91
|
+
{
|
92
|
+
normalize: true
|
93
|
+
}
|
94
|
+
)
|
95
|
+
);
|
96
|
+
return `${context.modelPrefix}${className}`;
|
97
|
+
}
|
98
|
+
function instanceDepth(context) {
|
99
|
+
const parts = context.instancePath.split("/");
|
100
|
+
return parts.length - 1;
|
101
|
+
}
|
102
|
+
function isNullable(schema, context) {
|
103
|
+
return schema.nullable === true || context.isOptional === true;
|
104
|
+
}
|
105
|
+
|
106
|
+
function kotlinAnyFromSchema(schema, context) {
|
107
|
+
const nullable = isNullable(schema, context);
|
108
|
+
const defaultValue = nullable ? "null" : "JsonNull";
|
109
|
+
return {
|
110
|
+
typeName: "JsonElement",
|
111
|
+
isNullable: nullable,
|
112
|
+
defaultValue,
|
113
|
+
fromJson(input) {
|
114
|
+
if (context.isOptional) {
|
115
|
+
return `when (${input}) {
|
116
|
+
null -> null
|
117
|
+
else -> ${input}
|
118
|
+
}`;
|
119
|
+
}
|
120
|
+
if (schema.nullable) {
|
121
|
+
return `when (${input}) {
|
122
|
+
JsonNull -> null
|
123
|
+
null -> null
|
124
|
+
else -> ${input}
|
125
|
+
}`;
|
126
|
+
}
|
127
|
+
return `when (${input}) {
|
128
|
+
is JsonElement -> ${input}!!
|
129
|
+
else -> JsonNull
|
130
|
+
}`;
|
131
|
+
},
|
132
|
+
toJson(input, target) {
|
133
|
+
if (schema.nullable) {
|
134
|
+
return `${target} += when (${input}) {
|
135
|
+
null -> "null"
|
136
|
+
else -> JsonInstance.encodeToString(${input})
|
137
|
+
}`;
|
138
|
+
}
|
139
|
+
return `${target} += JsonInstance.encodeToString(${input})`;
|
140
|
+
},
|
141
|
+
toQueryString() {
|
142
|
+
return `__logError("[WARNING] any's cannot be serialized to query params. Skipping field at ${context.instancePath}.")`;
|
143
|
+
},
|
144
|
+
content: ""
|
145
|
+
};
|
146
|
+
}
|
147
|
+
|
148
|
+
function kotlinArrayFromSchema(schema, context) {
|
149
|
+
const nullable = isNullable(schema, context);
|
150
|
+
const defaultValue = nullable ? "null" : "mutableListOf()";
|
151
|
+
const subType = kotlinTypeFromSchema(schema.elements, {
|
152
|
+
modelPrefix: context.modelPrefix,
|
153
|
+
clientName: context.clientName,
|
154
|
+
clientVersion: context.clientVersion,
|
155
|
+
instancePath: `${context.instancePath}/[element]`,
|
156
|
+
schemaPath: `${context.schemaPath}/elements`,
|
157
|
+
existingTypeIds: context.existingTypeIds
|
158
|
+
});
|
159
|
+
const typeName = `MutableList<${subType.typeName}${subType.isNullable ? "?" : ""}>`;
|
160
|
+
return {
|
161
|
+
typeName,
|
162
|
+
isNullable: nullable,
|
163
|
+
defaultValue,
|
164
|
+
fromJson(input) {
|
165
|
+
if (nullable) {
|
166
|
+
return `when (${input}) {
|
167
|
+
is JsonArray -> {
|
168
|
+
val __value: ${typeName} = mutableListOf()
|
169
|
+
for (__element in ${input}!!.jsonArray) {
|
170
|
+
__value.add(
|
171
|
+
${subType.fromJson("__element")}
|
172
|
+
)
|
173
|
+
}
|
174
|
+
__value
|
175
|
+
}
|
176
|
+
|
177
|
+
else -> null
|
178
|
+
}`;
|
179
|
+
}
|
180
|
+
return `when (${input}) {
|
181
|
+
is JsonArray -> {
|
182
|
+
val __value: ${typeName} = mutableListOf()
|
183
|
+
for (__element in ${input}!!.jsonArray) {
|
184
|
+
__value.add(
|
185
|
+
${subType.fromJson("__element")}
|
186
|
+
)
|
187
|
+
}
|
188
|
+
__value
|
189
|
+
}
|
190
|
+
|
191
|
+
else -> mutableListOf()
|
192
|
+
}`;
|
193
|
+
},
|
194
|
+
toJson(input, target) {
|
195
|
+
if (schema.nullable) {
|
196
|
+
return `if (${input} == null) {
|
197
|
+
${target} += "null"
|
198
|
+
} else {
|
199
|
+
${target} += "["
|
200
|
+
for ((__index, __element) in ${input}.withIndex()) {
|
201
|
+
if (__index != 0) {
|
202
|
+
${target} += ","
|
203
|
+
}
|
204
|
+
${subType.toJson("__element", target)}
|
205
|
+
}
|
206
|
+
${target} += "]"
|
207
|
+
}`;
|
208
|
+
}
|
209
|
+
return `${target} += "["
|
210
|
+
for ((__index, __element) in ${input}.withIndex()) {
|
211
|
+
if (__index != 0) {
|
212
|
+
${target} += ","
|
213
|
+
}
|
214
|
+
${subType.toJson("__element", target)}
|
215
|
+
}
|
216
|
+
${target} += "]"`;
|
217
|
+
},
|
218
|
+
toQueryString() {
|
219
|
+
return `__logError("[WARNING] arrays cannot be serialized to query params. Skipping field at ${context.instancePath}.")`;
|
220
|
+
},
|
221
|
+
content: subType.content
|
222
|
+
};
|
223
|
+
}
|
224
|
+
|
225
|
+
function kotlinObjectFromSchema(schema, context) {
|
226
|
+
const className = getClassName(schema, context);
|
227
|
+
const nullable = isNullable(schema, context);
|
228
|
+
const defaultValue = nullable ? "null" : `${className}.new()`;
|
229
|
+
const result = {
|
230
|
+
typeName: className,
|
231
|
+
isNullable: nullable,
|
232
|
+
defaultValue,
|
233
|
+
fromJson(input, key) {
|
234
|
+
if (nullable) {
|
235
|
+
return `when (${input}) {
|
236
|
+
is JsonObject -> ${className}.fromJsonElement(
|
237
|
+
${input}!!,
|
238
|
+
"$instancePath/${key}",
|
239
|
+
)
|
240
|
+
|
241
|
+
else -> null
|
242
|
+
}`;
|
243
|
+
}
|
244
|
+
return `when (${input}) {
|
245
|
+
is JsonObject -> ${className}.fromJsonElement(
|
246
|
+
${input}!!,
|
247
|
+
"$instancePath/${key}",
|
248
|
+
)
|
249
|
+
|
250
|
+
else -> ${className}.new()
|
251
|
+
}`;
|
252
|
+
},
|
253
|
+
toJson(input, target) {
|
254
|
+
if (schema.nullable) {
|
255
|
+
return `${target} += ${input}?.toJson()`;
|
256
|
+
}
|
257
|
+
return `${target} += ${input}.toJson()`;
|
258
|
+
},
|
259
|
+
toQueryString() {
|
260
|
+
return `__logError("[WARNING] nested objects cannot be serialized to query params. Skipping field at ${context.instancePath}.")`;
|
261
|
+
},
|
262
|
+
content: ""
|
263
|
+
};
|
264
|
+
if (context.existingTypeIds.includes(className)) {
|
265
|
+
return result;
|
266
|
+
}
|
267
|
+
const subContent = [];
|
268
|
+
const kotlinKeys = [];
|
269
|
+
const fieldParts = [];
|
270
|
+
const defaultParts = [];
|
271
|
+
const toJsonParts = [`var output = "{"`];
|
272
|
+
const toQueryParts = ["val queryParts = mutableListOf<String>()"];
|
273
|
+
const fromJsonParts = [];
|
274
|
+
const requiredKeys = Object.keys(schema.properties);
|
275
|
+
const optionalKeys = Object.keys(schema.optionalProperties ?? {});
|
276
|
+
const hasKnownKeys = requiredKeys.length > 0 || context.discriminatorKey && context.discriminatorValue;
|
277
|
+
if (!hasKnownKeys) {
|
278
|
+
toJsonParts.push("var hasProperties = false");
|
279
|
+
}
|
280
|
+
if (context.discriminatorKey && context.discriminatorValue) {
|
281
|
+
toJsonParts.push(
|
282
|
+
`output += "\\"${context.discriminatorKey}\\":\\"${context.discriminatorValue}\\""`
|
283
|
+
);
|
284
|
+
toQueryParts.push(
|
285
|
+
`queryParts.add("${context.discriminatorKey}=${context.discriminatorValue}")`
|
286
|
+
);
|
287
|
+
}
|
288
|
+
for (let i = 0; i < requiredKeys.length; i++) {
|
289
|
+
const key = requiredKeys[i];
|
290
|
+
const kotlinKey = kotlinIdentifier(key);
|
291
|
+
kotlinKeys.push(kotlinKey);
|
292
|
+
const prop = schema.properties[key];
|
293
|
+
const type = kotlinTypeFromSchema(prop, {
|
294
|
+
modelPrefix: context.modelPrefix,
|
295
|
+
clientName: context.clientName,
|
296
|
+
clientVersion: context.clientVersion,
|
297
|
+
instancePath: `/${className}/${key}`,
|
298
|
+
schemaPath: `${context.schemaPath}/properties/${key}`,
|
299
|
+
existingTypeIds: context.existingTypeIds
|
300
|
+
});
|
301
|
+
if (type.content) {
|
302
|
+
subContent.push(type.content);
|
303
|
+
}
|
304
|
+
fieldParts.push(
|
305
|
+
` val ${kotlinKey}: ${type.typeName}${type.isNullable ? "?" : ""},`
|
306
|
+
);
|
307
|
+
if (i === 0 && !context.discriminatorKey) {
|
308
|
+
toJsonParts.push(`output += "\\"${key}\\":"`);
|
309
|
+
} else {
|
310
|
+
toJsonParts.push(`output += ",\\"${key}\\":"`);
|
311
|
+
}
|
312
|
+
toJsonParts.push(type.toJson(kotlinKey, "output"));
|
313
|
+
toQueryParts.push(type.toQueryString(kotlinKey, "queryParts", key));
|
314
|
+
fromJsonParts.push(
|
315
|
+
`val ${kotlinKey}: ${type.typeName}${type.isNullable ? "?" : ""} = ${type.fromJson(`__input.jsonObject["${key}"]`, key)}`
|
316
|
+
);
|
317
|
+
defaultParts.push(
|
318
|
+
` ${kotlinKey} = ${type.defaultValue},`
|
319
|
+
);
|
320
|
+
}
|
321
|
+
for (let i = 0; i < optionalKeys.length; i++) {
|
322
|
+
const isFirst = i === 0 && requiredKeys.length === 0 && !context.discriminatorKey;
|
323
|
+
const isLast = i === optionalKeys.length - 1;
|
324
|
+
const key = optionalKeys[i];
|
325
|
+
const kotlinKey = kotlinIdentifier(key);
|
326
|
+
kotlinKeys.push(kotlinKey);
|
327
|
+
const type = kotlinTypeFromSchema(schema.optionalProperties[key], {
|
328
|
+
modelPrefix: context.modelPrefix,
|
329
|
+
clientName: context.clientName,
|
330
|
+
clientVersion: context.clientVersion,
|
331
|
+
instancePath: `/${className}/${key}`,
|
332
|
+
schemaPath: `${context.schemaPath}/optionalProperties/${key}`,
|
333
|
+
existingTypeIds: context.existingTypeIds,
|
334
|
+
isOptional: true
|
335
|
+
});
|
336
|
+
if (type.content) {
|
337
|
+
subContent.push(type.content);
|
338
|
+
}
|
339
|
+
const addCommaPart = isFirst ? "" : `
|
340
|
+
if (hasProperties) output += ","
|
341
|
+
`;
|
342
|
+
fieldParts.push(` val ${kotlinKey}: ${type.typeName}? = null,`);
|
343
|
+
if (hasKnownKeys) {
|
344
|
+
toJsonParts.push(`if (${kotlinKey} != null) {
|
345
|
+
output += ",\\"${key}\\":"
|
346
|
+
${type.toJson(kotlinKey, "output")}
|
347
|
+
}`);
|
348
|
+
} else {
|
349
|
+
toJsonParts.push(`if (${kotlinKey} != null) {${addCommaPart}
|
350
|
+
output += "\\"${key}\\":"
|
351
|
+
${type.toJson(kotlinKey, "output")}
|
352
|
+
${isLast ? "" : " hasProperties = true"}
|
353
|
+
}`);
|
354
|
+
}
|
355
|
+
toQueryParts.push(type.toQueryString(kotlinKey, "queryParts", key));
|
356
|
+
fromJsonParts.push(
|
357
|
+
`val ${kotlinKey}: ${type.typeName}? = ${type.fromJson(`__input.jsonObject["${key}"]`, key)}`
|
358
|
+
);
|
359
|
+
}
|
360
|
+
toJsonParts.push('output += "}"');
|
361
|
+
toJsonParts.push("return output");
|
362
|
+
toQueryParts.push('return queryParts.joinToString("&")');
|
363
|
+
const implementedClass = context.discriminatorParentId ?? `${context.clientName}Model`;
|
364
|
+
let discriminatorField = "";
|
365
|
+
if (context.discriminatorKey && context.discriminatorValue) {
|
366
|
+
discriminatorField = `
|
367
|
+
override val ${kotlinIdentifier(context.discriminatorKey)} get() = "${context.discriminatorValue}"
|
368
|
+
`;
|
369
|
+
}
|
370
|
+
const content = `data class ${className}(
|
371
|
+
${fieldParts.join("\n")}
|
372
|
+
) : ${implementedClass} {${discriminatorField}
|
373
|
+
override fun toJson(): String {
|
374
|
+
${toJsonParts.join("\n")}
|
375
|
+
}
|
376
|
+
|
377
|
+
override fun toUrlQueryParams(): String {
|
378
|
+
${toQueryParts.join("\n")}
|
379
|
+
}
|
380
|
+
|
381
|
+
companion object Factory : ${context.clientName}ModelFactory<${className}> {
|
382
|
+
@JvmStatic
|
383
|
+
override fun new(): ${className} {
|
384
|
+
return ${className}(
|
385
|
+
${defaultParts.join("\n")}
|
386
|
+
)
|
387
|
+
}
|
388
|
+
|
389
|
+
@JvmStatic
|
390
|
+
override fun fromJson(input: String): ${className} {
|
391
|
+
return fromJsonElement(JsonInstance.parseToJsonElement(input))
|
392
|
+
}
|
393
|
+
|
394
|
+
@JvmStatic
|
395
|
+
override fun fromJsonElement(__input: JsonElement, instancePath: String): ${className} {
|
396
|
+
if (__input !is JsonObject) {
|
397
|
+
__logError("[WARNING] ${className}.fromJsonElement() expected kotlinx.serialization.json.JsonObject at $instancePath. Got \${__input.javaClass}. Initializing empty ${className}.")
|
398
|
+
return new()
|
399
|
+
}
|
400
|
+
${fromJsonParts.join("\n")}
|
401
|
+
return ${className}(
|
402
|
+
${kotlinKeys.join(",\n ")},
|
403
|
+
)
|
404
|
+
}
|
405
|
+
}
|
406
|
+
}
|
407
|
+
|
408
|
+
${subContent.join("\n\n")}`;
|
409
|
+
context.existingTypeIds.push(className);
|
410
|
+
return {
|
411
|
+
...result,
|
412
|
+
content
|
413
|
+
};
|
414
|
+
}
|
415
|
+
|
416
|
+
function kotlinDiscriminatorFromSchema(schema, context) {
|
417
|
+
const kotlinDiscriminatorKey = kotlinIdentifier(schema.discriminator);
|
418
|
+
const className = getClassName(schema, context);
|
419
|
+
const nullable = isNullable(schema, context);
|
420
|
+
const subTypes = [];
|
421
|
+
const subContent = [];
|
422
|
+
for (const key of Object.keys(schema.mapping)) {
|
423
|
+
const subSchema = schema.mapping[key];
|
424
|
+
const subType = kotlinObjectFromSchema(subSchema, {
|
425
|
+
modelPrefix: context.modelPrefix,
|
426
|
+
clientName: context.clientName,
|
427
|
+
clientVersion: context.clientVersion,
|
428
|
+
instancePath: context.instancePath,
|
429
|
+
schemaPath: `${context.schemaPath}/mapping/${key}`,
|
430
|
+
existingTypeIds: context.existingTypeIds,
|
431
|
+
discriminatorKey: schema.discriminator,
|
432
|
+
discriminatorValue: key,
|
433
|
+
discriminatorParentId: className
|
434
|
+
});
|
435
|
+
subTypes.push({
|
436
|
+
typeName: subType.typeName,
|
437
|
+
discriminatorValue: key
|
438
|
+
});
|
439
|
+
if (subType.content) {
|
440
|
+
subContent.push(subType.content);
|
441
|
+
}
|
442
|
+
}
|
443
|
+
if (subTypes.length === 0) {
|
444
|
+
throw new Error("Discriminator schemas must have at least one mapping");
|
445
|
+
}
|
446
|
+
const defaultValue = nullable ? "null" : `${className}.new()`;
|
447
|
+
const result = {
|
448
|
+
typeName: className,
|
449
|
+
isNullable: nullable,
|
450
|
+
defaultValue,
|
451
|
+
fromJson(input, key) {
|
452
|
+
return `when (${input}) {
|
453
|
+
is JsonObject -> ${className}.fromJsonElement(
|
454
|
+
${input}!!,
|
455
|
+
"$instancePath/${key}",
|
456
|
+
)
|
457
|
+
else -> ${defaultValue}
|
458
|
+
}`;
|
459
|
+
},
|
460
|
+
toJson(input, target) {
|
461
|
+
if (schema.nullable) {
|
462
|
+
return `${target} += ${input}?.toJson()`;
|
463
|
+
}
|
464
|
+
return `${target} += ${input}.toJson()`;
|
465
|
+
},
|
466
|
+
toQueryString() {
|
467
|
+
return `__logError("[WARNING] nested objects cannot be serialized to query params. Skipping field at ${context.instancePath}.")`;
|
468
|
+
},
|
469
|
+
content: ""
|
470
|
+
};
|
471
|
+
if (context.existingTypeIds.includes(className)) {
|
472
|
+
return result;
|
473
|
+
}
|
474
|
+
const content = `sealed interface ${className} : ${context.clientName}Model {
|
475
|
+
val ${kotlinDiscriminatorKey}: String
|
476
|
+
|
477
|
+
companion object Factory : ${context.clientName}ModelFactory<${className}> {
|
478
|
+
@JvmStatic
|
479
|
+
override fun new(): ${className} {
|
480
|
+
return ${subTypes[0].typeName}.new()
|
481
|
+
}
|
482
|
+
|
483
|
+
@JvmStatic
|
484
|
+
override fun fromJson(input: String): ${className} {
|
485
|
+
return fromJsonElement(JsonInstance.parseToJsonElement(input))
|
486
|
+
}
|
487
|
+
|
488
|
+
@JvmStatic
|
489
|
+
override fun fromJsonElement(__input: JsonElement, instancePath: String): ${className} {
|
490
|
+
if (__input !is JsonObject) {
|
491
|
+
__logError("[WARNING] Discriminator.fromJsonElement() expected kotlinx.serialization.json.JsonObject at $instancePath. Got \${__input.javaClass}. Initializing empty ${className}.")
|
492
|
+
return new()
|
493
|
+
}
|
494
|
+
return when (__input.jsonObject["${schema.discriminator}"]) {
|
495
|
+
is JsonPrimitive -> when (__input.jsonObject["${schema.discriminator}"]!!.jsonPrimitive.contentOrNull) {
|
496
|
+
${subTypes.map((type) => `"${type.discriminatorValue}" -> ${type.typeName}.fromJsonElement(__input, instancePath)`).join("\n")}
|
497
|
+
else -> new()
|
498
|
+
}
|
499
|
+
|
500
|
+
else -> new()
|
501
|
+
}
|
502
|
+
}
|
503
|
+
}
|
504
|
+
}
|
505
|
+
|
506
|
+
${subContent.join("\n\n")}`;
|
507
|
+
context.existingTypeIds.push(className);
|
508
|
+
return {
|
509
|
+
...result,
|
510
|
+
content
|
511
|
+
};
|
512
|
+
}
|
513
|
+
|
514
|
+
function kotlinEnumFromSchema(schema, context) {
|
515
|
+
const nullable = isNullable(schema, context);
|
516
|
+
const className = getClassName(schema, context);
|
517
|
+
const enumItems = schema.enum.map((val) => {
|
518
|
+
return {
|
519
|
+
name: codegenUtils.pascalCase(val, { normalize: true }),
|
520
|
+
value: val
|
521
|
+
};
|
522
|
+
});
|
523
|
+
if (!enumItems.length) {
|
524
|
+
throw new Error(
|
525
|
+
`Enum schemas must have at least one enum value. At ${context.schemaPath}.`
|
526
|
+
);
|
527
|
+
}
|
528
|
+
const defaultValue = nullable ? "null" : `${className}.new()`;
|
529
|
+
let content = "";
|
530
|
+
if (!context.existingTypeIds.includes(className)) {
|
531
|
+
content = `enum class ${className} {
|
532
|
+
${enumItems.map((item) => item.name).join(",\n ")};
|
533
|
+
val serialValue: String
|
534
|
+
get() = when (this) {
|
535
|
+
${enumItems.map((item) => `${item.name} -> "${item.value}"`).join("\n ")}
|
536
|
+
}
|
537
|
+
|
538
|
+
companion object Factory : ${context.clientName}ModelFactory<${className}> {
|
539
|
+
@JvmStatic
|
540
|
+
override fun new(): ${className} {
|
541
|
+
return ${enumItems[0].name}
|
542
|
+
}
|
543
|
+
|
544
|
+
@JvmStatic
|
545
|
+
override fun fromJson(input: String): ${className} {
|
546
|
+
return when (input) {
|
547
|
+
${enumItems.map((item) => `${item.name}.serialValue -> ${item.name}`).join("\n ")}
|
548
|
+
else -> ${enumItems[0].name}
|
549
|
+
}
|
550
|
+
}
|
551
|
+
|
552
|
+
@JvmStatic
|
553
|
+
override fun fromJsonElement(__input: JsonElement, instancePath: String): ${className} {
|
554
|
+
if (__input !is JsonPrimitive) {
|
555
|
+
__logError("[WARNING] ${className}.fromJsonElement() expected kotlinx.serialization.json.JsonPrimitive at $instancePath. Got \${__input.javaClass}. Initializing empty ${className}.")
|
556
|
+
return new()
|
557
|
+
}
|
558
|
+
return when (__input.jsonPrimitive.contentOrNull) {
|
559
|
+
${enumItems.map((item) => `"${item.value}" -> ${item.name}`).join("\n ")}
|
560
|
+
else -> new()
|
561
|
+
}
|
562
|
+
}
|
563
|
+
}
|
564
|
+
}`;
|
565
|
+
context.existingTypeIds.push(className);
|
566
|
+
}
|
567
|
+
return {
|
568
|
+
typeName: className,
|
569
|
+
isNullable: nullable,
|
570
|
+
defaultValue,
|
571
|
+
fromJson(input, key) {
|
572
|
+
if (nullable) {
|
573
|
+
return `when (${input}) {
|
574
|
+
is JsonNull -> null
|
575
|
+
is JsonPrimitive -> ${className}.fromJsonElement(${input}!!, "$instancePath/${key ?? ""}")
|
576
|
+
else -> null
|
577
|
+
}`;
|
578
|
+
}
|
579
|
+
return `when (${input}) {
|
580
|
+
is JsonNull -> ${defaultValue}
|
581
|
+
is JsonPrimitive -> ${className}.fromJsonElement(${input}!!, "$instancePath/${key}")
|
582
|
+
else -> ${defaultValue}
|
583
|
+
}`;
|
584
|
+
},
|
585
|
+
toJson(input, target) {
|
586
|
+
if (schema.nullable) {
|
587
|
+
return `${target} += when (${input}) {
|
588
|
+
is ${className} -> "\\"\${${input}.serialValue}\\""
|
589
|
+
else -> "null"
|
590
|
+
}`;
|
591
|
+
}
|
592
|
+
return `${target} += "\\"\${${input}.serialValue}\\""`;
|
593
|
+
},
|
594
|
+
toQueryString(input, target, key) {
|
595
|
+
if (context.isOptional) {
|
596
|
+
return `if (${input} != null) {
|
597
|
+
${target}.add("${key}=\${${input}.serialValue}")
|
598
|
+
}`;
|
599
|
+
}
|
600
|
+
if (schema.nullable) {
|
601
|
+
return `${target}.add("${key}=\${${input}?.serialValue}")`;
|
602
|
+
}
|
603
|
+
return `${target}.add("${key}=\${${input}.serialValue}")`;
|
604
|
+
},
|
605
|
+
content
|
606
|
+
};
|
607
|
+
}
|
608
|
+
|
609
|
+
function kotlinMapFromSchema(schema, context) {
|
610
|
+
const nullable = isNullable(schema, context);
|
611
|
+
const subType = kotlinTypeFromSchema(schema.values, {
|
612
|
+
modelPrefix: context.modelPrefix,
|
613
|
+
clientName: context.clientName,
|
614
|
+
clientVersion: context.clientVersion,
|
615
|
+
instancePath: `${context.instancePath}/[value]`,
|
616
|
+
schemaPath: `${context.schemaPath}/values`,
|
617
|
+
existingTypeIds: context.existingTypeIds
|
618
|
+
});
|
619
|
+
const typeName = `MutableMap<String, ${subType.typeName}${subType.isNullable ? "?" : ""}>`;
|
620
|
+
const defaultValue = nullable ? "null" : "mutableMapOf()";
|
621
|
+
return {
|
622
|
+
typeName,
|
623
|
+
isNullable: nullable,
|
624
|
+
defaultValue,
|
625
|
+
fromJson(input, key) {
|
626
|
+
return `when (${input}) {
|
627
|
+
is JsonObject -> {
|
628
|
+
val __value: ${typeName} = mutableMapOf()
|
629
|
+
for (__entry in ${input}!!.jsonObject.entries) {
|
630
|
+
__value[__entry.key] = ${subType.fromJson("__entry.value", key)}
|
631
|
+
}
|
632
|
+
__value
|
633
|
+
}
|
634
|
+
|
635
|
+
else -> ${defaultValue}
|
636
|
+
}`;
|
637
|
+
},
|
638
|
+
toJson(input, target) {
|
639
|
+
if (schema.nullable) {
|
640
|
+
return `if (${input} == null) {
|
641
|
+
${target} += "null"
|
642
|
+
} else {
|
643
|
+
${target} += "{"
|
644
|
+
for ((__index, __entry) in ${input}.entries.withIndex()) {
|
645
|
+
if (__index != 0) {
|
646
|
+
${target} += ","
|
647
|
+
}
|
648
|
+
${target} += "\\"\${__entry.key}\\":"
|
649
|
+
${subType.toJson("__entry.value", target)}
|
650
|
+
}
|
651
|
+
${target} += "}"
|
652
|
+
}`;
|
653
|
+
}
|
654
|
+
return `${target} += "{"
|
655
|
+
for ((__index, __entry) in ${input}.entries.withIndex()) {
|
656
|
+
if (__index != 0) {
|
657
|
+
${target} += ","
|
658
|
+
}
|
659
|
+
${target} += "\\"\${__entry.key}\\":"
|
660
|
+
${subType.toJson("__entry.value", target)}
|
661
|
+
}
|
662
|
+
${target} += "}"`;
|
663
|
+
},
|
664
|
+
toQueryString() {
|
665
|
+
return `__logError("[WARNING] nested objects cannot be serialized to query params. Skipping field at ${context.instancePath}.")`;
|
666
|
+
},
|
667
|
+
content: subType.content
|
668
|
+
};
|
669
|
+
}
|
670
|
+
|
671
|
+
function defaultToQueryString(context, input, target, key) {
|
672
|
+
if (context.isOptional) {
|
673
|
+
return `if (${input} != null) {
|
674
|
+
${target}.add("${key}=$${input}")
|
675
|
+
}`;
|
676
|
+
}
|
677
|
+
return `${target}.add("${key}=$${input}")`;
|
678
|
+
}
|
679
|
+
function defaultToJsonString(context, input, target) {
|
680
|
+
return `${target} += ${input}`;
|
681
|
+
}
|
682
|
+
function kotlinStringFromSchema(schema, context) {
|
683
|
+
const nullable = isNullable(schema, context);
|
684
|
+
const defaultValue = nullable ? "null" : '""';
|
685
|
+
return {
|
686
|
+
typeName: "String",
|
687
|
+
isNullable: nullable,
|
688
|
+
defaultValue,
|
689
|
+
fromJson(input) {
|
690
|
+
if (nullable) {
|
691
|
+
return `when (${input}) {
|
692
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull
|
693
|
+
else -> null
|
694
|
+
}`;
|
695
|
+
}
|
696
|
+
return `when (${input}) {
|
697
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull ?: ${defaultValue}
|
698
|
+
else -> ${defaultValue}
|
699
|
+
}`;
|
700
|
+
},
|
701
|
+
toJson(input, target) {
|
702
|
+
if (schema.nullable) {
|
703
|
+
return `${target} += when (${input}) {
|
704
|
+
is String -> buildString { printQuoted(${input}) }
|
705
|
+
else -> "null"
|
706
|
+
}`;
|
707
|
+
}
|
708
|
+
return `${target} += buildString { printQuoted(${input}) }`;
|
709
|
+
},
|
710
|
+
toQueryString(input, target, key) {
|
711
|
+
return defaultToQueryString(context, input, target, key);
|
712
|
+
},
|
713
|
+
content: ""
|
714
|
+
};
|
715
|
+
}
|
716
|
+
function kotlinBooleanFromSchema(schema, context) {
|
717
|
+
const nullable = isNullable(schema, context);
|
718
|
+
const defaultValue = nullable ? "null" : "false";
|
719
|
+
return {
|
720
|
+
typeName: "Boolean",
|
721
|
+
isNullable: nullable,
|
722
|
+
defaultValue,
|
723
|
+
fromJson(input) {
|
724
|
+
if (nullable) {
|
725
|
+
return `when (${input}) {
|
726
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.booleanOrNull
|
727
|
+
else -> null
|
728
|
+
}`;
|
729
|
+
}
|
730
|
+
return `when (${input}) {
|
731
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.booleanOrNull ?: ${defaultValue}
|
732
|
+
else -> ${defaultValue}
|
733
|
+
}`;
|
734
|
+
},
|
735
|
+
toJson(input, target) {
|
736
|
+
return defaultToJsonString(context, input, target);
|
737
|
+
},
|
738
|
+
toQueryString(input, target, key) {
|
739
|
+
return defaultToQueryString(context, input, target, key);
|
740
|
+
},
|
741
|
+
content: ""
|
742
|
+
};
|
743
|
+
}
|
744
|
+
function kotlinTimestampFromSchema(schema, context) {
|
745
|
+
const nullable = isNullable(schema, context);
|
746
|
+
const defaultValue = nullable ? "null" : "Instant.now()";
|
747
|
+
return {
|
748
|
+
typeName: "Instant",
|
749
|
+
isNullable: nullable,
|
750
|
+
defaultValue,
|
751
|
+
fromJson(input) {
|
752
|
+
return `when (${input}) {
|
753
|
+
is JsonPrimitive ->
|
754
|
+
if (${input}!!.jsonPrimitive.isString)
|
755
|
+
Instant.parse(${input}!!.jsonPrimitive.content)
|
756
|
+
else
|
757
|
+
${defaultValue}
|
758
|
+
else -> ${defaultValue}
|
759
|
+
}`;
|
760
|
+
},
|
761
|
+
toJson(input, target) {
|
762
|
+
if (schema.nullable) {
|
763
|
+
return `${target} += when (${input}) {
|
764
|
+
is Instant -> "\\"\${timestampFormatter.format(${input})}\\""
|
765
|
+
else -> "null"
|
766
|
+
}`;
|
767
|
+
}
|
768
|
+
return `${target} += "\\"\${timestampFormatter.format(${input})}\\""`;
|
769
|
+
},
|
770
|
+
toQueryString(input, target, key) {
|
771
|
+
if (context.isOptional) {
|
772
|
+
return `if (${input} != null) {
|
773
|
+
${target}.add(
|
774
|
+
"${key}=\${
|
775
|
+
timestampFormatter.format(${input})
|
776
|
+
}"
|
777
|
+
)
|
778
|
+
}`;
|
779
|
+
}
|
780
|
+
if (schema.nullable) {
|
781
|
+
return `${target}.add(
|
782
|
+
"${key}=\${
|
783
|
+
when (${input}) {
|
784
|
+
is Instant -> timestampFormatter.format(${input})
|
785
|
+
else -> "null"
|
786
|
+
}
|
787
|
+
}"
|
788
|
+
)`;
|
789
|
+
}
|
790
|
+
return `${target}.add(
|
791
|
+
"${key}=\${
|
792
|
+
timestampFormatter.format(${input})
|
793
|
+
}"
|
794
|
+
)`;
|
795
|
+
},
|
796
|
+
content: ""
|
797
|
+
};
|
798
|
+
}
|
799
|
+
function kotlinFloat32FromSchema(schema, context) {
|
800
|
+
const nullable = isNullable(schema, context);
|
801
|
+
const defaultValue = nullable ? "null" : "0.0F";
|
802
|
+
return {
|
803
|
+
typeName: "Float",
|
804
|
+
isNullable: nullable,
|
805
|
+
defaultValue,
|
806
|
+
fromJson(input) {
|
807
|
+
if (nullable) {
|
808
|
+
return `when (${input}) {
|
809
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.floatOrNull
|
810
|
+
else -> null
|
811
|
+
}`;
|
812
|
+
}
|
813
|
+
return `when (${input}) {
|
814
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.floatOrNull ?: ${defaultValue}
|
815
|
+
else -> ${defaultValue}
|
816
|
+
}`;
|
817
|
+
},
|
818
|
+
toJson(input, target) {
|
819
|
+
return defaultToJsonString(context, input, target);
|
820
|
+
},
|
821
|
+
toQueryString(input, target, key) {
|
822
|
+
return defaultToQueryString(context, input, target, key);
|
823
|
+
},
|
824
|
+
content: ""
|
825
|
+
};
|
826
|
+
}
|
827
|
+
function kotlinFloat64FromSchema(schema, context) {
|
828
|
+
const nullable = isNullable(schema, context);
|
829
|
+
const defaultValue = nullable ? "null" : "0.0";
|
830
|
+
return {
|
831
|
+
typeName: "Double",
|
832
|
+
isNullable: nullable,
|
833
|
+
defaultValue,
|
834
|
+
fromJson(input) {
|
835
|
+
if (nullable) {
|
836
|
+
return `when (${input}) {
|
837
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.doubleOrNull
|
838
|
+
else -> null
|
839
|
+
}`;
|
840
|
+
}
|
841
|
+
return `when (${input}) {
|
842
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.doubleOrNull ?: 0.0
|
843
|
+
else -> 0.0
|
844
|
+
}`;
|
845
|
+
},
|
846
|
+
toJson(input, target) {
|
847
|
+
return defaultToJsonString(context, input, target);
|
848
|
+
},
|
849
|
+
toQueryString(input, target, key) {
|
850
|
+
return defaultToQueryString(context, input, target, key);
|
851
|
+
},
|
852
|
+
content: ""
|
853
|
+
};
|
854
|
+
}
|
855
|
+
function kotlinInt8FromSchema(schema, context) {
|
856
|
+
const nullable = isNullable(schema, context);
|
857
|
+
const defaultValue = nullable ? "null" : "0";
|
858
|
+
return {
|
859
|
+
typeName: "Byte",
|
860
|
+
isNullable: nullable,
|
861
|
+
defaultValue,
|
862
|
+
fromJson(input) {
|
863
|
+
if (nullable) {
|
864
|
+
return `when (${input}) {
|
865
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull?.toByteOrNull()
|
866
|
+
else -> null
|
867
|
+
}`;
|
868
|
+
}
|
869
|
+
return `when (${input}) {
|
870
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull?.toByteOrNull() ?: 0
|
871
|
+
else -> 0
|
872
|
+
}`;
|
873
|
+
},
|
874
|
+
toJson(input, target) {
|
875
|
+
return defaultToJsonString(context, input, target);
|
876
|
+
},
|
877
|
+
toQueryString(input, target, key) {
|
878
|
+
return defaultToQueryString(context, input, target, key);
|
879
|
+
},
|
880
|
+
content: ""
|
881
|
+
};
|
882
|
+
}
|
883
|
+
function kotlinInt16FromSchema(schema, context) {
|
884
|
+
const nullable = isNullable(schema, context);
|
885
|
+
const defaultValue = nullable ? "null" : "0";
|
886
|
+
return {
|
887
|
+
typeName: "Short",
|
888
|
+
isNullable: nullable,
|
889
|
+
defaultValue,
|
890
|
+
fromJson(input) {
|
891
|
+
if (nullable) {
|
892
|
+
return `when (${input}) {
|
893
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull?.toShortOrNull()
|
894
|
+
else -> null
|
895
|
+
}`;
|
896
|
+
}
|
897
|
+
return `when (${input}) {
|
898
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull?.toShortOrNull() ?: 0
|
899
|
+
else -> 0
|
900
|
+
}`;
|
901
|
+
},
|
902
|
+
toJson(input, target) {
|
903
|
+
return defaultToJsonString(context, input, target);
|
904
|
+
},
|
905
|
+
toQueryString(input, target, key) {
|
906
|
+
return defaultToQueryString(context, input, target, key);
|
907
|
+
},
|
908
|
+
content: ""
|
909
|
+
};
|
910
|
+
}
|
911
|
+
function kotlinInt32FromSchema(schema, context) {
|
912
|
+
const nullable = isNullable(schema, context);
|
913
|
+
const defaultValue = nullable ? "null" : "0";
|
914
|
+
return {
|
915
|
+
typeName: "Int",
|
916
|
+
isNullable: nullable,
|
917
|
+
defaultValue,
|
918
|
+
fromJson(input) {
|
919
|
+
if (nullable) {
|
920
|
+
return `when (${input}) {
|
921
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.intOrNull
|
922
|
+
else -> null
|
923
|
+
}`;
|
924
|
+
}
|
925
|
+
return `when (${input}) {
|
926
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.intOrNull ?: 0
|
927
|
+
else -> 0
|
928
|
+
}`;
|
929
|
+
},
|
930
|
+
toJson(input, target) {
|
931
|
+
return defaultToJsonString(context, input, target);
|
932
|
+
},
|
933
|
+
toQueryString(input, target, key) {
|
934
|
+
return defaultToQueryString(context, input, target, key);
|
935
|
+
},
|
936
|
+
content: ""
|
937
|
+
};
|
938
|
+
}
|
939
|
+
function kotlinInt64FromSchema(schema, context) {
|
940
|
+
const nullable = isNullable(schema, context);
|
941
|
+
const defaultValue = nullable ? "null" : "0L";
|
942
|
+
return {
|
943
|
+
typeName: "Long",
|
944
|
+
isNullable: nullable,
|
945
|
+
defaultValue,
|
946
|
+
fromJson(input) {
|
947
|
+
if (nullable) {
|
948
|
+
return `when (${input}) {
|
949
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.longOrNull
|
950
|
+
else -> null
|
951
|
+
}`;
|
952
|
+
}
|
953
|
+
return `when (${input}) {
|
954
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.longOrNull ?: 0L
|
955
|
+
else -> 0L
|
956
|
+
}`;
|
957
|
+
},
|
958
|
+
toJson(input, target) {
|
959
|
+
if (schema.nullable) {
|
960
|
+
return `${target} += when (${input}) {
|
961
|
+
is Long -> "\\"$${input}\\""
|
962
|
+
else -> "null"
|
963
|
+
}`;
|
964
|
+
}
|
965
|
+
return `${target} += "\\"$${input}\\""`;
|
966
|
+
},
|
967
|
+
toQueryString(input, target, key) {
|
968
|
+
return defaultToQueryString(context, input, target, key);
|
969
|
+
},
|
970
|
+
content: ""
|
971
|
+
};
|
972
|
+
}
|
973
|
+
function kotlinUint8FromSchema(schema, context) {
|
974
|
+
const nullable = isNullable(schema, context);
|
975
|
+
const defaultValue = nullable ? "null" : "0u";
|
976
|
+
return {
|
977
|
+
typeName: "UByte",
|
978
|
+
isNullable: nullable,
|
979
|
+
defaultValue,
|
980
|
+
fromJson(input) {
|
981
|
+
if (nullable) {
|
982
|
+
return `when (${input}) {
|
983
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull?.toUByteOrNull()
|
984
|
+
else -> null
|
985
|
+
}`;
|
986
|
+
}
|
987
|
+
return `when (${input}) {
|
988
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull?.toUByteOrNull() ?: 0u
|
989
|
+
else -> 0u
|
990
|
+
}`;
|
991
|
+
},
|
992
|
+
toJson(input, target) {
|
993
|
+
return defaultToJsonString(context, input, target);
|
994
|
+
},
|
995
|
+
toQueryString(input, target, key) {
|
996
|
+
return defaultToQueryString(context, input, target, key);
|
997
|
+
},
|
998
|
+
content: ""
|
999
|
+
};
|
1000
|
+
}
|
1001
|
+
function kotlinUint16FromSchema(schema, context) {
|
1002
|
+
const nullable = isNullable(schema, context);
|
1003
|
+
const defaultValue = nullable ? "null" : "0u";
|
1004
|
+
return {
|
1005
|
+
typeName: "UShort",
|
1006
|
+
isNullable: nullable,
|
1007
|
+
defaultValue,
|
1008
|
+
fromJson(input) {
|
1009
|
+
if (nullable) {
|
1010
|
+
return `when (${input}) {
|
1011
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull?.toUShortOrNull()
|
1012
|
+
else -> null
|
1013
|
+
}`;
|
1014
|
+
}
|
1015
|
+
return `when (${input}) {
|
1016
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull?.toUShortOrNull() ?: 0u
|
1017
|
+
else -> 0u
|
1018
|
+
}`;
|
1019
|
+
},
|
1020
|
+
toJson(input, target) {
|
1021
|
+
return defaultToJsonString(context, input, target);
|
1022
|
+
},
|
1023
|
+
toQueryString(input, target, key) {
|
1024
|
+
return defaultToQueryString(context, input, target, key);
|
1025
|
+
},
|
1026
|
+
content: ""
|
1027
|
+
};
|
1028
|
+
}
|
1029
|
+
function kotlinUint32FromSchema(schema, context) {
|
1030
|
+
const nullable = isNullable(schema, context);
|
1031
|
+
const defaultValue = nullable ? "null" : "0u";
|
1032
|
+
return {
|
1033
|
+
typeName: "UInt",
|
1034
|
+
isNullable: nullable,
|
1035
|
+
defaultValue,
|
1036
|
+
fromJson(input) {
|
1037
|
+
if (nullable) {
|
1038
|
+
return `when (${input}) {
|
1039
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull?.toUIntOrNull()
|
1040
|
+
else -> null
|
1041
|
+
}`;
|
1042
|
+
}
|
1043
|
+
return `when (${input}) {
|
1044
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull?.toUIntOrNull() ?: 0u
|
1045
|
+
else -> 0u
|
1046
|
+
}`;
|
1047
|
+
},
|
1048
|
+
toJson(input, target) {
|
1049
|
+
return defaultToJsonString(context, input, target);
|
1050
|
+
},
|
1051
|
+
toQueryString(input, target, key) {
|
1052
|
+
return defaultToQueryString(context, input, target, key);
|
1053
|
+
},
|
1054
|
+
content: ""
|
1055
|
+
};
|
1056
|
+
}
|
1057
|
+
function kotlinUint64FromSchema(schema, context) {
|
1058
|
+
const nullable = isNullable(schema, context);
|
1059
|
+
const defaultValue = nullable ? "null" : "0UL";
|
1060
|
+
return {
|
1061
|
+
typeName: "ULong",
|
1062
|
+
isNullable: nullable,
|
1063
|
+
defaultValue,
|
1064
|
+
fromJson(input) {
|
1065
|
+
if (nullable) {
|
1066
|
+
return `when (${input}) {
|
1067
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull?.toULongOrNull()
|
1068
|
+
else -> null
|
1069
|
+
}`;
|
1070
|
+
}
|
1071
|
+
return `when (${input}) {
|
1072
|
+
is JsonPrimitive -> ${input}!!.jsonPrimitive.contentOrNull?.toULongOrNull() ?: 0UL
|
1073
|
+
else -> 0UL
|
1074
|
+
}`;
|
1075
|
+
},
|
1076
|
+
toJson(input, target) {
|
1077
|
+
if (schema.nullable) {
|
1078
|
+
return `${target} += when (${input}) {
|
1079
|
+
is ULong -> "\\"$${input}\\""
|
1080
|
+
else -> "null"
|
1081
|
+
}`;
|
1082
|
+
}
|
1083
|
+
return `${target} += "\\"$${input}\\""`;
|
1084
|
+
},
|
1085
|
+
toQueryString(input, target, key) {
|
1086
|
+
return defaultToQueryString(context, input, target, key);
|
1087
|
+
},
|
1088
|
+
content: ""
|
1089
|
+
};
|
1090
|
+
}
|
1091
|
+
|
1092
|
+
function kotlinProcedureFromSchema(schema, context) {
|
1093
|
+
switch (schema.transport) {
|
1094
|
+
case "http":
|
1095
|
+
return kotlinHttpRpcFromSchema(schema, context);
|
1096
|
+
case "ws":
|
1097
|
+
return kotlinWsRpcFromSchema();
|
1098
|
+
default:
|
1099
|
+
console.warn(
|
1100
|
+
`[codegen-kotlin] Unknown transport type "${schema.transport}". Skipping "${context.instancePath}".`
|
1101
|
+
);
|
1102
|
+
return "";
|
1103
|
+
}
|
1104
|
+
}
|
1105
|
+
function kotlinHttpRpcFromSchema(schema, context) {
|
1106
|
+
const name = getProcedureName(context);
|
1107
|
+
const params = schema.params ? kotlinClassName(`${context.modelPrefix}_${schema.params}`) : void 0;
|
1108
|
+
const response = schema.response ? kotlinClassName(`${context.modelPrefix}_${schema.response}`) : void 0;
|
1109
|
+
if (schema.isEventStream) {
|
1110
|
+
return `fun ${name}(
|
1111
|
+
scope: CoroutineScope,
|
1112
|
+
${params ? `params: ${params},` : ""}
|
1113
|
+
lastEventId: String? = null,
|
1114
|
+
bufferCapacity: Int = 1024,
|
1115
|
+
onOpen: ((response: HttpResponse) -> Unit) = {},
|
1116
|
+
onClose: (() -> Unit) = {},
|
1117
|
+
onError: ((error: ${context.clientName}Error) -> Unit) = {},
|
1118
|
+
onConnectionError: ((error: ${context.clientName}Error) -> Unit) = {},
|
1119
|
+
onData: ((${response ? `data: ${response}` : ""}) -> Unit) = {},
|
1120
|
+
): Job {
|
1121
|
+
val job = scope.launch {
|
1122
|
+
__handleSseRequest(
|
1123
|
+
scope = scope,
|
1124
|
+
httpClient = httpClient,
|
1125
|
+
url = "$baseUrl${schema.path}",
|
1126
|
+
method = HttpMethod.${codegenUtils.pascalCase(schema.method, { normalize: true })},
|
1127
|
+
params = ${params ? "params" : "null"},
|
1128
|
+
headers = headers,
|
1129
|
+
backoffTime = 0,
|
1130
|
+
maxBackoffTime = 30000L,
|
1131
|
+
lastEventId = lastEventId,
|
1132
|
+
bufferCapacity = bufferCapacity,
|
1133
|
+
onOpen = onOpen,
|
1134
|
+
onClose = onClose,
|
1135
|
+
onError = onError,
|
1136
|
+
onConnectionError = onConnectionError,
|
1137
|
+
onData = { str ->
|
1138
|
+
${response ? `val data = ${response}.fromJson(str)` : ""}
|
1139
|
+
onData(${response ? "data" : ""})
|
1140
|
+
}
|
1141
|
+
)
|
1142
|
+
}
|
1143
|
+
return job
|
1144
|
+
}`;
|
1145
|
+
}
|
1146
|
+
const headingCheck = `if (response.headers["Content-Type"] != "application/json") {
|
1147
|
+
throw ${context.clientName}Error(
|
1148
|
+
code = 0,
|
1149
|
+
errorMessage = "Expected server to return Content-Type \\"application/json\\". Got \\"\${response.headers["Content-Type"]}\\"",
|
1150
|
+
data = JsonPrimitive(response.bodyAsText()),
|
1151
|
+
stack = null,
|
1152
|
+
)
|
1153
|
+
}`;
|
1154
|
+
return `suspend fun ${name}(${params ? `params: ${params}` : ""}): ${response ?? "Unit"} {
|
1155
|
+
val response = __prepareRequest(
|
1156
|
+
client = httpClient,
|
1157
|
+
url = "$baseUrl${schema.path}",
|
1158
|
+
method = HttpMethod.${codegenUtils.pascalCase(schema.method, { normalize: true })},
|
1159
|
+
params = ${params ? "params" : null},
|
1160
|
+
headers = headers?.invoke(),
|
1161
|
+
).execute()
|
1162
|
+
${response ? headingCheck : ""}
|
1163
|
+
if (response.status.value in 200..299) {
|
1164
|
+
return ${response ? `${response}.fromJson(response.bodyAsText())` : ""}
|
1165
|
+
}
|
1166
|
+
throw ${context.clientName}Error.fromJson(response.bodyAsText())
|
1167
|
+
}`;
|
1168
|
+
}
|
1169
|
+
function kotlinWsRpcFromSchema(schema, context) {
|
1170
|
+
return "";
|
1171
|
+
}
|
1172
|
+
function kotlinServiceFromSchema(schema, context) {
|
1173
|
+
const name = getServiceName(context);
|
1174
|
+
const procedureParts = [];
|
1175
|
+
const subServiceParts = [];
|
1176
|
+
for (const key of Object.keys(schema)) {
|
1177
|
+
const kotlinKey = kotlinIdentifier(key);
|
1178
|
+
const subSchema = schema[key];
|
1179
|
+
if (codegenUtils.isServiceDefinition(subSchema)) {
|
1180
|
+
const subService = kotlinServiceFromSchema(subSchema, {
|
1181
|
+
...context,
|
1182
|
+
instancePath: `${context.instancePath}.${key}`
|
1183
|
+
});
|
1184
|
+
procedureParts.push(`val ${kotlinKey}: ${subService.name} = ${subService.name}(
|
1185
|
+
httpClient = httpClient,
|
1186
|
+
baseUrl = baseUrl,
|
1187
|
+
headers = headers,
|
1188
|
+
)`);
|
1189
|
+
if (subService.content) {
|
1190
|
+
subServiceParts.push(subService.content);
|
1191
|
+
}
|
1192
|
+
continue;
|
1193
|
+
}
|
1194
|
+
if (codegenUtils.isRpcDefinition(subSchema)) {
|
1195
|
+
const procedure = kotlinProcedureFromSchema(subSchema, {
|
1196
|
+
...context,
|
1197
|
+
instancePath: `${context.instancePath}.${key}`
|
1198
|
+
});
|
1199
|
+
if (procedure.length > 0) {
|
1200
|
+
procedureParts.push(procedure);
|
1201
|
+
}
|
1202
|
+
continue;
|
1203
|
+
}
|
1204
|
+
}
|
1205
|
+
return {
|
1206
|
+
name,
|
1207
|
+
content: `class ${name}(
|
1208
|
+
private val httpClient: HttpClient,
|
1209
|
+
private val baseUrl: String,
|
1210
|
+
private val headers: headersFn,
|
1211
|
+
) {
|
1212
|
+
${procedureParts.join("\n\n ")}
|
1213
|
+
}
|
1214
|
+
|
1215
|
+
${subServiceParts.join("\n\n")}`
|
1216
|
+
};
|
1217
|
+
}
|
1218
|
+
function getProcedureName(context) {
|
1219
|
+
const name = context.instancePath.split(".").pop() ?? "";
|
1220
|
+
return kotlinIdentifier(name);
|
1221
|
+
}
|
1222
|
+
function getServiceName(context) {
|
1223
|
+
const name = codegenUtils.pascalCase(context.instancePath.split(".").join("_"));
|
1224
|
+
return `${context.clientName}${name}Service`;
|
1225
|
+
}
|
1226
|
+
|
1227
|
+
function kotlinRefFromSchema(schema, context) {
|
1228
|
+
const typeName = kotlinClassName(schema.ref);
|
1229
|
+
const nullable = isNullable(schema, context);
|
1230
|
+
const defaultValue = nullable ? "null" : `${typeName}.new()`;
|
1231
|
+
return {
|
1232
|
+
typeName,
|
1233
|
+
isNullable: nullable,
|
1234
|
+
defaultValue,
|
1235
|
+
fromJson(input, key) {
|
1236
|
+
return `when (${input}) {
|
1237
|
+
is JsonObject -> ${typeName}.fromJsonElement(
|
1238
|
+
${input}!!,
|
1239
|
+
"$instancePath/${key}",
|
1240
|
+
)
|
1241
|
+
else -> ${defaultValue}
|
1242
|
+
}`;
|
1243
|
+
},
|
1244
|
+
toJson(input, target) {
|
1245
|
+
if (schema.nullable) {
|
1246
|
+
return `${target} += ${input}?.toJson()`;
|
1247
|
+
}
|
1248
|
+
return `${target} += ${input}.toJson()`;
|
1249
|
+
},
|
1250
|
+
toQueryString() {
|
1251
|
+
return `__logError("[WARNING] nested objects cannot be serialized to query params. Skipping field at ${context.instancePath}.")`;
|
1252
|
+
},
|
1253
|
+
content: ""
|
1254
|
+
};
|
1255
|
+
}
|
1256
|
+
|
1257
|
+
const kotlinClientGenerator = codegenUtils.defineClientGeneratorPlugin(
|
1258
|
+
(options) => {
|
1259
|
+
return {
|
1260
|
+
generator(def) {
|
1261
|
+
const client = kotlinClientFromAppDefinition(def, options);
|
1262
|
+
fs__default.writeFileSync(options.outputFile, client);
|
1263
|
+
},
|
1264
|
+
options
|
1265
|
+
};
|
1266
|
+
}
|
1267
|
+
);
|
1268
|
+
function kotlinClientFromAppDefinition(def, options) {
|
1269
|
+
const clientName = kotlinClassName(options.clientName ?? "Client");
|
1270
|
+
const context = {
|
1271
|
+
modelPrefix: options.modelPrefix ?? "",
|
1272
|
+
clientName,
|
1273
|
+
clientVersion: def.info?.version ?? "",
|
1274
|
+
instancePath: "",
|
1275
|
+
schemaPath: "",
|
1276
|
+
existingTypeIds: []
|
1277
|
+
};
|
1278
|
+
const modelParts = [];
|
1279
|
+
for (const key of Object.keys(def.definitions)) {
|
1280
|
+
const subSchema = def.definitions[key];
|
1281
|
+
const model = kotlinTypeFromSchema(subSchema, {
|
1282
|
+
modelPrefix: context.modelPrefix,
|
1283
|
+
clientName: context.clientName,
|
1284
|
+
clientVersion: context.clientVersion,
|
1285
|
+
instancePath: `/${key}`,
|
1286
|
+
schemaPath: `/definitions`,
|
1287
|
+
existingTypeIds: context.existingTypeIds
|
1288
|
+
});
|
1289
|
+
if (model.content) {
|
1290
|
+
modelParts.push(model.content);
|
1291
|
+
}
|
1292
|
+
}
|
1293
|
+
const procedureParts = [];
|
1294
|
+
const subServiceParts = [];
|
1295
|
+
const services = codegenUtils.unflattenProcedures(def.procedures);
|
1296
|
+
for (const key of Object.keys(services)) {
|
1297
|
+
const subSchema = services[key];
|
1298
|
+
if (codegenUtils.isServiceDefinition(subSchema)) {
|
1299
|
+
const kotlinKey = kotlinIdentifier(key);
|
1300
|
+
const subService = kotlinServiceFromSchema(subSchema, {
|
1301
|
+
...context,
|
1302
|
+
instancePath: `${key}`
|
1303
|
+
});
|
1304
|
+
procedureParts.push(`val ${kotlinKey}: ${subService.name} = ${subService.name}(
|
1305
|
+
httpClient = httpClient,
|
1306
|
+
baseUrl = baseUrl,
|
1307
|
+
headers = headers,
|
1308
|
+
)`);
|
1309
|
+
if (subService.content) {
|
1310
|
+
subServiceParts.push(subService.content);
|
1311
|
+
}
|
1312
|
+
continue;
|
1313
|
+
}
|
1314
|
+
if (codegenUtils.isRpcDefinition(subSchema)) {
|
1315
|
+
const procedure = kotlinProcedureFromSchema(subSchema, {
|
1316
|
+
...context,
|
1317
|
+
instancePath: key
|
1318
|
+
});
|
1319
|
+
if (procedure.length) {
|
1320
|
+
procedureParts.push(procedure);
|
1321
|
+
}
|
1322
|
+
continue;
|
1323
|
+
}
|
1324
|
+
}
|
1325
|
+
if (procedureParts.length === 0) {
|
1326
|
+
return `${getHeader({ clientName, clientVersion: context.clientVersion, packageName: "" })}
|
1327
|
+
${getUtilityClasses(clientName)}
|
1328
|
+
|
1329
|
+
${modelParts.join("\n\n")}
|
1330
|
+
|
1331
|
+
${getUtilityFunctions(clientName)}`;
|
1332
|
+
}
|
1333
|
+
return `${getHeader({ clientName, clientVersion: context.clientVersion, packageName: "" })}
|
1334
|
+
|
1335
|
+
class ${clientName}(
|
1336
|
+
private val httpClient: HttpClient,
|
1337
|
+
private val baseUrl: String,
|
1338
|
+
private val headers: headersFn,
|
1339
|
+
) {
|
1340
|
+
${procedureParts.join("\n\n ")}
|
1341
|
+
}
|
1342
|
+
|
1343
|
+
${subServiceParts.join("\n\n")}
|
1344
|
+
|
1345
|
+
${getUtilityClasses(clientName)}
|
1346
|
+
|
1347
|
+
${modelParts.join("\n\n")}
|
1348
|
+
|
1349
|
+
${getUtilityFunctions(clientName)}`;
|
1350
|
+
}
|
1351
|
+
function kotlinTypeFromSchema(schema, context) {
|
1352
|
+
if (codegenUtils.isSchemaFormType(schema)) {
|
1353
|
+
switch (schema.type) {
|
1354
|
+
case "string":
|
1355
|
+
return kotlinStringFromSchema(schema, context);
|
1356
|
+
case "boolean":
|
1357
|
+
return kotlinBooleanFromSchema(schema, context);
|
1358
|
+
case "timestamp":
|
1359
|
+
return kotlinTimestampFromSchema(schema, context);
|
1360
|
+
case "float32":
|
1361
|
+
return kotlinFloat32FromSchema(schema, context);
|
1362
|
+
case "float64":
|
1363
|
+
return kotlinFloat64FromSchema(schema, context);
|
1364
|
+
case "int8":
|
1365
|
+
return kotlinInt8FromSchema(schema, context);
|
1366
|
+
case "int16":
|
1367
|
+
return kotlinInt16FromSchema(schema, context);
|
1368
|
+
case "int32":
|
1369
|
+
return kotlinInt32FromSchema(schema, context);
|
1370
|
+
case "int64":
|
1371
|
+
return kotlinInt64FromSchema(schema, context);
|
1372
|
+
case "uint8":
|
1373
|
+
return kotlinUint8FromSchema(schema, context);
|
1374
|
+
case "uint16":
|
1375
|
+
return kotlinUint16FromSchema(schema, context);
|
1376
|
+
case "uint32":
|
1377
|
+
return kotlinUint32FromSchema(schema, context);
|
1378
|
+
case "uint64":
|
1379
|
+
return kotlinUint64FromSchema(schema, context);
|
1380
|
+
default:
|
1381
|
+
schema.type;
|
1382
|
+
throw new Error(`Unhandled schema.type case`);
|
1383
|
+
}
|
1384
|
+
}
|
1385
|
+
if (codegenUtils.isSchemaFormEnum(schema)) {
|
1386
|
+
return kotlinEnumFromSchema(schema, context);
|
1387
|
+
}
|
1388
|
+
if (codegenUtils.isSchemaFormProperties(schema)) {
|
1389
|
+
return kotlinObjectFromSchema(schema, context);
|
1390
|
+
}
|
1391
|
+
if (codegenUtils.isSchemaFormElements(schema)) {
|
1392
|
+
return kotlinArrayFromSchema(schema, context);
|
1393
|
+
}
|
1394
|
+
if (codegenUtils.isSchemaFormValues(schema)) {
|
1395
|
+
return kotlinMapFromSchema(schema, context);
|
1396
|
+
}
|
1397
|
+
if (codegenUtils.isSchemaFormDiscriminator(schema)) {
|
1398
|
+
return kotlinDiscriminatorFromSchema(schema, context);
|
1399
|
+
}
|
1400
|
+
if (codegenUtils.isSchemaFormRef(schema)) {
|
1401
|
+
return kotlinRefFromSchema(schema, context);
|
1402
|
+
}
|
1403
|
+
return kotlinAnyFromSchema(schema, context);
|
1404
|
+
}
|
1405
|
+
function getUtilityClasses(clientName) {
|
1406
|
+
return `interface ${clientName}Model {
|
1407
|
+
fun toJson(): String
|
1408
|
+
fun toUrlQueryParams(): String
|
1409
|
+
}
|
1410
|
+
|
1411
|
+
interface ${clientName}ModelFactory<T> {
|
1412
|
+
fun new(): T
|
1413
|
+
fun fromJson(input: String): T
|
1414
|
+
fun fromJsonElement(
|
1415
|
+
__input: JsonElement,
|
1416
|
+
instancePath: String = "",
|
1417
|
+
): T
|
1418
|
+
}
|
1419
|
+
|
1420
|
+
data class ${clientName}Error(
|
1421
|
+
val code: Int,
|
1422
|
+
val errorMessage: String,
|
1423
|
+
val data: JsonElement?,
|
1424
|
+
val stack: List<String>?,
|
1425
|
+
) : Exception(errorMessage), ${clientName}Model {
|
1426
|
+
override fun toJson(): String {
|
1427
|
+
var output = "{"
|
1428
|
+
output += "\\"code\\":"
|
1429
|
+
output += "$code"
|
1430
|
+
output += ",\\"message\\":"
|
1431
|
+
output += buildString { printQuoted(errorMessage) }
|
1432
|
+
if (data != null) {
|
1433
|
+
output += ",\\"data\\":"
|
1434
|
+
output += JsonInstance.encodeToString(data)
|
1435
|
+
}
|
1436
|
+
if (stack != null) {
|
1437
|
+
output += ",\\"stack\\":"
|
1438
|
+
output += "["
|
1439
|
+
for ((__index, __element) in stack.withIndex()) {
|
1440
|
+
if (__index > 0) {
|
1441
|
+
output += ","
|
1442
|
+
}
|
1443
|
+
output += buildString { printQuoted(__element) }
|
1444
|
+
}
|
1445
|
+
output += "]"
|
1446
|
+
}
|
1447
|
+
output += "}"
|
1448
|
+
return output
|
1449
|
+
}
|
1450
|
+
|
1451
|
+
override fun toUrlQueryParams(): String {
|
1452
|
+
val queryParts = mutableListOf<String>()
|
1453
|
+
queryParts.add("code=\${code}")
|
1454
|
+
queryParts.add("message=\${errorMessage}")
|
1455
|
+
return queryParts.joinToString("&")
|
1456
|
+
}
|
1457
|
+
|
1458
|
+
companion object Factory : ${clientName}ModelFactory<${clientName}Error> {
|
1459
|
+
override fun new(): ${clientName}Error {
|
1460
|
+
return ${clientName}Error(
|
1461
|
+
code = 0,
|
1462
|
+
errorMessage = "",
|
1463
|
+
data = null,
|
1464
|
+
stack = null
|
1465
|
+
)
|
1466
|
+
}
|
1467
|
+
|
1468
|
+
override fun fromJson(input: String): ${clientName}Error {
|
1469
|
+
return fromJsonElement(JsonInstance.parseToJsonElement(input))
|
1470
|
+
}
|
1471
|
+
|
1472
|
+
override fun fromJsonElement(__input: JsonElement, instancePath: String): ${clientName}Error {
|
1473
|
+
if (__input !is JsonObject) {
|
1474
|
+
__logError("[WARNING] ${clientName}Error.fromJsonElement() expected JsonObject at $instancePath. Got \${__input.javaClass}. Initializing empty ${clientName}Error.")
|
1475
|
+
}
|
1476
|
+
val code = when (__input.jsonObject["code"]) {
|
1477
|
+
is JsonPrimitive -> __input.jsonObject["code"]!!.jsonPrimitive.intOrNull ?: 0
|
1478
|
+
else -> 0
|
1479
|
+
}
|
1480
|
+
val errorMessage = when (__input.jsonObject["message"]) {
|
1481
|
+
is JsonPrimitive -> __input.jsonObject["message"]!!.jsonPrimitive.contentOrNull ?: ""
|
1482
|
+
else -> ""
|
1483
|
+
}
|
1484
|
+
val data = when (__input.jsonObject["data"]) {
|
1485
|
+
is JsonNull -> null
|
1486
|
+
is JsonElement -> __input.jsonObject["data"]!!
|
1487
|
+
else -> null
|
1488
|
+
}
|
1489
|
+
val stack = when (__input.jsonObject["stack"]) {
|
1490
|
+
is JsonArray -> {
|
1491
|
+
val stackVal = mutableListOf<String>()
|
1492
|
+
for (item in __input.jsonObject["stack"]!!.jsonArray) {
|
1493
|
+
stackVal.add(
|
1494
|
+
when (item) {
|
1495
|
+
is JsonPrimitive -> item.contentOrNull ?: ""
|
1496
|
+
else -> ""
|
1497
|
+
}
|
1498
|
+
)
|
1499
|
+
}
|
1500
|
+
stackVal
|
1501
|
+
}
|
1502
|
+
|
1503
|
+
else -> null
|
1504
|
+
|
1505
|
+
}
|
1506
|
+
return ${clientName}Error(
|
1507
|
+
code,
|
1508
|
+
errorMessage,
|
1509
|
+
data,
|
1510
|
+
stack,
|
1511
|
+
)
|
1512
|
+
}
|
1513
|
+
|
1514
|
+
}
|
1515
|
+
}`;
|
1516
|
+
}
|
1517
|
+
function getUtilityFunctions(clientName) {
|
1518
|
+
return `// Implementation copied from https://github.com/Kotlin/kotlinx.serialization/blob/d0ae697b9394103879e6c7f836d0f7cf128f4b1e/formats/json/commonMain/src/kotlinx/serialization/json/internal/StringOps.kt#L45
|
1519
|
+
internal const val STRING = '"'
|
1520
|
+
|
1521
|
+
private fun toHexChar(i: Int): Char {
|
1522
|
+
val d = i and 0xf
|
1523
|
+
return if (d < 10) (d + '0'.code).toChar()
|
1524
|
+
else (d - 10 + 'a'.code).toChar()
|
1525
|
+
}
|
1526
|
+
|
1527
|
+
internal val ESCAPE_STRINGS: Array<String?> = arrayOfNulls<String>(93).apply {
|
1528
|
+
for (c in 0..0x1f) {
|
1529
|
+
val c1 = toHexChar(c shr 12)
|
1530
|
+
val c2 = toHexChar(c shr 8)
|
1531
|
+
val c3 = toHexChar(c shr 4)
|
1532
|
+
val c4 = toHexChar(c)
|
1533
|
+
this[c] = "\\\\u$c1$c2$c3$c4"
|
1534
|
+
}
|
1535
|
+
this['"'.code] = "\\\\\\""
|
1536
|
+
this['\\\\'.code] = "\\\\\\\\"
|
1537
|
+
this['\\t'.code] = "\\\\t"
|
1538
|
+
this['\\b'.code] = "\\\\b"
|
1539
|
+
this['\\n'.code] = "\\\\n"
|
1540
|
+
this['\\r'.code] = "\\\\r"
|
1541
|
+
this[0x0c] = "\\\\f"
|
1542
|
+
}
|
1543
|
+
|
1544
|
+
internal val ESCAPE_MARKERS: ByteArray = ByteArray(93).apply {
|
1545
|
+
for (c in 0..0x1f) {
|
1546
|
+
this[c] = 1.toByte()
|
1547
|
+
}
|
1548
|
+
this['"'.code] = '"'.code.toByte()
|
1549
|
+
this['\\\\'.code] = '\\\\'.code.toByte()
|
1550
|
+
this['\\t'.code] = 't'.code.toByte()
|
1551
|
+
this['\\b'.code] = 'b'.code.toByte()
|
1552
|
+
this['\\n'.code] = 'n'.code.toByte()
|
1553
|
+
this['\\r'.code] = 'r'.code.toByte()
|
1554
|
+
this[0x0c] = 'f'.code.toByte()
|
1555
|
+
}
|
1556
|
+
|
1557
|
+
internal fun StringBuilder.printQuoted(value: String) {
|
1558
|
+
append(STRING)
|
1559
|
+
var lastPos = 0
|
1560
|
+
for (i in value.indices) {
|
1561
|
+
val c = value[i].code
|
1562
|
+
if (c < ESCAPE_STRINGS.size && ESCAPE_STRINGS[c] != null) {
|
1563
|
+
append(value, lastPos, i) // flush prev
|
1564
|
+
append(ESCAPE_STRINGS[c])
|
1565
|
+
lastPos = i + 1
|
1566
|
+
}
|
1567
|
+
}
|
1568
|
+
|
1569
|
+
if (lastPos != 0) append(value, lastPos, value.length)
|
1570
|
+
else append(value)
|
1571
|
+
append(STRING)
|
1572
|
+
}
|
1573
|
+
|
1574
|
+
private fun __logError(string: String) {
|
1575
|
+
System.err.println(string)
|
1576
|
+
}
|
1577
|
+
|
1578
|
+
private suspend fun __prepareRequest(
|
1579
|
+
client: HttpClient,
|
1580
|
+
url: String,
|
1581
|
+
method: HttpMethod,
|
1582
|
+
params: ${clientName}Model?,
|
1583
|
+
headers: MutableMap<String, String>?,
|
1584
|
+
): HttpStatement {
|
1585
|
+
var finalUrl = url
|
1586
|
+
var finalBody = ""
|
1587
|
+
when (method) {
|
1588
|
+
HttpMethod.Get, HttpMethod.Head -> {
|
1589
|
+
finalUrl = "$finalUrl?\${params?.toUrlQueryParams() ?: ""}"
|
1590
|
+
}
|
1591
|
+
|
1592
|
+
HttpMethod.Post, HttpMethod.Put, HttpMethod.Patch, HttpMethod.Delete -> {
|
1593
|
+
finalBody = params?.toJson() ?: ""
|
1594
|
+
}
|
1595
|
+
}
|
1596
|
+
val builder = HttpRequestBuilder()
|
1597
|
+
builder.method = method
|
1598
|
+
builder.url(finalUrl)
|
1599
|
+
builder.timeout {
|
1600
|
+
requestTimeoutMillis = 10 * 60 * 1000
|
1601
|
+
}
|
1602
|
+
if (headers != null) {
|
1603
|
+
for (entry in headers.entries) {
|
1604
|
+
builder.headers[entry.key] = entry.value
|
1605
|
+
}
|
1606
|
+
}
|
1607
|
+
builder.headers["client-version"] = generatedClientVersion
|
1608
|
+
if (method != HttpMethod.Get && method != HttpMethod.Head) {
|
1609
|
+
builder.setBody(finalBody)
|
1610
|
+
}
|
1611
|
+
return client.prepareRequest(builder)
|
1612
|
+
}
|
1613
|
+
|
1614
|
+
private fun __parseSseEvent(input: String): __SseEvent {
|
1615
|
+
val lines = input.split("\\n")
|
1616
|
+
var id: String? = null
|
1617
|
+
var event: String? = null
|
1618
|
+
var data: String = ""
|
1619
|
+
for (line in lines) {
|
1620
|
+
if (line.startsWith("id: ")) {
|
1621
|
+
id = line.substring(3).trim()
|
1622
|
+
continue
|
1623
|
+
}
|
1624
|
+
if (line.startsWith("event: ")) {
|
1625
|
+
event = line.substring(6).trim()
|
1626
|
+
continue
|
1627
|
+
}
|
1628
|
+
if (line.startsWith("data: ")) {
|
1629
|
+
data = line.substring(5).trim()
|
1630
|
+
continue
|
1631
|
+
}
|
1632
|
+
}
|
1633
|
+
return __SseEvent(id, event, data)
|
1634
|
+
}
|
1635
|
+
|
1636
|
+
private class __SseEvent(val id: String? = null, val event: String? = null, val data: String)
|
1637
|
+
|
1638
|
+
private class __SseEventParsingResult(val events: List<__SseEvent>, val leftover: String)
|
1639
|
+
|
1640
|
+
private fun __parseSseEvents(input: String): __SseEventParsingResult {
|
1641
|
+
val inputs = input.split("\\n\\n").toMutableList()
|
1642
|
+
if (inputs.isEmpty()) {
|
1643
|
+
return __SseEventParsingResult(
|
1644
|
+
events = listOf(),
|
1645
|
+
leftover = "",
|
1646
|
+
)
|
1647
|
+
}
|
1648
|
+
if (inputs.size == 1) {
|
1649
|
+
return __SseEventParsingResult(
|
1650
|
+
events = listOf(),
|
1651
|
+
leftover = inputs.last(),
|
1652
|
+
)
|
1653
|
+
}
|
1654
|
+
val leftover = inputs.last()
|
1655
|
+
inputs.removeLast()
|
1656
|
+
val events = mutableListOf<__SseEvent>()
|
1657
|
+
for (item in inputs) {
|
1658
|
+
if (item.contains("data: ")) {
|
1659
|
+
events.add(__parseSseEvent(item))
|
1660
|
+
}
|
1661
|
+
}
|
1662
|
+
return __SseEventParsingResult(
|
1663
|
+
events = events,
|
1664
|
+
leftover = leftover,
|
1665
|
+
)
|
1666
|
+
}
|
1667
|
+
|
1668
|
+
private suspend fun __handleSseRequest(
|
1669
|
+
scope: CoroutineScope,
|
1670
|
+
httpClient: HttpClient,
|
1671
|
+
url: String,
|
1672
|
+
method: HttpMethod,
|
1673
|
+
params: ${clientName}Model?,
|
1674
|
+
headers: headersFn,
|
1675
|
+
backoffTime: Long,
|
1676
|
+
maxBackoffTime: Long,
|
1677
|
+
lastEventId: String?,
|
1678
|
+
onOpen: ((response: HttpResponse) -> Unit) = {},
|
1679
|
+
onClose: (() -> Unit) = {},
|
1680
|
+
onError: ((error: ${clientName}Error) -> Unit) = {},
|
1681
|
+
onData: ((data: String) -> Unit) = {},
|
1682
|
+
onConnectionError: ((error: ${clientName}Error) -> Unit) = {},
|
1683
|
+
bufferCapacity: Int,
|
1684
|
+
) {
|
1685
|
+
val finalHeaders = headers?.invoke() ?: mutableMapOf()
|
1686
|
+
var lastId = lastEventId
|
1687
|
+
// exponential backoff maxing out at 32 seconds
|
1688
|
+
if (backoffTime > 0) {
|
1689
|
+
withContext(scope.coroutineContext) {
|
1690
|
+
Thread.sleep(backoffTime)
|
1691
|
+
}
|
1692
|
+
}
|
1693
|
+
var newBackoffTime =
|
1694
|
+
if (backoffTime == 0L) 2L else if (backoffTime * 2L >= maxBackoffTime) maxBackoffTime else backoffTime * 2L
|
1695
|
+
if (lastId != null) {
|
1696
|
+
finalHeaders["Last-Event-ID"] = lastId.toString()
|
1697
|
+
}
|
1698
|
+
val request = __prepareRequest(
|
1699
|
+
client = httpClient,
|
1700
|
+
url = url,
|
1701
|
+
method = method,
|
1702
|
+
params = params,
|
1703
|
+
headers = finalHeaders,
|
1704
|
+
)
|
1705
|
+
try {
|
1706
|
+
request.execute { httpResponse ->
|
1707
|
+
try {
|
1708
|
+
onOpen(httpResponse)
|
1709
|
+
} catch (e: CancellationException) {
|
1710
|
+
onClose()
|
1711
|
+
return@execute
|
1712
|
+
}
|
1713
|
+
if (httpResponse.status.value !in 200..299) {
|
1714
|
+
try {
|
1715
|
+
if (httpResponse.headers["Content-Type"] == "application/json") {
|
1716
|
+
onConnectionError(
|
1717
|
+
${clientName}Error.fromJson(httpResponse.bodyAsText())
|
1718
|
+
)
|
1719
|
+
} else {
|
1720
|
+
onConnectionError(
|
1721
|
+
${clientName}Error(
|
1722
|
+
code = httpResponse.status.value,
|
1723
|
+
errorMessage = httpResponse.status.description,
|
1724
|
+
data = JsonPrimitive(httpResponse.bodyAsText()),
|
1725
|
+
stack = null,
|
1726
|
+
)
|
1727
|
+
)
|
1728
|
+
}
|
1729
|
+
} catch (e: CancellationException) {
|
1730
|
+
onClose()
|
1731
|
+
return@execute
|
1732
|
+
}
|
1733
|
+
__handleSseRequest(
|
1734
|
+
scope = scope,
|
1735
|
+
httpClient = httpClient,
|
1736
|
+
url = url,
|
1737
|
+
method = method,
|
1738
|
+
params = params,
|
1739
|
+
headers = headers,
|
1740
|
+
backoffTime = newBackoffTime,
|
1741
|
+
maxBackoffTime = maxBackoffTime,
|
1742
|
+
lastEventId = lastId,
|
1743
|
+
bufferCapacity = bufferCapacity,
|
1744
|
+
onOpen = onOpen,
|
1745
|
+
onClose = onClose,
|
1746
|
+
onError = onError,
|
1747
|
+
onData = onData,
|
1748
|
+
onConnectionError = onConnectionError,
|
1749
|
+
)
|
1750
|
+
return@execute
|
1751
|
+
}
|
1752
|
+
if (httpResponse.headers["Content-Type"] != "text/event-stream") {
|
1753
|
+
try {
|
1754
|
+
onConnectionError(
|
1755
|
+
${clientName}Error(
|
1756
|
+
code = 0,
|
1757
|
+
errorMessage = "Expected server to return Content-Type \\"text/event-stream\\". Got \\"\${httpResponse.headers["Content-Type"]}\\"",
|
1758
|
+
data = JsonPrimitive(httpResponse.bodyAsText()),
|
1759
|
+
stack = null,
|
1760
|
+
)
|
1761
|
+
)
|
1762
|
+
} catch (e: CancellationException) {
|
1763
|
+
return@execute
|
1764
|
+
}
|
1765
|
+
__handleSseRequest(
|
1766
|
+
scope = scope,
|
1767
|
+
httpClient = httpClient,
|
1768
|
+
url = url,
|
1769
|
+
method = method,
|
1770
|
+
params = params,
|
1771
|
+
headers = headers,
|
1772
|
+
backoffTime = newBackoffTime,
|
1773
|
+
maxBackoffTime = maxBackoffTime,
|
1774
|
+
lastEventId = lastId,
|
1775
|
+
bufferCapacity = bufferCapacity,
|
1776
|
+
onOpen = onOpen,
|
1777
|
+
onClose = onClose,
|
1778
|
+
onError = onError,
|
1779
|
+
onData = onData,
|
1780
|
+
onConnectionError = onConnectionError,
|
1781
|
+
)
|
1782
|
+
return@execute
|
1783
|
+
}
|
1784
|
+
newBackoffTime = 0
|
1785
|
+
val channel: ByteReadChannel = httpResponse.bodyAsChannel()
|
1786
|
+
var pendingData = ""
|
1787
|
+
while (!channel.isClosedForRead) {
|
1788
|
+
val buffer = ByteBuffer.allocateDirect(bufferCapacity)
|
1789
|
+
val read = channel.readAvailable(buffer)
|
1790
|
+
if (read == -1) break
|
1791
|
+
buffer.flip()
|
1792
|
+
val input = Charsets.UTF_8.decode(buffer).toString()
|
1793
|
+
val parsedResult = __parseSseEvents("\${pendingData}\${input}")
|
1794
|
+
pendingData = parsedResult.leftover
|
1795
|
+
for (event in parsedResult.events) {
|
1796
|
+
if (event.id != null) {
|
1797
|
+
lastId = event.id
|
1798
|
+
}
|
1799
|
+
when (event.event) {
|
1800
|
+
"message" -> {
|
1801
|
+
try {
|
1802
|
+
onData(event.data)
|
1803
|
+
} catch (e: CancellationException) {
|
1804
|
+
onClose()
|
1805
|
+
return@execute
|
1806
|
+
}
|
1807
|
+
}
|
1808
|
+
|
1809
|
+
"done" -> {
|
1810
|
+
onClose()
|
1811
|
+
return@execute
|
1812
|
+
}
|
1813
|
+
|
1814
|
+
"error" -> {
|
1815
|
+
val error = ${clientName}Error.fromJson(event.data)
|
1816
|
+
try {
|
1817
|
+
onError(error)
|
1818
|
+
} catch (e: CancellationException) {
|
1819
|
+
onClose()
|
1820
|
+
return@execute
|
1821
|
+
}
|
1822
|
+
}
|
1823
|
+
|
1824
|
+
else -> {}
|
1825
|
+
}
|
1826
|
+
}
|
1827
|
+
}
|
1828
|
+
__handleSseRequest(
|
1829
|
+
scope = scope,
|
1830
|
+
httpClient = httpClient,
|
1831
|
+
url = url,
|
1832
|
+
method = method,
|
1833
|
+
params = params,
|
1834
|
+
headers = headers,
|
1835
|
+
backoffTime = newBackoffTime,
|
1836
|
+
maxBackoffTime = maxBackoffTime,
|
1837
|
+
lastEventId = lastId,
|
1838
|
+
bufferCapacity = bufferCapacity,
|
1839
|
+
onOpen = onOpen,
|
1840
|
+
onClose = onClose,
|
1841
|
+
onError = onError,
|
1842
|
+
onData = onData,
|
1843
|
+
onConnectionError = onConnectionError,
|
1844
|
+
)
|
1845
|
+
}
|
1846
|
+
} catch (e: java.net.ConnectException) {
|
1847
|
+
onConnectionError(
|
1848
|
+
${clientName}Error(
|
1849
|
+
code = 503,
|
1850
|
+
errorMessage = if (e.message != null) e.message!! else "Error connecting to $url",
|
1851
|
+
data = JsonPrimitive(e.toString()),
|
1852
|
+
stack = e.stackTraceToString().split("\\n"),
|
1853
|
+
)
|
1854
|
+
)
|
1855
|
+
__handleSseRequest(
|
1856
|
+
scope = scope,
|
1857
|
+
httpClient = httpClient,
|
1858
|
+
url = url,
|
1859
|
+
method = method,
|
1860
|
+
params = params,
|
1861
|
+
headers = headers,
|
1862
|
+
backoffTime = newBackoffTime,
|
1863
|
+
maxBackoffTime = maxBackoffTime,
|
1864
|
+
lastEventId = lastId,
|
1865
|
+
bufferCapacity = bufferCapacity,
|
1866
|
+
onOpen = onOpen,
|
1867
|
+
onClose = onClose,
|
1868
|
+
onError = onError,
|
1869
|
+
onData = onData,
|
1870
|
+
onConnectionError = onConnectionError,
|
1871
|
+
)
|
1872
|
+
return
|
1873
|
+
} catch (e: Exception) {
|
1874
|
+
__handleSseRequest(
|
1875
|
+
scope = scope,
|
1876
|
+
httpClient = httpClient,
|
1877
|
+
url = url,
|
1878
|
+
method = method,
|
1879
|
+
params = params,
|
1880
|
+
headers = headers,
|
1881
|
+
backoffTime = newBackoffTime,
|
1882
|
+
maxBackoffTime = maxBackoffTime,
|
1883
|
+
lastEventId = lastId,
|
1884
|
+
bufferCapacity = bufferCapacity,
|
1885
|
+
onOpen = onOpen,
|
1886
|
+
onClose = onClose,
|
1887
|
+
onError = onError,
|
1888
|
+
onData = onData,
|
1889
|
+
onConnectionError = onConnectionError,
|
1890
|
+
)
|
1891
|
+
}
|
1892
|
+
}`;
|
1893
|
+
}
|
1894
|
+
function getHeader(options) {
|
1895
|
+
return `@file:Suppress(
|
1896
|
+
"FunctionName", "LocalVariableName", "UNNECESSARY_NOT_NULL_ASSERTION", "ClassName", "NAME_SHADOWING",
|
1897
|
+
"USELESS_IS_CHECK", "unused", "RemoveRedundantQualifierName", "CanBeParameter", "RedundantUnitReturnType",
|
1898
|
+
"RedundantExplicitType"
|
1899
|
+
)
|
1900
|
+
|
1901
|
+
import io.ktor.client.*
|
1902
|
+
import io.ktor.client.plugins.*
|
1903
|
+
import io.ktor.client.request.*
|
1904
|
+
import io.ktor.client.statement.*
|
1905
|
+
import io.ktor.http.*
|
1906
|
+
import io.ktor.utils.io.*
|
1907
|
+
import kotlinx.coroutines.CoroutineScope
|
1908
|
+
import kotlinx.coroutines.Job
|
1909
|
+
import kotlinx.coroutines.launch
|
1910
|
+
import kotlinx.coroutines.withContext
|
1911
|
+
import kotlinx.serialization.encodeToString
|
1912
|
+
import kotlinx.serialization.json.*
|
1913
|
+
import java.nio.ByteBuffer
|
1914
|
+
import java.time.Instant
|
1915
|
+
import java.time.ZoneId
|
1916
|
+
import java.time.ZoneOffset
|
1917
|
+
import java.time.format.DateTimeFormatter
|
1918
|
+
|
1919
|
+
private const val generatedClientVersion = "${options.clientVersion}"
|
1920
|
+
private val timestampFormatter =
|
1921
|
+
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
1922
|
+
.withZone(ZoneId.ofOffset("GMT", ZoneOffset.UTC))
|
1923
|
+
private val JsonInstance = Json {
|
1924
|
+
encodeDefaults = true
|
1925
|
+
ignoreUnknownKeys = true
|
1926
|
+
}
|
1927
|
+
private typealias headersFn = (() -> MutableMap<String, String>?)?`;
|
1928
|
+
}
|
1929
|
+
|
1930
|
+
exports.kotlinClientFromAppDefinition = kotlinClientFromAppDefinition;
|
1931
|
+
exports.kotlinClientGenerator = kotlinClientGenerator;
|
1932
|
+
exports.kotlinTypeFromSchema = kotlinTypeFromSchema;
|