@arrirpc/codegen-kotlin 0.45.0

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