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