@sdk-it/dart 0.16.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,12 +1,13 @@
1
1
  // packages/dart/src/lib/generate.ts
2
+ import { parse as partContentType } from "fast-content-type-parse";
2
3
  import { merge as merge2 } from "lodash-es";
3
4
  import assert2 from "node:assert";
4
5
  import { writeFile } from "node:fs/promises";
5
6
  import { join } from "node:path";
6
- import { camelcase as camelcase2 } from "stringcase";
7
+ import { camelcase as camelcase3 } from "stringcase";
8
+ import yaml from "yaml";
7
9
  import {
8
10
  followRef as followRef2,
9
- forEachOperation,
10
11
  getFolderExportsV2,
11
12
  isEmpty as isEmpty2,
12
13
  isRef as isRef2,
@@ -16,10 +17,302 @@ import {
16
17
  writeFiles
17
18
  } from "@sdk-it/core";
18
19
 
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
+
19
312
  // packages/dart/src/lib/dart-emitter.ts
20
313
  import { merge } from "lodash-es";
21
314
  import assert from "node:assert";
22
- import { camelcase, snakecase } from "stringcase";
315
+ import { camelcase as camelcase2, snakecase } from "stringcase";
23
316
  import {
24
317
  cleanRef,
25
318
  followRef,
@@ -30,8 +323,11 @@ import {
30
323
  pascalcase
31
324
  } from "@sdk-it/core";
32
325
  var formatName = (it) => {
33
- const startsWithDigitPattern = /^\d/;
326
+ const startsWithDigitPattern = /^-?\d/;
34
327
  if (typeof it === "number") {
328
+ if (Math.sign(it) === -1) {
329
+ return `$_${Math.abs(it)}`;
330
+ }
35
331
  return `$${it}`;
36
332
  }
37
333
  if (it === "default") {
@@ -39,7 +335,12 @@ var formatName = (it) => {
39
335
  }
40
336
  if (typeof it === "string") {
41
337
  if (startsWithDigitPattern.test(it)) {
42
- return `$${it}`;
338
+ if (typeof it === "number") {
339
+ if (Math.sign(it) === -1) {
340
+ return `$_${Math.abs(it)}`;
341
+ }
342
+ return `$${it}`;
343
+ }
43
344
  }
44
345
  let nameToFormat = it;
45
346
  if (nameToFormat.startsWith("[")) {
@@ -87,12 +388,14 @@ var DartSerializer = class {
87
388
  }
88
389
  #object(className, schema, context) {
89
390
  if (schema.additionalProperties) {
391
+ this.#emit(className, `typedef ${className} = Map<String, dynamic>;`);
90
392
  return {
91
393
  content: "",
92
394
  use: "Map<String, dynamic>",
93
- toJson: `this.${camelcase(context.name)}`,
94
- fromJson: `json['${camelcase(context.name)}']`,
95
- matches: `json['${camelcase(context.name)}'] is 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>`
96
399
  };
97
400
  }
98
401
  if (isEmpty(schema.properties)) {
@@ -118,6 +421,7 @@ var DartSerializer = class {
118
421
  }
119
422
  return {
120
423
  content: "",
424
+ encode: "input.toJson()",
121
425
  use: className,
122
426
  toJson: `${this.#safe(context.name, context.required)}`,
123
427
  fromJson: `${className}.fromJson(json['${context.name}'])`,
@@ -137,24 +441,24 @@ var DartSerializer = class {
137
441
  required,
138
442
  propName: [className, propName].filter(Boolean).join("_")
139
443
  });
140
- const nullable2 = !required;
444
+ const nullable2 = typeStr.nullable || !required;
141
445
  const nullableSuffix = nullable2 ? "?" : "";
142
446
  props.push(
143
- `final ${typeStr.use}${nullableSuffix} ${camelcase(propName)};`
447
+ `final ${typeStr.use}${nullableSuffix} ${camelcase2(propName)};`
144
448
  );
145
- fromJsonParams.push(`${camelcase(propName)}: ${typeStr.fromJson}`);
449
+ fromJsonParams.push(`${camelcase2(propName)}: ${typeStr.fromJson}`);
146
450
  toJsonProperties.push(`'${propName}': ${typeStr.toJson}`);
147
451
  constructorParams.push(
148
- `${required ? "required " : ""}this.${camelcase(propName)},`
452
+ `${required ? "required " : ""}this.${camelcase2(propName)},`
149
453
  );
150
454
  if (required) {
151
455
  matches.push(`(
152
- json.containsKey('${camelcase(propName)}')
456
+ json.containsKey('${camelcase2(propName)}')
153
457
  ? ${nullable2 ? `json['${propName}'] == null` : `json['${propName}'] != null`} && ${typeStr.matches}
154
458
  : false)`);
155
459
  } else {
156
460
  matches.push(`(
157
- json.containsKey('${camelcase(propName)}')
461
+ json.containsKey('${camelcase2(propName)}')
158
462
  ? ${nullable2 ? `json['${propName}'] == null` : `json['${propName}'] != null`} || ${typeStr.matches}
159
463
  : true)`);
160
464
  }
@@ -182,38 +486,55 @@ return ${matches.join(" && ")};
182
486
  return {
183
487
  use: className,
184
488
  content,
489
+ encode: "input.toJson()",
185
490
  toJson: `${this.#safe(context.name, context.required)}`,
186
- fromJson: `${className}.fromJson(json['${context.name}'])`,
491
+ fromJson: context.name ? `${context.forJson || className}.fromJson(json['${context.name}'])` : `${context.forJson || className}.fromJson(json)`,
187
492
  matches: `${className}.matches(json['${context.name}'])`
188
493
  };
189
494
  }
190
495
  #safe(accces, required) {
191
- return required ? `this.${camelcase(accces)}.toJson()` : `this.${camelcase(accces)} != null ? this.${camelcase(accces)}!.toJson() : null`;
496
+ return required ? `this.${camelcase2(accces)}.toJson()` : `this.${camelcase2(accces)} != null ? this.${camelcase2(accces)}!.toJson() : null`;
192
497
  }
193
498
  #array(className, schema, required = false, context) {
194
- const { items } = schema;
195
- if (!items) {
196
- return {
499
+ let serialized;
500
+ if (!schema.items) {
501
+ serialized = {
197
502
  content: "",
198
503
  use: "List<dynamic>",
199
504
  toJson: "",
200
- fromJson: "",
505
+ fromJson: `List<dynamic>.from(${context.name ? `json['${context.name}']` : `json`})})`,
201
506
  matches: ""
202
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
+ };
203
531
  }
204
- const itemsType = this.handle(className, items, true, context);
205
- return {
206
- content: "",
207
- use: `List<${itemsType.use}>`,
208
- fromJson: required ? `(json['${context.name}'] as List<${itemsType.simple ? itemsType.use : "dynamic"}>).map((it) => ${itemsType.simple ? "it" : `${itemsType.use}.fromJson(it)`}).toList()` : `json['${context.name}'] != null ? (json['${context.name}'] as List).map((it) => ${itemsType.fromJson}).toList() : null`,
209
- toJson: `${context.required ? `this.${camelcase(context.name)}${itemsType.simple ? "" : ".map((it) => it.toJson()).toList()"}` : `this.${camelcase(context.name)}!= null? this.${camelcase(context.name)}${itemsType.simple ? "" : "!.map((it) => it.toJson()).toList()"} : null`}`,
210
- matches: `json['${camelcase(context.name)}'].every((it) => ${itemsType.matches})`
211
- };
532
+ return serialized;
212
533
  }
213
534
  /**
214
535
  * Convert a basic type to Dart
215
536
  */
216
- primitive(className, type, schema, context, required = false) {
537
+ #primitive(className, type, schema, context, required = false) {
217
538
  switch (type) {
218
539
  case "string":
219
540
  return this.#string(schema, context);
@@ -224,7 +545,7 @@ return ${matches.join(" && ")};
224
545
  return {
225
546
  content: "",
226
547
  use: "bool",
227
- toJson: `${camelcase(context.name)}`,
548
+ toJson: `${camelcase2(context.name)}`,
228
549
  fromJson: `json['${context.name}']`,
229
550
  matches: `json['${context.name}'] is bool`
230
551
  };
@@ -236,31 +557,31 @@ return ${matches.join(" && ")};
236
557
  return {
237
558
  content: "",
238
559
  use: "Null",
239
- toJson: `${camelcase(context.name)}`,
560
+ toJson: `${camelcase2(context.name)}`,
240
561
  fromJson: `json['${context.name}']`
241
562
  };
242
563
  default:
243
564
  return {
244
565
  content: "",
245
566
  use: "dynamic",
246
- toJson: `${camelcase(context.name)}`,
567
+ toJson: `${camelcase2(context.name)}`,
247
568
  fromJson: `json['${context.name}']`
248
569
  };
249
570
  }
250
571
  }
251
- #ref($ref, required, context) {
572
+ #ref(className, $ref, required, context) {
252
573
  const schemaName = cleanRef($ref).split("/").pop();
253
- const result = this.handle(
254
- context.alias || schemaName,
574
+ const serialized = this.handle(
575
+ schemaName,
255
576
  followRef(this.#spec, $ref),
256
577
  required,
257
578
  {
258
579
  ...context,
259
580
  propName: schemaName,
260
- noEmit: !context.forceEmit
581
+ noEmit: !!context.alias || !!className || !context.forceEmit
261
582
  }
262
583
  );
263
- return result;
584
+ return serialized;
264
585
  }
265
586
  // fixme: this method should no longer be needed because the logic in it is being preprocessed before emitting begins
266
587
  #allOf(className, schemas, context) {
@@ -278,16 +599,30 @@ return ${matches.join(" && ")};
278
599
  delete objectSchema.allOf;
279
600
  return this.handle(name, objectSchema, true, context);
280
601
  }
281
- anyOf(className, schemas, context) {
602
+ #anyOf(className, schemas, context) {
282
603
  if (schemas.length === 0) {
283
604
  return {
284
605
  content: "",
285
606
  use: "dynamic",
286
- toJson: `${camelcase(context.name)}`,
607
+ toJson: `${camelcase2(context.name)}`,
287
608
  fromJson: `json['${context.name}']`
288
609
  };
289
610
  }
290
- return this.handle(className, schemas[0], true, context);
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
+ });
291
626
  }
292
627
  #mixinise(name, context) {
293
628
  const mixins = this.#getRefUsage(name);
@@ -306,7 +641,7 @@ return ${matches.join(" && ")};
306
641
  return {
307
642
  content: "",
308
643
  use: "dynamic",
309
- toJson: `${camelcase(context.name)}`,
644
+ toJson: `${camelcase2(context.name)}`,
310
645
  fromJson: `json['${context.name}']`
311
646
  };
312
647
  }
@@ -315,7 +650,7 @@ return ${matches.join(" && ")};
315
650
  const objects = schemas.filter(notRef).filter((it) => it.type === "object");
316
651
  for (const schema of schemas) {
317
652
  if (isRef(schema)) {
318
- const refType = this.#ref(schema.$ref, true, context);
653
+ const refType = this.#ref(className, schema.$ref, true, context);
319
654
  patterns.push({
320
655
  pattern: `case ${refType.type || "Map<String, dynamic>"} map when ${refType.use}.matches(map): return ${refType.use}.fromJson(map);`,
321
656
  name: refType.use
@@ -457,7 +792,9 @@ return ${matches.join(" && ")};
457
792
  ${values.map((it) => `static const _EnumValue ${formatName(it)} = _EnumValue(${typeof it === "number" ? it : `'${it}'`});`).join("\n")}
458
793
  dynamic toJson();
459
794
 
460
- static _EnumValue fromJson(${valType} value) {
795
+ ${valType} get value;
796
+
797
+ static _EnumValue fromJson(${valType} value) {
461
798
  switch (value) {
462
799
  ${values.map(
463
800
  (it) => `case ${typeof it === "number" ? it : `'${it}'`}: return ${formatName(it)};`
@@ -484,7 +821,7 @@ return false;
484
821
  type: Array.isArray(schema.type) ? this.#simple(schema.type[0]) : schema.type ? this.#simple(schema.type) : void 0,
485
822
  content,
486
823
  use: pascalcase(name),
487
- toJson: `${context.required ? `this.${camelcase(context.name)}.toJson()` : `this.${camelcase(context.name)} != null ? this.${camelcase(context.name)}!.toJson() : null`}`,
824
+ toJson: `${context.required ? `this.${camelcase2(context.name)}.toJson()` : `this.${camelcase2(context.name)} != null ? this.${camelcase2(context.name)}!.toJson() : null`}`,
488
825
  fromJson: `${pascalcase(name)}.fromJson(json['${context.name}'])`,
489
826
  matches: `${pascalcase(name)}.matches(json['${context.name}'])`
490
827
  };
@@ -501,27 +838,30 @@ return false;
501
838
  content: "",
502
839
  use: "DateTime",
503
840
  simple: true,
504
- toJson: `this.${camelcase(context.name)}.toIso8601String()`,
505
- fromJson: `DateTime.parse(json['${context.name}'])`,
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",
506
845
  matches: `json['${context.name}'] is String`
507
846
  };
508
847
  case "binary":
509
848
  case "byte":
510
849
  return {
511
850
  content: "",
512
- use: "Uint8List",
513
- toJson: `this.${camelcase(context.name)}`,
851
+ use: "File",
852
+ toJson: `this.${camelcase2(context.name)}`,
514
853
  simple: true,
515
- fromJson: `json['${context.name}']`,
854
+ fromJson: context.name ? `json['${context.name}']` : "json",
516
855
  matches: `json['${context.name}'] is Uint8List`
517
856
  };
518
857
  default:
519
858
  return {
520
- use: "String",
859
+ encode: "input",
860
+ use: `String`,
521
861
  content: "",
522
862
  simple: true,
523
- toJson: `this.${camelcase(context.name)}`,
524
- fromJson: `json['${context.name}'] as String`,
863
+ toJson: `this.${camelcase2(context.name)}`,
864
+ fromJson: context.name ? `json['${context.name}'] as String` : "json",
525
865
  matches: `json['${context.name}'] is String`
526
866
  };
527
867
  }
@@ -530,35 +870,44 @@ return false;
530
870
  * Handle number/integer types with formats
531
871
  */
532
872
  number(schema, context) {
533
- const type = schema.type === "integer" ? "int" : "double";
534
- if (schema.format === "int64") {
873
+ if (schema.type === "integer") {
535
874
  return {
536
875
  content: "",
537
876
  use: "int",
538
877
  simple: true,
539
- toJson: `this.${camelcase(context.name)}`,
878
+ toJson: `this.${camelcase2(context.name)}`,
540
879
  fromJson: `json['${context.name}']`,
541
880
  matches: `json['${context.name}'] is int`
542
881
  };
543
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
+ }
544
893
  return {
545
894
  content: "",
895
+ use: "num",
546
896
  simple: true,
547
- use: type,
548
- toJson: `this.${camelcase(context.name)}`,
897
+ toJson: `this.${camelcase2(context.name)}`,
549
898
  fromJson: `json['${context.name}']`,
550
- matches: `json['${context.name}'] is int`
899
+ matches: `json['${context.name}'] is double`
551
900
  };
552
901
  }
553
- handle(className, schema, required = true, context = {}) {
902
+ #serialize(className, schema, required = true, context = {}) {
554
903
  if (isRef(schema)) {
555
- return this.#ref(schema.$ref, required, context);
904
+ return this.#ref(className, schema.$ref, required, context);
556
905
  }
557
906
  if (schema.allOf && Array.isArray(schema.allOf)) {
558
907
  return this.#allOf(className, schema.allOf, context);
559
908
  }
560
909
  if (schema.anyOf && Array.isArray(schema.anyOf)) {
561
- return this.anyOf(className, schema.anyOf, context);
910
+ return this.#anyOf(className, schema.anyOf, context);
562
911
  }
563
912
  if (schema.oneOf && Array.isArray(schema.oneOf)) {
564
913
  return this.#oneOf(className, schema.oneOf, context);
@@ -585,11 +934,11 @@ return false;
585
934
  return {
586
935
  content: "",
587
936
  use: "dynamic",
588
- toJson: `${camelcase(context.name)}`,
937
+ toJson: `${camelcase2(context.name)}`,
589
938
  fromJson: `json['${context.name}']`
590
939
  };
591
940
  }
592
- return this.primitive(
941
+ return this.#primitive(
593
942
  className,
594
943
  types[0],
595
944
  schema,
@@ -597,9 +946,28 @@ return false;
597
946
  required
598
947
  );
599
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
+ }
600
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";
601
969
 
602
- // packages/dart/src/lib/interceptors.txt
970
+ // packages/dart/src/lib/http/interceptors.txt
603
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";
604
972
 
605
973
  // packages/dart/src/lib/generate.ts
@@ -665,25 +1033,58 @@ function tuneSpec(spec, schemas, refs) {
665
1033
  }
666
1034
  }
667
1035
  async function generate(spec, settings) {
668
- const output = settings.mode === "full" ? join(settings.output, "src") : settings.output;
1036
+ const clientName = settings.name || "Client";
1037
+ const output = join(settings.output, "lib");
669
1038
  const groups = {};
670
1039
  spec.components ??= {};
671
1040
  spec.components.schemas ??= {};
1041
+ const inputs = {};
1042
+ const outputs = {};
672
1043
  forEachOperation({ spec }, (entry, operation) => {
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
+ }
673
1067
  console.log(`Processing ${entry.method} ${entry.path}`);
674
1068
  const group = groups[entry.groupName] ?? (groups[entry.groupName] = {
675
1069
  methods: [],
676
1070
  use: `final ${entry.groupName} = new ${pascalcase2(entry.groupName)}();`
677
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
+ }
678
1078
  group.methods.push(`
679
- Future<http.Response> ${camelcase2(operation.operationId)}() async {
680
- 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(
681
1083
  method: '${entry.method}',
682
1084
  url: Uri.parse('${entry.path}'),
683
1085
  headers: {},
684
- ));
685
- final response = await http.Response.fromStream(stream);
686
- return response;
1086
+ ), ${["json", "multipart"].includes(input.contentType) ? input.encode : ``});
1087
+ ${response ? `${response.decode};` : "return stream;"}
687
1088
  }
688
1089
  `);
689
1090
  });
@@ -698,7 +1099,7 @@ async function generate(spec, settings) {
698
1099
  );
699
1100
  const models = Object.entries(spec.components.schemas).reduce((acc, [name, schema]) => {
700
1101
  const serializer = new DartSerializer(spec, (name2, content) => {
701
- acc[`models/${snakecase2(name2)}.dart`] = `import 'dart:typed_data'; import './index.dart';
1102
+ acc[`models/${snakecase2(name2)}.dart`] = `import 'dart:io';import 'dart:typed_data'; import './index.dart';
702
1103
 
703
1104
  ${content}`;
704
1105
  });
@@ -710,13 +1111,19 @@ ${content}`;
710
1111
  return {
711
1112
  ...acc,
712
1113
  [`api/${snakecase2(name)}.dart`]: `
1114
+ import 'dart:convert';
1115
+
713
1116
  import 'package:http/http.dart' as http;
1117
+
714
1118
  import '../interceptors.dart';
1119
+ import '../inputs/index.dart';
1120
+ import '../outputs/index.dart';
1121
+ import '../models/index.dart';
715
1122
  import '../http.dart';
716
1123
 
717
- class ${pascalcase2(name)} {
1124
+ class ${pascalcase2(name)}Client {
718
1125
  final Dispatcher dispatcher;
719
- ${pascalcase2(name)}(this.dispatcher);
1126
+ ${pascalcase2(name)}Client(this.dispatcher);
720
1127
  ${methods.join("\n")}
721
1128
  }
722
1129
  `
@@ -729,15 +1136,15 @@ import '../http.dart';
729
1136
  import './interceptors.dart';
730
1137
  import './http.dart';
731
1138
 
732
- class Client {
1139
+ class ${clientName} {
733
1140
  final Options options;
734
- ${Object.keys(groups).map((name) => `late final ${pascalcase2(name)} ${camelcase2(name)};`).join("\n")}
1141
+ ${Object.keys(groups).map((name) => `late final ${pascalcase2(name)}Client ${camelcase3(name)};`).join("\n")}
735
1142
 
736
- Client(this.options) {
1143
+ ${clientName}(this.options) {
737
1144
  final interceptors = [new BaseUrlInterceptor(() => this.options.baseUrl)];
738
1145
  final dispatcher = new Dispatcher(interceptors);
739
1146
  ${Object.keys(groups).map(
740
- (name) => `this.${camelcase2(name)} = new ${pascalcase2(name)}(dispatcher);`
1147
+ (name) => `this.${camelcase3(name)} = new ${pascalcase2(name)}Client(dispatcher);`
741
1148
  ).join("\n")}
742
1149
 
743
1150
  }
@@ -757,41 +1164,137 @@ class Options {
757
1164
 
758
1165
  `;
759
1166
  await writeFiles(output, {
760
- ...models
1167
+ ...models,
1168
+ ...inputs,
1169
+ ...outputs
761
1170
  });
762
1171
  await writeFiles(output, {
763
1172
  "models/index.dart": await getFolderExportsV2(join(output, "models"), {
764
1173
  exportSyntax: "export",
765
- extensions: ["dart"]
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"
766
1183
  }),
767
- "index.dart": client,
768
1184
  "interceptors.dart": interceptors_default,
769
- "http.dart": `
770
- import 'interceptors.dart';
771
- import 'package:http/http.dart' as http;
772
-
773
- class Dispatcher {
774
- final List<Interceptor> interceptors;
775
-
776
- Dispatcher(this.interceptors);
777
-
778
- Future<http.StreamedResponse> dispatch(RequestConfig config) {
779
- final modifedConfig = interceptors.fold(
780
- config,
781
- (acc, interceptor) => interceptor.before(acc),
782
- );
783
- final request = http.Request(modifedConfig.method, modifedConfig.url);
784
- return request.send();
785
- }
786
- }
787
- `,
1185
+ "http.dart": dispatcher_default,
788
1186
  ...clazzez
789
- // 'index.dart': await getFolderExports(output, settings.useTsExtension),
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
+ }
790
1212
  });
791
1213
  await settings.formatCode?.({
792
1214
  output
793
1215
  });
794
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';
1234
+
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
+ }
1253
+ }
1254
+ return { inputs, inputName, contentType, encode };
1255
+ }
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;
1297
+ }
795
1298
  export {
796
1299
  generate
797
1300
  };