@sdk-it/dart 0.15.0 → 0.17.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/dist/index.js CHANGED
@@ -1,48 +1,1129 @@
1
1
  // packages/dart/src/lib/generate.ts
2
- import { execSync } from "node:child_process";
2
+ import { parse as partContentType } from "fast-content-type-parse";
3
+ import { merge as merge2 } from "lodash-es";
4
+ import assert2 from "node:assert";
5
+ import { writeFile } from "node:fs/promises";
3
6
  import { join } from "node:path";
4
- import "npm-run-path";
5
- import { camelcase, pascalcase, spinalcase } from "stringcase";
6
- import { forEachOperation, writeFiles } from "@sdk-it/core";
7
+ import { camelcase as camelcase3 } from "stringcase";
8
+ import yaml from "yaml";
9
+ import {
10
+ followRef as followRef2,
11
+ getFolderExportsV2,
12
+ isEmpty as isEmpty2,
13
+ isRef as isRef2,
14
+ notRef as notRef2,
15
+ pascalcase as pascalcase2,
16
+ snakecase as snakecase2,
17
+ writeFiles
18
+ } from "@sdk-it/core";
7
19
 
8
- // packages/dart/src/lib/interceptors.txt
20
+ // packages/spec/dist/lib/loaders/local-loader.js
21
+ import { parse } from "yaml";
22
+
23
+ // packages/spec/dist/lib/loaders/remote-loader.js
24
+ import { parse as parse2 } from "yaml";
25
+
26
+ // packages/spec/dist/lib/operation.js
27
+ import { camelcase } from "stringcase";
28
+ var defaults = {
29
+ operationId: (operation, path, method) => {
30
+ if (operation.operationId) {
31
+ return camelcase(operation.operationId);
32
+ }
33
+ const metadata = operation["x-oaiMeta"];
34
+ if (metadata && metadata.name) {
35
+ return camelcase(metadata.name);
36
+ }
37
+ return camelcase(
38
+ [method, ...path.replace(/[\\/\\{\\}]/g, " ").split(" ")].filter(Boolean).join(" ").trim()
39
+ );
40
+ },
41
+ tag: (operation, path) => {
42
+ return operation.tags?.[0] || determineGenericTag(path, operation);
43
+ }
44
+ };
45
+ function forEachOperation(config, callback) {
46
+ const result = [];
47
+ for (const [path, pathItem] of Object.entries(config.spec.paths ?? {})) {
48
+ const { parameters = [], ...methods } = pathItem;
49
+ const fixedPath = path.replace(/:([^/]+)/g, "{$1}");
50
+ for (const [method, operation] of Object.entries(methods)) {
51
+ const formatOperationId = config.operationId ?? defaults.operationId;
52
+ const formatTag = config.tag ?? defaults.tag;
53
+ const operationName = formatOperationId(operation, fixedPath, method);
54
+ const operationTag = formatTag(operation, fixedPath);
55
+ const metadata = operation["x-oaiMeta"] ?? {};
56
+ result.push(
57
+ callback(
58
+ {
59
+ name: metadata.name,
60
+ method,
61
+ path: fixedPath,
62
+ groupName: operationTag,
63
+ tag: operationTag
64
+ },
65
+ {
66
+ ...operation,
67
+ parameters: [...parameters, ...operation.parameters ?? []],
68
+ operationId: operationName
69
+ }
70
+ )
71
+ );
72
+ }
73
+ }
74
+ return result;
75
+ }
76
+ var reservedKeywords = /* @__PURE__ */ new Set([
77
+ "abstract",
78
+ "arguments",
79
+ "await",
80
+ "boolean",
81
+ "break",
82
+ "byte",
83
+ "case",
84
+ "catch",
85
+ "char",
86
+ "class",
87
+ "const",
88
+ "continue",
89
+ "debugger",
90
+ "default",
91
+ "delete",
92
+ "do",
93
+ "double",
94
+ "else",
95
+ "enum",
96
+ "eval",
97
+ "export",
98
+ "extends",
99
+ "false",
100
+ "final",
101
+ "finally",
102
+ "float",
103
+ "for",
104
+ "function",
105
+ "goto",
106
+ "if",
107
+ "implements",
108
+ "import",
109
+ "in",
110
+ "instanceof",
111
+ "int",
112
+ "interface",
113
+ "let",
114
+ "long",
115
+ "native",
116
+ "new",
117
+ "null",
118
+ "package",
119
+ "private",
120
+ "protected",
121
+ "public",
122
+ "return",
123
+ "short",
124
+ "static",
125
+ "super",
126
+ "switch",
127
+ "synchronized",
128
+ "this",
129
+ "throw",
130
+ "throws",
131
+ "transient",
132
+ "true",
133
+ "try",
134
+ "typeof",
135
+ "var",
136
+ "void",
137
+ "volatile",
138
+ "while",
139
+ "with",
140
+ "yield",
141
+ // Potentially problematic identifiers / Common Verbs used as tags
142
+ "object",
143
+ "string",
144
+ "number",
145
+ "any",
146
+ "unknown",
147
+ "never",
148
+ "get",
149
+ "list",
150
+ "create",
151
+ "update",
152
+ "delete",
153
+ "post",
154
+ "put",
155
+ "patch",
156
+ "do",
157
+ "send",
158
+ "add",
159
+ "remove",
160
+ "set",
161
+ "find",
162
+ "search",
163
+ "check",
164
+ "make"
165
+ // Added make, check
166
+ ]);
167
+ function sanitizeTag(camelCasedTag) {
168
+ if (/^\d/.test(camelCasedTag)) {
169
+ return `_${camelCasedTag}`;
170
+ }
171
+ return reservedKeywords.has(camelCasedTag) ? `${camelCasedTag}_` : camelCasedTag;
172
+ }
173
+ function determineGenericTag(pathString, operation) {
174
+ const operationId = operation.operationId || "";
175
+ const VERSION_REGEX = /^[vV]\d+$/;
176
+ const commonVerbs = /* @__PURE__ */ new Set([
177
+ // Verbs to potentially strip from operationId prefix
178
+ "get",
179
+ "list",
180
+ "create",
181
+ "update",
182
+ "delete",
183
+ "post",
184
+ "put",
185
+ "patch",
186
+ "do",
187
+ "send",
188
+ "add",
189
+ "remove",
190
+ "set",
191
+ "find",
192
+ "search",
193
+ "check",
194
+ "make"
195
+ // Added make
196
+ ]);
197
+ const segments = pathString.split("/").filter(Boolean);
198
+ const potentialCandidates = segments.filter(
199
+ (segment) => segment && !segment.startsWith("{") && !segment.endsWith("}") && !VERSION_REGEX.test(segment)
200
+ );
201
+ for (let i = potentialCandidates.length - 1; i >= 0; i--) {
202
+ const segment = potentialCandidates[i];
203
+ if (!segment.startsWith("@")) {
204
+ return sanitizeTag(camelcase(segment));
205
+ }
206
+ }
207
+ const canFallbackToPathSegment = potentialCandidates.length > 0;
208
+ if (operationId) {
209
+ const lowerOpId = operationId.toLowerCase();
210
+ const parts = operationId.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").replace(/([a-zA-Z])(\d)/g, "$1_$2").replace(/(\d)([a-zA-Z])/g, "$1_$2").toLowerCase().split(/[_-\s]+/);
211
+ const validParts = parts.filter(Boolean);
212
+ if (commonVerbs.has(lowerOpId) && validParts.length === 1 && canFallbackToPathSegment) {
213
+ } else if (validParts.length > 0) {
214
+ const firstPart = validParts[0];
215
+ const isFirstPartVerb = commonVerbs.has(firstPart);
216
+ if (isFirstPartVerb && validParts.length > 1) {
217
+ const verbPrefixLength = firstPart.length;
218
+ let nextPartStartIndex = -1;
219
+ if (operationId.length > verbPrefixLength) {
220
+ const charAfterPrefix = operationId[verbPrefixLength];
221
+ if (charAfterPrefix >= "A" && charAfterPrefix <= "Z") {
222
+ nextPartStartIndex = verbPrefixLength;
223
+ } else if (charAfterPrefix >= "0" && charAfterPrefix <= "9") {
224
+ nextPartStartIndex = verbPrefixLength;
225
+ } else if (["_", "-"].includes(charAfterPrefix)) {
226
+ nextPartStartIndex = verbPrefixLength + 1;
227
+ } else {
228
+ const match = operationId.substring(verbPrefixLength).match(/[A-Z0-9]/);
229
+ if (match && match.index !== void 0) {
230
+ nextPartStartIndex = verbPrefixLength + match.index;
231
+ }
232
+ if (nextPartStartIndex === -1 && operationId.length > verbPrefixLength) {
233
+ nextPartStartIndex = verbPrefixLength;
234
+ }
235
+ }
236
+ }
237
+ if (nextPartStartIndex !== -1 && nextPartStartIndex < operationId.length) {
238
+ const remainingOriginalSubstring = operationId.substring(nextPartStartIndex);
239
+ const potentialTag = camelcase(remainingOriginalSubstring);
240
+ if (potentialTag) {
241
+ return sanitizeTag(potentialTag);
242
+ }
243
+ }
244
+ const potentialTagJoined = camelcase(validParts.slice(1).join("_"));
245
+ if (potentialTagJoined) {
246
+ return sanitizeTag(potentialTagJoined);
247
+ }
248
+ }
249
+ const potentialTagFull = camelcase(operationId);
250
+ if (potentialTagFull) {
251
+ const isResultSingleVerb = validParts.length === 1 && isFirstPartVerb;
252
+ if (!(isResultSingleVerb && canFallbackToPathSegment)) {
253
+ if (potentialTagFull.length > 0) {
254
+ return sanitizeTag(potentialTagFull);
255
+ }
256
+ }
257
+ }
258
+ const firstPartCamel = camelcase(firstPart);
259
+ if (firstPartCamel) {
260
+ const isFirstPartCamelVerb = commonVerbs.has(firstPartCamel);
261
+ if (!isFirstPartCamelVerb || validParts.length === 1 || !canFallbackToPathSegment) {
262
+ return sanitizeTag(firstPartCamel);
263
+ }
264
+ }
265
+ if (isFirstPartVerb && validParts.length > 1 && validParts[1] && canFallbackToPathSegment) {
266
+ const secondPartCamel = camelcase(validParts[1]);
267
+ if (secondPartCamel) {
268
+ return sanitizeTag(secondPartCamel);
269
+ }
270
+ }
271
+ }
272
+ }
273
+ if (potentialCandidates.length > 0) {
274
+ let firstCandidate = potentialCandidates[0];
275
+ if (firstCandidate.startsWith("@")) {
276
+ firstCandidate = firstCandidate.substring(1);
277
+ }
278
+ if (firstCandidate) {
279
+ return sanitizeTag(camelcase(firstCandidate));
280
+ }
281
+ }
282
+ console.warn(
283
+ `Could not determine a suitable tag for path: ${pathString}, operationId: ${operationId}. Using 'unknown'.`
284
+ );
285
+ return "unknown";
286
+ }
287
+ function parseJsonContentType(contentType) {
288
+ if (!contentType) {
289
+ return null;
290
+ }
291
+ let mainType = contentType.trim();
292
+ const semicolonIndex = mainType.indexOf(";");
293
+ if (semicolonIndex !== -1) {
294
+ mainType = mainType.substring(0, semicolonIndex).trim();
295
+ }
296
+ mainType = mainType.toLowerCase();
297
+ if (mainType.endsWith("/json")) {
298
+ return mainType.split("/")[1];
299
+ } else if (mainType.endsWith("+json")) {
300
+ return mainType.split("+")[1];
301
+ }
302
+ return null;
303
+ }
304
+ function isStreamingContentType(contentType) {
305
+ return contentType === "application/octet-stream";
306
+ }
307
+ function isSuccessStatusCode(statusCode) {
308
+ statusCode = Number(statusCode);
309
+ return statusCode >= 200 && statusCode < 300;
310
+ }
311
+
312
+ // packages/dart/src/lib/dart-emitter.ts
313
+ import { merge } from "lodash-es";
314
+ import assert from "node:assert";
315
+ import { camelcase as camelcase2, snakecase } from "stringcase";
316
+ import {
317
+ cleanRef,
318
+ followRef,
319
+ isEmpty,
320
+ isRef,
321
+ notRef,
322
+ parseRef,
323
+ pascalcase
324
+ } from "@sdk-it/core";
325
+ var formatName = (it) => {
326
+ const startsWithDigitPattern = /^-?\d/;
327
+ if (typeof it === "number") {
328
+ if (Math.sign(it) === -1) {
329
+ return `$_${Math.abs(it)}`;
330
+ }
331
+ return `$${it}`;
332
+ }
333
+ if (it === "default") {
334
+ return "$default";
335
+ }
336
+ if (typeof it === "string") {
337
+ if (startsWithDigitPattern.test(it)) {
338
+ if (typeof it === "number") {
339
+ if (Math.sign(it) === -1) {
340
+ return `$_${Math.abs(it)}`;
341
+ }
342
+ return `$${it}`;
343
+ }
344
+ }
345
+ let nameToFormat = it;
346
+ if (nameToFormat.startsWith("[")) {
347
+ nameToFormat = nameToFormat.slice(1);
348
+ }
349
+ if (nameToFormat.endsWith("]")) {
350
+ nameToFormat = nameToFormat.slice(0, -1);
351
+ }
352
+ return snakecase(nameToFormat);
353
+ }
354
+ return snakecase(String(it));
355
+ };
356
+ var DartSerializer = class {
357
+ #spec;
358
+ #emit;
359
+ constructor(spec, emit) {
360
+ this.#spec = spec;
361
+ this.#emit = emit;
362
+ }
363
+ #getRefUsage(schemaName, list = []) {
364
+ this.#spec.components ??= {};
365
+ this.#spec.components.schemas ??= {};
366
+ this.#spec.components.responses ??= {};
367
+ const checkSchema = (schema) => {
368
+ if (isRef(schema)) {
369
+ const { model } = parseRef(schema.$ref);
370
+ return model === schemaName;
371
+ }
372
+ if (schema.oneOf && Array.isArray(schema.oneOf)) {
373
+ return schema.oneOf.some(
374
+ (subSchema) => checkSchema(subSchema)
375
+ );
376
+ }
377
+ if (schema.type === "array" && schema.items && notRef(schema.items) && schema.items.oneOf) {
378
+ return checkSchema(schema.items);
379
+ }
380
+ return false;
381
+ };
382
+ for (const [key, value] of Object.entries(this.#spec.components.schemas)) {
383
+ if (checkSchema(value)) {
384
+ list.push(key);
385
+ }
386
+ }
387
+ return list;
388
+ }
389
+ #object(className, schema, context) {
390
+ if (schema.additionalProperties) {
391
+ this.#emit(className, `typedef ${className} = Map<String, dynamic>;`);
392
+ return {
393
+ content: "",
394
+ use: "Map<String, dynamic>",
395
+ encode: "input",
396
+ toJson: `this.${camelcase2(context.name)}`,
397
+ fromJson: `json['${camelcase2(context.name)}']`,
398
+ matches: `json['${camelcase2(context.name)}'] is Map<String, dynamic>`
399
+ };
400
+ }
401
+ if (isEmpty(schema.properties)) {
402
+ if (context.noEmit !== true) {
403
+ this.#emit(
404
+ className,
405
+ `class ${className} {
406
+ const ${className}(); // Add const constructor
407
+
408
+ factory ${className}.fromJson(Map<String, dynamic> json) {
409
+ return const ${className}();
410
+ }
411
+
412
+ Map<String, dynamic> toJson() => {};
413
+
414
+ /// Determines if a given map can be parsed into an instance of this class.
415
+ /// Returns true for any map since this class has no properties.
416
+ static bool matches(Map<String, dynamic> json) {
417
+ return true; // Any map is fine for an empty object
418
+ }
419
+ }`
420
+ );
421
+ }
422
+ return {
423
+ content: "",
424
+ encode: "input.toJson()",
425
+ use: className,
426
+ toJson: `${this.#safe(context.name, context.required)}`,
427
+ fromJson: `${className}.fromJson(json['${context.name}'])`,
428
+ matches: `${className}.matches(json['${context.name}'])`
429
+ };
430
+ }
431
+ const props = [];
432
+ const toJsonProperties = [];
433
+ const constructorParams = [];
434
+ const fromJsonParams = [];
435
+ const matches = [];
436
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
437
+ const propName = key.replace("[]", "");
438
+ const required = (schema.required ?? []).includes(key);
439
+ const typeStr = this.handle(className, propSchema, required, {
440
+ name: propName,
441
+ required,
442
+ propName: [className, propName].filter(Boolean).join("_")
443
+ });
444
+ const nullable2 = typeStr.nullable || !required;
445
+ const nullableSuffix = nullable2 ? "?" : "";
446
+ props.push(
447
+ `final ${typeStr.use}${nullableSuffix} ${camelcase2(propName)};`
448
+ );
449
+ fromJsonParams.push(`${camelcase2(propName)}: ${typeStr.fromJson}`);
450
+ toJsonProperties.push(`'${propName}': ${typeStr.toJson}`);
451
+ constructorParams.push(
452
+ `${required ? "required " : ""}this.${camelcase2(propName)},`
453
+ );
454
+ if (required) {
455
+ matches.push(`(
456
+ json.containsKey('${camelcase2(propName)}')
457
+ ? ${nullable2 ? `json['${propName}'] == null` : `json['${propName}'] != null`} && ${typeStr.matches}
458
+ : false)`);
459
+ } else {
460
+ matches.push(`(
461
+ json.containsKey('${camelcase2(propName)}')
462
+ ? ${nullable2 ? `json['${propName}'] == null` : `json['${propName}'] != null`} || ${typeStr.matches}
463
+ : true)`);
464
+ }
465
+ }
466
+ const { mixins, withMixins } = this.#mixinise(className, context);
467
+ const content = `class ${className} ${withMixins} {
468
+ ${props.join("\n")}
469
+ ${!mixins.length ? "const" : ""} ${className}({
470
+ ${constructorParams.join("\n")}})${mixins.length > 1 ? "" : `:super()`};
471
+ factory ${className}.fromJson(Map<String, dynamic> json) {
472
+ return ${className}(
473
+ ${fromJsonParams.join(",\n")});
474
+ }
475
+ Map<String, dynamic> toJson() => {
476
+ ${toJsonProperties.join(",\n")}
477
+ };
478
+ static bool matches(Map<String, dynamic> json) {
479
+ return ${matches.join(" && ")};
480
+ }
481
+ }`;
482
+ if (context.noEmit !== true) {
483
+ this.#emit(className, content);
484
+ }
485
+ const nullable = !context.required || context.nullable === true;
486
+ return {
487
+ use: className,
488
+ content,
489
+ encode: "input.toJson()",
490
+ toJson: `${this.#safe(context.name, context.required)}`,
491
+ fromJson: context.name ? `${context.forJson || className}.fromJson(json['${context.name}'])` : `${context.forJson || className}.fromJson(json)`,
492
+ matches: `${className}.matches(json['${context.name}'])`
493
+ };
494
+ }
495
+ #safe(accces, required) {
496
+ return required ? `this.${camelcase2(accces)}.toJson()` : `this.${camelcase2(accces)} != null ? this.${camelcase2(accces)}!.toJson() : null`;
497
+ }
498
+ #array(className, schema, required = false, context) {
499
+ let serialized;
500
+ if (!schema.items) {
501
+ serialized = {
502
+ content: "",
503
+ use: "List<dynamic>",
504
+ toJson: "",
505
+ fromJson: `List<dynamic>.from(${context.name ? `json['${context.name}']` : `json`})})`,
506
+ matches: ""
507
+ };
508
+ } else {
509
+ const itemsType = this.handle(className, schema.items, true, context);
510
+ const fromJson = required ? context.name ? `(json['${context.name}'] as List<${itemsType.simple ? itemsType.use : "dynamic"}>)
511
+ .map((it) => ${itemsType.simple ? "it" : `${itemsType.use}.fromJson(it)`})
512
+ .toList()` : `(json as List<${itemsType.simple ? itemsType.use : "dynamic"}>)
513
+ .map((it) => ${itemsType.simple ? "it" : `${itemsType.use}.fromJson(it)`})
514
+ .toList()` : context.name ? `json['${context.name}'] != null
515
+ ? (json['${context.name}'] as List<${itemsType.simple ? itemsType.use : "dynamic"}>)
516
+ .map((it) => ${itemsType.simple ? "it" : `${itemsType.use}.fromJson(it)`})
517
+ .toList()
518
+ : null` : `json != null
519
+ ? (json as List<${itemsType.simple ? itemsType.use : "dynamic"}>)
520
+ .map((it) => ${itemsType.simple ? "it" : `${itemsType.use}.fromJson(it)`})
521
+ .toList()
522
+ : null`;
523
+ serialized = {
524
+ encode: `input.map((it) => ${itemsType.simple ? "it" : `it.toJson()`}).toList()`,
525
+ content: "",
526
+ use: `List<${itemsType.use}>`,
527
+ fromJson,
528
+ toJson: `${context.required ? `this.${camelcase2(context.name)}${itemsType.simple ? "" : ".map((it) => it.toJson()).toList()"}` : `this.${camelcase2(context.name)}!= null? this.${camelcase2(context.name)}${itemsType.simple ? "" : "!.map((it) => it.toJson()).toList()"} : null`}`,
529
+ matches: `json['${camelcase2(context.name)}'].every((it) => ${itemsType.matches})`
530
+ };
531
+ }
532
+ return serialized;
533
+ }
534
+ /**
535
+ * Convert a basic type to Dart
536
+ */
537
+ #primitive(className, type, schema, context, required = false) {
538
+ switch (type) {
539
+ case "string":
540
+ return this.#string(schema, context);
541
+ case "number":
542
+ case "integer":
543
+ return this.number(schema, context);
544
+ case "boolean":
545
+ return {
546
+ content: "",
547
+ use: "bool",
548
+ toJson: `${camelcase2(context.name)}`,
549
+ fromJson: `json['${context.name}']`,
550
+ matches: `json['${context.name}'] is bool`
551
+ };
552
+ case "object":
553
+ return this.#object(className, schema, context);
554
+ case "array":
555
+ return this.#array(className, schema, required, context);
556
+ case "null":
557
+ return {
558
+ content: "",
559
+ use: "Null",
560
+ toJson: `${camelcase2(context.name)}`,
561
+ fromJson: `json['${context.name}']`
562
+ };
563
+ default:
564
+ return {
565
+ content: "",
566
+ use: "dynamic",
567
+ toJson: `${camelcase2(context.name)}`,
568
+ fromJson: `json['${context.name}']`
569
+ };
570
+ }
571
+ }
572
+ #ref(className, $ref, required, context) {
573
+ const schemaName = cleanRef($ref).split("/").pop();
574
+ const serialized = this.handle(
575
+ schemaName,
576
+ followRef(this.#spec, $ref),
577
+ required,
578
+ {
579
+ ...context,
580
+ propName: schemaName,
581
+ noEmit: !!context.alias || !!className || !context.forceEmit
582
+ }
583
+ );
584
+ return serialized;
585
+ }
586
+ // fixme: this method should no longer be needed because the logic in it is being preprocessed before emitting begins
587
+ #allOf(className, schemas, context) {
588
+ const name = pascalcase(context.propName || className);
589
+ const refs = schemas.filter(isRef);
590
+ const nonRefs = schemas.filter(notRef);
591
+ if (nonRefs.some((it) => it.type && it.type !== "object")) {
592
+ assert(false, `allOf ${name} must be an object`);
593
+ }
594
+ const objectSchema = merge(
595
+ {},
596
+ ...nonRefs,
597
+ ...refs.map((ref) => followRef(this.#spec, ref.$ref))
598
+ );
599
+ delete objectSchema.allOf;
600
+ return this.handle(name, objectSchema, true, context);
601
+ }
602
+ #anyOf(className, schemas, context) {
603
+ if (schemas.length === 0) {
604
+ return {
605
+ content: "",
606
+ use: "dynamic",
607
+ toJson: `${camelcase2(context.name)}`,
608
+ fromJson: `json['${context.name}']`
609
+ };
610
+ }
611
+ const nullSchemaIndex = schemas.findIndex((schema) => {
612
+ if (isRef(schema)) {
613
+ const refSchema = followRef(this.#spec, schema.$ref);
614
+ return refSchema.type === "null";
615
+ }
616
+ return schema.type === "null";
617
+ });
618
+ const anyOfSchemas = schemas.slice(0);
619
+ if (nullSchemaIndex >= 0) {
620
+ anyOfSchemas.splice(nullSchemaIndex, 1);
621
+ }
622
+ return this.handle(className, anyOfSchemas[0], true, {
623
+ ...context,
624
+ nullable: nullSchemaIndex >= 0
625
+ });
626
+ }
627
+ #mixinise(name, context) {
628
+ const mixins = this.#getRefUsage(name);
629
+ if (context.mixin) {
630
+ mixins.unshift(context.mixin);
631
+ }
632
+ const withMixins = mixins.length > 1 ? ` with ${mixins.join(", ")}` : mixins.length === 1 ? `extends ${mixins[0]}` : "";
633
+ return {
634
+ withMixins,
635
+ mixins
636
+ };
637
+ }
638
+ #oneOf(className, schemas, context) {
639
+ const name = pascalcase(context.propName || className);
640
+ if (schemas.length === 0) {
641
+ return {
642
+ content: "",
643
+ use: "dynamic",
644
+ toJson: `${camelcase2(context.name)}`,
645
+ fromJson: `json['${context.name}']`
646
+ };
647
+ }
648
+ const content = [];
649
+ const patterns = [];
650
+ const objects = schemas.filter(notRef).filter((it) => it.type === "object");
651
+ for (const schema of schemas) {
652
+ if (isRef(schema)) {
653
+ const refType = this.#ref(className, schema.$ref, true, context);
654
+ patterns.push({
655
+ pattern: `case ${refType.type || "Map<String, dynamic>"} map when ${refType.use}.matches(map): return ${refType.use}.fromJson(map);`,
656
+ name: refType.use
657
+ });
658
+ } else if (schema.type === "string") {
659
+ content.push(`class ${name}Text with ${name} {
660
+ final String value;
661
+ ${name}Text(this.value);
662
+ @override
663
+ dynamic toJson() => value;
664
+ static bool matches(dynamic value) {
665
+ return value is String;
666
+ }}
667
+ `);
668
+ patterns.push({
669
+ pattern: `case String(): return ${name}Text(json);`,
670
+ name: `${name}Text`
671
+ });
672
+ } else if (schema.type === "array") {
673
+ const itemsType = this.handle(name, schema.items, true, {
674
+ ...context,
675
+ noEmit: true
676
+ });
677
+ content.push(`class ${name}List with ${name} {
678
+ final List<${itemsType.use}> value;
679
+ ${name}List(this.value);
680
+ @override
681
+ dynamic toJson() => value;
682
+ static bool matches(dynamic value) {
683
+ return value is List;
684
+ }}`);
685
+ patterns.push({
686
+ pattern: `case List(): return ${name}List(List<${itemsType.use}>.from(json));`,
687
+ name: `${name}List`
688
+ });
689
+ }
690
+ }
691
+ if (objects.length) {
692
+ const candidates = {};
693
+ for (const schema of objects) {
694
+ if (schema.additionalProperties === true) {
695
+ continue;
696
+ }
697
+ assert(
698
+ schema.properties,
699
+ `Schema ${name} has no properties which are required in oneOf in order to determine the discriminator.`
700
+ );
701
+ for (const [propName, propSchema] of Object.entries(
702
+ schema.properties
703
+ )) {
704
+ if (notRef(propSchema) && propSchema.enum && // fixme: the enum can have more than one value as long as it is not duplicated else where on the other schemas
705
+ propSchema.enum.length === 1) {
706
+ candidates[propName] ??= /* @__PURE__ */ new Set();
707
+ candidates[propName].add(String(propSchema.enum[0]));
708
+ }
709
+ }
710
+ }
711
+ let discriminatorProp;
712
+ for (const [name2, values] of Object.entries(candidates)) {
713
+ if (
714
+ // make sure we pick the prop that exists on all objects
715
+ values.size === objects.filter((it) => it.properties?.[name2]).length
716
+ ) {
717
+ discriminatorProp = name2;
718
+ break;
719
+ }
720
+ }
721
+ if (discriminatorProp) {
722
+ for (const schema of objects) {
723
+ const discriminatorValue = schema.properties[discriminatorProp].enum?.[0];
724
+ const varientName = `${name}${pascalcase(discriminatorValue)}`;
725
+ patterns.push({
726
+ pattern: `case Map<String, dynamic> map when ${varientName}.matches(json): return ${varientName}.fromJson(map);`,
727
+ name: varientName
728
+ });
729
+ const objResult = this.#object(varientName, schema, {
730
+ ...context,
731
+ noEmit: true,
732
+ mixin: name
733
+ });
734
+ content.push(objResult.content);
735
+ }
736
+ }
737
+ }
738
+ const { mixins, withMixins } = this.#mixinise(name, context);
739
+ content.unshift(`abstract ${mixins.length ? "" : "mixin"} class ${name} ${withMixins} {
740
+ dynamic toJson();
741
+ ${patterns.length ? `static ${name} fromJson(dynamic json) {
742
+ switch (json){
743
+ ${patterns.map((it) => it.pattern).join("\n")}
744
+ default:
745
+ throw ArgumentError("Invalid type for query property: \${json}");
746
+ }
747
+ }
748
+
749
+
750
+ ${patterns.length ? ` static bool matches(dynamic value) {
751
+ return ${patterns.map((it) => `value is ${it.name}`).join(" || ")};
752
+ }` : ""}
753
+
754
+ ` : ""}
755
+ }`);
756
+ this.#emit(name, content.join("\n"));
757
+ return {
758
+ content: content.join("\n"),
759
+ use: name,
760
+ toJson: `${this.#safe(context.name, context.required)}`,
761
+ fromJson: `${name}.fromJson(json['${context.name}'])`,
762
+ matches: `${name}.matches(json['${context.name}'])`
763
+ };
764
+ }
765
+ #simple(type) {
766
+ switch (type) {
767
+ case "string":
768
+ return "String";
769
+ case "number":
770
+ return "double";
771
+ case "integer":
772
+ return "int";
773
+ case "boolean":
774
+ return "bool";
775
+ default:
776
+ return "dynamic";
777
+ }
778
+ }
779
+ #enum(className, schema, context) {
780
+ const name = context.propName || className;
781
+ const values = schema.enum;
782
+ const valType = this.#simple(schema.type || "string");
783
+ const { mixins, withMixins } = this.#mixinise(className, context);
784
+ const content = `
785
+ class _EnumValue implements ${pascalcase(name)} {
786
+ final ${valType} value;
787
+ const _EnumValue(this.value);
788
+ @override
789
+ toJson() {return this.value;}
790
+ }
791
+ abstract ${mixins.length ? "" : "mixin"} class ${pascalcase(name)} ${withMixins} {
792
+ ${values.map((it) => `static const _EnumValue ${formatName(it)} = _EnumValue(${typeof it === "number" ? it : `'${it}'`});`).join("\n")}
793
+ dynamic toJson();
794
+
795
+ ${valType} get value;
796
+
797
+ static _EnumValue fromJson(${valType} value) {
798
+ switch (value) {
799
+ ${values.map(
800
+ (it) => `case ${typeof it === "number" ? it : `'${it}'`}: return ${formatName(it)};`
801
+ ).join("\n")}
802
+ default:
803
+ throw ArgumentError.value(value, "value", "No enum value with that name");
804
+ }
805
+ }
806
+
807
+ static bool matches(${valType} value) {
808
+ try {
809
+ fromJson(value);
810
+ return true;
811
+ } catch (error) {
812
+ return false;
813
+ }
814
+ }
815
+
816
+ }`;
817
+ if (context.noEmit !== true) {
818
+ this.#emit(name, content);
819
+ }
820
+ return {
821
+ type: Array.isArray(schema.type) ? this.#simple(schema.type[0]) : schema.type ? this.#simple(schema.type) : void 0,
822
+ content,
823
+ use: pascalcase(name),
824
+ toJson: `${context.required ? `this.${camelcase2(context.name)}.toJson()` : `this.${camelcase2(context.name)} != null ? this.${camelcase2(context.name)}!.toJson() : null`}`,
825
+ fromJson: `${pascalcase(name)}.fromJson(json['${context.name}'])`,
826
+ matches: `${pascalcase(name)}.matches(json['${context.name}'])`
827
+ };
828
+ }
829
+ /**
830
+ * Handle string type with formats
831
+ */
832
+ #string(schema, context) {
833
+ switch (schema.format) {
834
+ case "date-time":
835
+ case "datetime":
836
+ case "date":
837
+ return {
838
+ content: "",
839
+ use: "DateTime",
840
+ simple: true,
841
+ toJson: context.required ? `this.${camelcase2(context.name)}.toIso8601String()` : `this.${camelcase2(context.name)} != null ? this.${camelcase2(
842
+ context.name
843
+ )}!.toIso8601String() : null`,
844
+ fromJson: context.name ? `json['${context.name}'] != null ? DateTime.parse(json['${context.name}']) : null` : "json",
845
+ matches: `json['${context.name}'] is String`
846
+ };
847
+ case "binary":
848
+ case "byte":
849
+ return {
850
+ content: "",
851
+ use: "File",
852
+ toJson: `this.${camelcase2(context.name)}`,
853
+ simple: true,
854
+ fromJson: context.name ? `json['${context.name}']` : "json",
855
+ matches: `json['${context.name}'] is Uint8List`
856
+ };
857
+ default:
858
+ return {
859
+ encode: "input",
860
+ use: `String`,
861
+ content: "",
862
+ simple: true,
863
+ toJson: `this.${camelcase2(context.name)}`,
864
+ fromJson: context.name ? `json['${context.name}'] as String` : "json",
865
+ matches: `json['${context.name}'] is String`
866
+ };
867
+ }
868
+ }
869
+ /**
870
+ * Handle number/integer types with formats
871
+ */
872
+ number(schema, context) {
873
+ if (schema.type === "integer") {
874
+ return {
875
+ content: "",
876
+ use: "int",
877
+ simple: true,
878
+ toJson: `this.${camelcase2(context.name)}`,
879
+ fromJson: `json['${context.name}']`,
880
+ matches: `json['${context.name}'] is int`
881
+ };
882
+ }
883
+ if (["float", "double"].includes(schema.format)) {
884
+ return {
885
+ content: "",
886
+ use: "double",
887
+ simple: true,
888
+ toJson: `this.${camelcase2(context.name)}`,
889
+ fromJson: `json['${context.name}']`,
890
+ matches: `json['${context.name}'] is double`
891
+ };
892
+ }
893
+ return {
894
+ content: "",
895
+ use: "num",
896
+ simple: true,
897
+ toJson: `this.${camelcase2(context.name)}`,
898
+ fromJson: `json['${context.name}']`,
899
+ matches: `json['${context.name}'] is double`
900
+ };
901
+ }
902
+ #serialize(className, schema, required = true, context = {}) {
903
+ if (isRef(schema)) {
904
+ return this.#ref(className, schema.$ref, required, context);
905
+ }
906
+ if (schema.allOf && Array.isArray(schema.allOf)) {
907
+ return this.#allOf(className, schema.allOf, context);
908
+ }
909
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
910
+ return this.#anyOf(className, schema.anyOf, context);
911
+ }
912
+ if (schema.oneOf && Array.isArray(schema.oneOf)) {
913
+ return this.#oneOf(className, schema.oneOf, context);
914
+ }
915
+ if (schema.enum && Array.isArray(schema.enum)) {
916
+ return this.#enum(className, schema, context);
917
+ }
918
+ const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
919
+ let nullable = false;
920
+ if ("nullable" in schema && schema.nullable) {
921
+ nullable = true;
922
+ } else if (schema.default === null) {
923
+ nullable = true;
924
+ } else if (types.includes("null")) {
925
+ nullable = true;
926
+ }
927
+ if (!types.length) {
928
+ if ("properties" in schema) {
929
+ return this.#object(className, schema, context);
930
+ }
931
+ if ("items" in schema) {
932
+ return this.#array(className, schema, true, context);
933
+ }
934
+ return {
935
+ content: "",
936
+ use: "dynamic",
937
+ toJson: `${camelcase2(context.name)}`,
938
+ fromJson: `json['${context.name}']`
939
+ };
940
+ }
941
+ return this.#primitive(
942
+ className,
943
+ types[0],
944
+ schema,
945
+ { ...context, nullable },
946
+ required
947
+ );
948
+ }
949
+ handle(className, schema, required = true, context = {}) {
950
+ const alias = context.alias;
951
+ context.alias = void 0;
952
+ const serialized = this.#serialize(className, schema, required, {
953
+ ...context,
954
+ forJson: alias
955
+ });
956
+ if (alias) {
957
+ this.#emit(className, `typedef ${alias} = ${serialized.use};`);
958
+ return serialized;
959
+ }
960
+ return serialized;
961
+ }
962
+ };
963
+ function isObjectSchema(schema) {
964
+ return !isRef(schema) && (schema.type === "object" || !!schema.properties);
965
+ }
966
+
967
+ // packages/dart/src/lib/http/dispatcher.txt
968
+ var dispatcher_default = "import 'package:mime/mime.dart' as mime;\nimport 'dart:convert';\nimport 'dart:io';\n\nimport './interceptors.dart';\nimport 'package:http/http.dart' as http;\nimport 'package:http_parser/http_parser.dart';\n\nclass Dispatcher {\n final List<Interceptor> interceptors;\n\n Dispatcher(this.interceptors);\n\n Future<http.StreamedResponse> multipart(\n RequestConfig config,\n Map<String, dynamic> body,\n ) async {\n final modifiedConfig = interceptors.fold(\n config,\n (acc, interceptor) => interceptor.before(acc),\n );\n final request = http.MultipartRequest(\n modifiedConfig.method,\n modifiedConfig.url,\n );\n request.headers.addAll(modifiedConfig.headers);\n for (var entry in body.entries) {\n final key = entry.key;\n final value = entry.value;\n if (value is File) {\n final mimeType = mime.lookupMimeType(value.path);\n request.files.add(\n http.MultipartFile(\n key,\n value.openRead(),\n await value.length(),\n filename: value.uri.pathSegments.last,\n contentType: mimeType != null ? MediaType.parse(mimeType) : null,\n ),\n );\n } else {\n request.fields[key] = value.toString();\n }\n }\n\n return request.send();\n }\n\n Future<http.StreamedResponse> empty(RequestConfig config) {\n final modifiedConfig = interceptors.fold(\n config,\n (acc, interceptor) => interceptor.before(acc),\n );\n final request = http.Request(modifiedConfig.method, modifiedConfig.url);\n request.headers.addAll(modifiedConfig.headers);\n return request.send();\n }\n\n Future<http.StreamedResponse> json(RequestConfig config, dynamic body) {\n final modifiedConfig = interceptors.fold(\n config,\n (acc, interceptor) => interceptor.before(acc),\n );\n final request = http.Request(modifiedConfig.method, modifiedConfig.url);\n request.headers.addAll(modifiedConfig.headers);\n\n if ((body is Map || body is List)) {\n request.headers['Content-Type'] = 'application/json';\n request.body = jsonEncode(body);\n } else if (body is String) {\n request.body = body;\n } else {\n throw ArgumentError('Unsupported body type: ${body.runtimeType}');\n }\n\n return request.send();\n }\n}\n";
969
+
970
+ // packages/dart/src/lib/http/interceptors.txt
9
971
  var interceptors_default = "abstract class Interceptor {\n RequestConfig before(RequestConfig config);\n void after();\n}\n\nclass BaseUrlInterceptor extends Interceptor {\n final String Function() getBaseUrl;\n BaseUrlInterceptor(this.getBaseUrl);\n\n @override\n RequestConfig before(RequestConfig config) {\n final baseUrl = getBaseUrl();\n if (config.url.scheme.isEmpty) {\n config.url = Uri.parse(baseUrl + config.url.toString());\n }\n return config;\n }\n\n @override\n void after() {\n //\n }\n}\n\nclass RequestConfig {\n final String method;\n Uri url;\n final Map<String, String> headers;\n RequestConfig({required this.method, required this.url, required this.headers});\n}\n";
10
972
 
11
973
  // packages/dart/src/lib/generate.ts
974
+ function tuneSpec(spec, schemas, refs) {
975
+ for (const [name, schema] of Object.entries(schemas)) {
976
+ if (isRef2(schema))
977
+ continue;
978
+ if (schema.allOf && Array.isArray(schema.allOf) && schema.allOf.length) {
979
+ const schemas2 = schema.allOf;
980
+ const refs2 = schemas2.filter(isRef2);
981
+ const nonRefs = schemas2.filter(notRef2);
982
+ if (nonRefs.some((it) => it.type && it.type !== "object")) {
983
+ assert2(false, `allOf ${name} must be an object`);
984
+ }
985
+ const objectSchema = merge2(
986
+ {},
987
+ ...nonRefs,
988
+ ...refs2.map((ref) => followRef2(spec, ref.$ref))
989
+ );
990
+ delete objectSchema.allOf;
991
+ delete schema.allOf;
992
+ Object.assign(schema, objectSchema);
993
+ }
994
+ if (schema.type === "object") {
995
+ if (!isEmpty2(schema.oneOf)) {
996
+ for (const oneOfIdx in schema.oneOf) {
997
+ const oneOf = schema.oneOf[oneOfIdx];
998
+ if (isRef2(oneOf))
999
+ continue;
1000
+ if (!isEmpty2(oneOf.required) && schema.properties) {
1001
+ schema.oneOf[oneOfIdx] = schema.properties[oneOf.required[0]];
1002
+ }
1003
+ }
1004
+ delete schema.type;
1005
+ tuneSpec(spec, schemas, refs);
1006
+ continue;
1007
+ }
1008
+ schema.properties ??= {};
1009
+ for (const [propName, value] of Object.entries(schema.properties)) {
1010
+ if (isRef2(value))
1011
+ continue;
1012
+ const refName = pascalcase2(`${name} ${propName.replace("[]", "")}`);
1013
+ refs.push({ name: refName, value });
1014
+ schema.properties[propName] = {
1015
+ $ref: `#/components/schemas/${refName}`
1016
+ };
1017
+ const props = Object.fromEntries(
1018
+ Object.entries(value.properties ?? {}).map(([key, value2]) => {
1019
+ return [pascalcase2(`${refName} ${key}`), value2];
1020
+ })
1021
+ );
1022
+ tuneSpec(spec, props, refs);
1023
+ }
1024
+ } else if (schema.type === "array") {
1025
+ if (isRef2(schema.items))
1026
+ continue;
1027
+ const refName = name;
1028
+ refs.push({ name: refName, value: schema.items ?? {} });
1029
+ schema.items = {
1030
+ $ref: `#/components/schemas/${refName}`
1031
+ };
1032
+ }
1033
+ }
1034
+ }
12
1035
  async function generate(spec, settings) {
13
- const output = settings.mode === "full" ? join(settings.output, "src") : settings.output;
1036
+ const clientName = settings.name || "Client";
1037
+ const output = join(settings.output, "lib");
14
1038
  const groups = {};
1039
+ spec.components ??= {};
1040
+ spec.components.schemas ??= {};
1041
+ const inputs = {};
1042
+ const outputs = {};
15
1043
  forEachOperation({ spec }, (entry, operation) => {
16
- const [groupName] = operation.tags ?? [];
17
- const group = groups[groupName] ?? (groups[groupName] = {
1044
+ operation.responses ??= {};
1045
+ for (const status in operation.responses) {
1046
+ if (!isSuccessStatusCode(status))
1047
+ continue;
1048
+ const response2 = isRef2(operation.responses[status]) ? followRef2(spec, operation.responses[status].$ref) : operation.responses[status];
1049
+ if (response2.content && Object.keys(response2.content).length) {
1050
+ for (const [contentType, mediaType] of Object.entries(
1051
+ response2.content
1052
+ )) {
1053
+ if (parseJsonContentType(contentType)) {
1054
+ if (mediaType.schema && !isRef2(mediaType.schema)) {
1055
+ spec.components ??= {};
1056
+ spec.components.schemas ??= {};
1057
+ const outputName = pascalcase2(`${operation.operationId} output`);
1058
+ spec.components.schemas[outputName] = mediaType.schema;
1059
+ operation.responses[status].content[contentType].schema = {
1060
+ $ref: `#/components/schemas/${outputName}`
1061
+ };
1062
+ }
1063
+ }
1064
+ }
1065
+ }
1066
+ }
1067
+ console.log(`Processing ${entry.method} ${entry.path}`);
1068
+ const group = groups[entry.groupName] ?? (groups[entry.groupName] = {
18
1069
  methods: [],
19
- use: `final ${groupName} = new ${pascalcase(groupName)}();`
1070
+ use: `final ${entry.groupName} = new ${pascalcase2(entry.groupName)}();`
20
1071
  });
1072
+ const input = toInputs(spec, { entry, operation });
1073
+ Object.assign(inputs, input.inputs);
1074
+ const response = toOutput(spec, operation);
1075
+ if (response) {
1076
+ Object.assign(outputs, response.outputs);
1077
+ }
21
1078
  group.methods.push(`
22
- Future<http.Response> ${camelcase(operation.operationId)}() async {
23
- final stream = await this.dispatcher.dispatch(RequestConfig(
1079
+ Future<${response ? response.returnType : "http.StreamedResponse"}> ${camelcase3(operation.operationId)}(
1080
+ ${isEmpty2(operation.requestBody) ? "" : `${input.inputName} input`}
1081
+ ) async {
1082
+ final stream = await this.dispatcher.${input.contentType}(RequestConfig(
24
1083
  method: '${entry.method}',
25
1084
  url: Uri.parse('${entry.path}'),
26
1085
  headers: {},
27
- ));
28
- final response = await http.Response.fromStream(stream);
29
- return response;
1086
+ ), ${["json", "multipart"].includes(input.contentType) ? input.encode : ``});
1087
+ ${response ? `${response.decode};` : "return stream;"}
30
1088
  }
31
1089
  `);
32
1090
  });
1091
+ const newRefs = [];
1092
+ tuneSpec(spec, spec.components.schemas, newRefs);
1093
+ for (const ref of newRefs) {
1094
+ spec.components.schemas[ref.name] = ref.value;
1095
+ }
1096
+ await writeFile(
1097
+ join(process.cwd(), "openai.json"),
1098
+ JSON.stringify(spec, null, 2)
1099
+ );
1100
+ const models = Object.entries(spec.components.schemas).reduce((acc, [name, schema]) => {
1101
+ const serializer = new DartSerializer(spec, (name2, content) => {
1102
+ acc[`models/${snakecase2(name2)}.dart`] = `import 'dart:io';import 'dart:typed_data'; import './index.dart';
1103
+
1104
+ ${content}`;
1105
+ });
1106
+ serializer.handle(pascalcase2(name), schema);
1107
+ return acc;
1108
+ }, {});
33
1109
  const clazzez = Object.entries(groups).reduce(
34
1110
  (acc, [name, { methods }]) => {
35
1111
  return {
36
1112
  ...acc,
37
- [`api/${spinalcase(name)}.dart`]: `
38
- import 'dart:convert';
1113
+ [`api/${snakecase2(name)}.dart`]: `
1114
+ import 'dart:convert';
1115
+
39
1116
  import 'package:http/http.dart' as http;
1117
+
40
1118
  import '../interceptors.dart';
1119
+ import '../inputs/index.dart';
1120
+ import '../outputs/index.dart';
1121
+ import '../models/index.dart';
41
1122
  import '../http.dart';
42
1123
 
43
- class ${pascalcase(name)} {
1124
+ class ${pascalcase2(name)}Client {
44
1125
  final Dispatcher dispatcher;
45
- ${pascalcase(name)}(this.dispatcher);
1126
+ ${pascalcase2(name)}Client(this.dispatcher);
46
1127
  ${methods.join("\n")}
47
1128
  }
48
1129
  `
@@ -50,21 +1131,20 @@ import '../http.dart';
50
1131
  },
51
1132
  {}
52
1133
  );
53
- console.dir({ groups }, { depth: null });
54
1134
  const client = `
1135
+ ${Object.keys(groups).map((name) => `import './api/${snakecase2(name)}.dart';`).join("\n")}
55
1136
  import './interceptors.dart';
56
1137
  import './http.dart';
57
- ${Object.keys(groups).map((name) => `import './api/${spinalcase(name)}.dart';`).join("\n")}
58
1138
 
59
- class Client {
1139
+ class ${clientName} {
60
1140
  final Options options;
61
- ${Object.keys(groups).map((name) => `late final ${pascalcase(name)} ${camelcase(name)};`).join("\n")}
1141
+ ${Object.keys(groups).map((name) => `late final ${pascalcase2(name)}Client ${camelcase3(name)};`).join("\n")}
62
1142
 
63
- Client(this.options) {
1143
+ ${clientName}(this.options) {
64
1144
  final interceptors = [new BaseUrlInterceptor(() => this.options.baseUrl)];
65
-
1145
+ final dispatcher = new Dispatcher(interceptors);
66
1146
  ${Object.keys(groups).map(
67
- (name) => `this.${camelcase(name)} = new ${pascalcase(name)}(new Dispatcher(interceptors));`
1147
+ (name) => `this.${camelcase3(name)} = new ${pascalcase2(name)}Client(dispatcher);`
68
1148
  ).join("\n")}
69
1149
 
70
1150
  }
@@ -84,31 +1164,136 @@ class Options {
84
1164
 
85
1165
  `;
86
1166
  await writeFiles(output, {
87
- "index.dart": client,
1167
+ ...models,
1168
+ ...inputs,
1169
+ ...outputs
1170
+ });
1171
+ await writeFiles(output, {
1172
+ "models/index.dart": await getFolderExportsV2(join(output, "models"), {
1173
+ exportSyntax: "export",
1174
+ extensions: "dart"
1175
+ }),
1176
+ "inputs/index.dart": await getFolderExportsV2(join(output, "inputs"), {
1177
+ exportSyntax: "export",
1178
+ extensions: "dart"
1179
+ }),
1180
+ "outputs/index.dart": await getFolderExportsV2(join(output, "outputs"), {
1181
+ exportSyntax: "export",
1182
+ extensions: "dart"
1183
+ }),
88
1184
  "interceptors.dart": interceptors_default,
89
- "http.dart": `
90
- import 'interceptors.dart';
91
- import 'package:http/http.dart' as http;
92
-
93
- class Dispatcher {
94
- final List<Interceptor> interceptors;
95
-
96
- Dispatcher(this.interceptors);
1185
+ "http.dart": dispatcher_default,
1186
+ ...clazzez
1187
+ });
1188
+ await writeFiles(output, {
1189
+ "package.dart": `${await getFolderExportsV2(join(output), {
1190
+ exportSyntax: "export",
1191
+ extensions: "dart",
1192
+ ignore(dirent) {
1193
+ return dirent.isFile() && dirent.name === "package.dart";
1194
+ }
1195
+ })}${client}`
1196
+ });
1197
+ await writeFiles(settings.output, {
1198
+ "pubspec.yaml": {
1199
+ ignoreIfExists: true,
1200
+ content: yaml.stringify({
1201
+ name: settings.name ? `${snakecase2(clientName.toLowerCase())}_sdk` : "sdk",
1202
+ version: "0.0.1",
1203
+ environment: {
1204
+ sdk: "^3.7.2"
1205
+ },
1206
+ dependencies: {
1207
+ http: "^1.3.0",
1208
+ mime: "^2.0.0"
1209
+ }
1210
+ })
1211
+ }
1212
+ });
1213
+ await settings.formatCode?.({
1214
+ output
1215
+ });
1216
+ }
1217
+ function toInputs(spec, { entry, operation }) {
1218
+ const inputs = {};
1219
+ const inputName = pascalcase2(`${operation.operationId} input`);
1220
+ let contentType = "empty";
1221
+ let encode = "";
1222
+ if (!isEmpty2(operation.requestBody)) {
1223
+ const requestBody = isRef2(operation.requestBody) ? followRef2(spec, operation.requestBody.$ref) : operation.requestBody;
1224
+ for (const type in requestBody.content) {
1225
+ const ctSchema = isRef2(requestBody.content[type].schema) ? followRef2(spec, requestBody.content[type].schema.$ref) : requestBody.content[type].schema;
1226
+ if (!ctSchema) {
1227
+ console.warn(
1228
+ `Schema not found for ${type} in ${entry.method} ${entry.path}`
1229
+ );
1230
+ continue;
1231
+ }
1232
+ const serializer = new DartSerializer(spec, (name, content) => {
1233
+ inputs[join(`inputs/${name}.dart`)] = `import 'dart:io';import 'dart:typed_data';import '../models/index.dart'; import './index.dart';
97
1234
 
98
- Future<http.StreamedResponse> dispatch(RequestConfig config) {
99
- final modifedConfig = interceptors.fold(
100
- config,
101
- (acc, interceptor) => interceptor.before(acc),
102
- );
103
- final request = http.Request(modifedConfig.method, modifedConfig.url);
104
- return request.send();
1235
+ ${content}`;
1236
+ });
1237
+ const serialized = serializer.handle(inputName, ctSchema, true, {
1238
+ alias: isObjectSchema(ctSchema) ? void 0 : inputName
1239
+ });
1240
+ encode = serialized.encode;
1241
+ if (contentType) {
1242
+ console.warn(
1243
+ `${entry.method} ${entry.path} have more than one content type`
1244
+ );
1245
+ }
1246
+ const [mediaType, mediaSubType] = partContentType(type).type.split("/");
1247
+ if (mediaType === "application") {
1248
+ contentType = parseJsonContentType(type);
1249
+ } else {
1250
+ contentType = mediaType;
1251
+ }
1252
+ }
105
1253
  }
1254
+ return { inputs, inputName, contentType, encode };
106
1255
  }
107
- `,
108
- ...clazzez
109
- // 'index.dart': await getFolderExports(output, settings.useTsExtension),
110
- });
111
- execSync("dart format .", { cwd: output });
1256
+ function toOutput(spec, operation) {
1257
+ const outputName = pascalcase2(`${operation.operationId} output`);
1258
+ operation.responses ??= {};
1259
+ const outputs = {};
1260
+ for (const status in operation.responses) {
1261
+ const response = isRef2(operation.responses[status]) ? followRef2(spec, operation.responses[status].$ref) : operation.responses[status];
1262
+ for (const type in response.content) {
1263
+ const { schema } = response.content[type];
1264
+ if (!schema) {
1265
+ console.warn(
1266
+ `Schema not found for ${type} in ${operation.operationId}`
1267
+ );
1268
+ continue;
1269
+ }
1270
+ const serializer = new DartSerializer(spec, (name, content) => {
1271
+ });
1272
+ if (isStreamingContentType(type)) {
1273
+ return {
1274
+ type: "stream",
1275
+ outputName,
1276
+ outputs,
1277
+ decode: `return stream`,
1278
+ returnType: `http.StreamedResponse`
1279
+ };
1280
+ }
1281
+ if (parseJsonContentType(type)) {
1282
+ const serialized = serializer.handle(outputName, schema, true, {
1283
+ // alias: outputName,
1284
+ noEmit: true
1285
+ });
1286
+ return {
1287
+ type: "json",
1288
+ outputName,
1289
+ outputs,
1290
+ decode: `final response = await http.Response.fromStream(stream);final dynamic json = jsonDecode(response.body); return ${serialized.fromJson}`,
1291
+ returnType: serialized.use
1292
+ };
1293
+ }
1294
+ }
1295
+ }
1296
+ return null;
112
1297
  }
113
1298
  export {
114
1299
  generate