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