@f3liz/rescript-autogen-openapi 0.1.7 → 0.3.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.

Potentially problematic release.


This version of @f3liz/rescript-autogen-openapi might be problematic. Click here for more details.

@@ -19,7 +19,7 @@ function applyConstraints(base, min, max, toString) {
19
19
  }
20
20
  }
21
21
 
22
- function generateSchemaWithContext(ctx, depthOpt, irType) {
22
+ function generateSchemaWithContext(ctx, depthOpt, extractedTypeMap, irType) {
23
23
  let depth = depthOpt !== undefined ? depthOpt : 0;
24
24
  if (depth > 100) {
25
25
  GenerationContext.addWarning(ctx, {
@@ -29,7 +29,11 @@ function generateSchemaWithContext(ctx, depthOpt, irType) {
29
29
  });
30
30
  return "S.json";
31
31
  }
32
- let recurse = nextIrType => generateSchemaWithContext(ctx, depth + 1 | 0, nextIrType);
32
+ let recurse = nextIrType => generateSchemaWithContext(ctx, depth + 1 | 0, extractedTypeMap, nextIrType);
33
+ let foundExtracted = extractedTypeMap !== undefined ? extractedTypeMap.find(param => SchemaIR.equals(param.irType, irType)) : undefined;
34
+ if (foundExtracted !== undefined) {
35
+ return foundExtracted.typeName + `Schema`;
36
+ }
33
37
  if (typeof irType !== "object") {
34
38
  switch (irType) {
35
39
  case "Boolean" :
@@ -66,15 +70,46 @@ function generateSchemaWithContext(ctx, depthOpt, irType) {
66
70
  if (additionalProperties !== undefined) {
67
71
  return `S.dict(` + recurse(additionalProperties) + `)`;
68
72
  } else {
69
- return "S.json";
73
+ return "S.dict(S.json)";
70
74
  }
71
75
  }
72
76
  let fields = properties.map(param => {
77
+ let fieldType = param[1];
73
78
  let name = param[0];
74
- let schemaCode = recurse(param[1]);
79
+ let schemaCode = recurse(fieldType);
75
80
  let camelName = CodegenUtils.escapeKeyword(JsConvertCase.toCamelCase(name));
81
+ let alreadyNullable = true;
82
+ if (!schemaCode.startsWith("S.nullableAsOption(")) {
83
+ let tmp;
84
+ if (typeof fieldType !== "object") {
85
+ tmp = false;
86
+ } else {
87
+ switch (fieldType.TAG) {
88
+ case "Union" :
89
+ tmp = fieldType._0.some(t => {
90
+ if (typeof t !== "object") {
91
+ return t === "Null";
92
+ }
93
+ if (t.TAG !== "Literal") {
94
+ return false;
95
+ }
96
+ let tmp = t._0;
97
+ return typeof tmp !== "object";
98
+ });
99
+ break;
100
+ case "Option" :
101
+ tmp = true;
102
+ break;
103
+ default:
104
+ tmp = false;
105
+ }
106
+ }
107
+ alreadyNullable = tmp;
108
+ }
76
109
  if (param[2]) {
77
110
  return ` ` + camelName + `: s.field("` + name + `", ` + schemaCode + `),`;
111
+ } else if (alreadyNullable) {
112
+ return ` ` + camelName + `: s.fieldOr("` + name + `", ` + schemaCode + `, None),`;
78
113
  } else {
79
114
  return ` ` + camelName + `: s.fieldOr("` + name + `", S.nullableAsOption(` + schemaCode + `), None),`;
80
115
  }
@@ -97,7 +132,22 @@ function generateSchemaWithContext(ctx, depthOpt, irType) {
97
132
  }
98
133
  case "Union" :
99
134
  let types = irType._0;
100
- let match = Stdlib_Array.reduce(types, [
135
+ let nonNullTypes = types.filter(t => {
136
+ if (typeof t !== "object") {
137
+ return t !== "Null";
138
+ }
139
+ if (t.TAG !== "Literal") {
140
+ return true;
141
+ }
142
+ let tmp = t._0;
143
+ return typeof tmp === "object";
144
+ });
145
+ let hasNull = nonNullTypes.length < types.length;
146
+ if (hasNull && nonNullTypes.length === 1) {
147
+ return `S.nullableAsOption(` + recurse(nonNullTypes[0]) + `)`;
148
+ }
149
+ let effectiveTypes = hasNull ? nonNullTypes : types;
150
+ let match = Stdlib_Array.reduce(effectiveTypes, [
101
151
  false,
102
152
  false,
103
153
  undefined,
@@ -122,9 +172,10 @@ function generateSchemaWithContext(ctx, depthOpt, irType) {
122
172
  }
123
173
  });
124
174
  let arrayItemType = match[2];
125
- if (match[0] && match[1] && SchemaIR.equals(Stdlib_Option.getOr(arrayItemType, "Unknown"), Stdlib_Option.getOr(match[3], "Unknown"))) {
126
- return `S.array(` + recurse(Stdlib_Option.getOr(arrayItemType, "Unknown")) + `)`;
127
- } else if (types.every(t => {
175
+ let result;
176
+ if (match[0] && match[1] && effectiveTypes.length === 2 && SchemaIR.equals(Stdlib_Option.getOr(arrayItemType, "Unknown"), Stdlib_Option.getOr(match[3], "Unknown"))) {
177
+ result = `S.array(` + recurse(Stdlib_Option.getOr(arrayItemType, "Unknown")) + `)`;
178
+ } else if (effectiveTypes.every(t => {
128
179
  if (typeof t !== "object") {
129
180
  return false;
130
181
  }
@@ -132,39 +183,286 @@ function generateSchemaWithContext(ctx, depthOpt, irType) {
132
183
  return false;
133
184
  }
134
185
  let tmp = t._0;
135
- return typeof tmp !== "object" ? false : tmp.TAG === "StringLiteral";
136
- }) && types.length !== 0 && types.length <= 50) {
137
- return `S.union([` + types.map(recurse).join(", ") + `])`;
138
- } else {
139
- GenerationContext.addWarning(ctx, {
140
- TAG: "ComplexUnionSimplified",
141
- location: ctx.path,
142
- types: types.map(SchemaIR.toString).join(" | ")
186
+ if (typeof tmp !== "object") {
187
+ return false;
188
+ } else {
189
+ return tmp.TAG === "StringLiteral";
190
+ }
191
+ }) && effectiveTypes.length !== 0 && effectiveTypes.length <= 50) {
192
+ result = `S.union([` + effectiveTypes.map(recurse).join(", ") + `])`;
193
+ } else if (effectiveTypes.length !== 0) {
194
+ let runtimeKinds = {};
195
+ effectiveTypes.forEach(t => {
196
+ let kind;
197
+ if (typeof t !== "object") {
198
+ switch (t) {
199
+ case "Boolean" :
200
+ kind = "boolean";
201
+ break;
202
+ case "Null" :
203
+ kind = "null";
204
+ break;
205
+ default:
206
+ kind = "unknown";
207
+ }
208
+ } else {
209
+ switch (t.TAG) {
210
+ case "String" :
211
+ kind = "string";
212
+ break;
213
+ case "Number" :
214
+ case "Integer" :
215
+ kind = "number";
216
+ break;
217
+ case "Array" :
218
+ kind = "array";
219
+ break;
220
+ case "Literal" :
221
+ let tmp = t._0;
222
+ if (typeof tmp !== "object") {
223
+ kind = "null";
224
+ } else {
225
+ switch (tmp.TAG) {
226
+ case "StringLiteral" :
227
+ kind = "string";
228
+ break;
229
+ case "NumberLiteral" :
230
+ kind = "number";
231
+ break;
232
+ case "BooleanLiteral" :
233
+ kind = "boolean";
234
+ break;
235
+ }
236
+ }
237
+ break;
238
+ case "Object" :
239
+ case "Intersection" :
240
+ case "Reference" :
241
+ kind = "object";
242
+ break;
243
+ default:
244
+ kind = "unknown";
245
+ }
246
+ }
247
+ let count = Stdlib_Option.getOr(runtimeKinds[kind], 0);
248
+ runtimeKinds[kind] = count + 1 | 0;
143
249
  });
144
- return "S.json";
250
+ let canUnbox = Object.values(runtimeKinds).every(count => count <= 1);
251
+ if (canUnbox) {
252
+ let rawNames = effectiveTypes.map(CodegenUtils.variantConstructorName);
253
+ let names = CodegenUtils.deduplicateNames(rawNames);
254
+ let branches = effectiveTypes.map((memberType, i) => {
255
+ let constructorName = names[i];
256
+ if (typeof memberType === "object" && memberType.TAG === "Object") {
257
+ let additionalProperties = memberType.additionalProperties;
258
+ let properties = memberType.properties;
259
+ if (properties.length === 0) {
260
+ if (additionalProperties !== undefined) {
261
+ return `S.dict(` + recurse(additionalProperties) + `)->S.shape(v => ` + constructorName + `(v))`;
262
+ } else {
263
+ return `S.dict(S.json)->S.shape(v => ` + constructorName + `(v))`;
264
+ }
265
+ }
266
+ let fields = properties.map(param => {
267
+ let fieldType = param[1];
268
+ let name = param[0];
269
+ let schemaCode = recurse(fieldType);
270
+ let camelName = CodegenUtils.escapeKeyword(JsConvertCase.toCamelCase(name));
271
+ let alreadyNullable = true;
272
+ if (!schemaCode.startsWith("S.nullableAsOption(")) {
273
+ let tmp;
274
+ if (typeof fieldType !== "object") {
275
+ tmp = false;
276
+ } else {
277
+ switch (fieldType.TAG) {
278
+ case "Union" :
279
+ tmp = fieldType._0.some(t => {
280
+ if (typeof t !== "object") {
281
+ return t === "Null";
282
+ }
283
+ if (t.TAG !== "Literal") {
284
+ return false;
285
+ }
286
+ let tmp = t._0;
287
+ return typeof tmp !== "object";
288
+ });
289
+ break;
290
+ case "Option" :
291
+ tmp = true;
292
+ break;
293
+ default:
294
+ tmp = false;
295
+ }
296
+ }
297
+ alreadyNullable = tmp;
298
+ }
299
+ if (param[2]) {
300
+ return ` ` + camelName + `: s.field("` + name + `", ` + schemaCode + `),`;
301
+ } else if (alreadyNullable) {
302
+ return ` ` + camelName + `: s.fieldOr("` + name + `", ` + schemaCode + `, None),`;
303
+ } else {
304
+ return ` ` + camelName + `: s.fieldOr("` + name + `", S.nullableAsOption(` + schemaCode + `), None),`;
305
+ }
306
+ }).join("\n");
307
+ return `S.object(s => ` + constructorName + `({\n` + fields + `\n }))`;
308
+ }
309
+ let innerSchema = recurse(memberType);
310
+ return innerSchema + `->S.shape(v => ` + constructorName + `(v))`;
311
+ });
312
+ result = `S.union([` + branches.join(", ") + `])`;
313
+ } else {
314
+ result = recurse(effectiveTypes[effectiveTypes.length - 1 | 0]);
315
+ }
316
+ } else {
317
+ result = "S.json";
318
+ }
319
+ if (hasNull) {
320
+ return `S.nullableAsOption(` + result + `)`;
321
+ } else {
322
+ return result;
145
323
  }
146
324
  case "Intersection" :
147
325
  let types$1 = irType._0;
148
- if (types$1.every(t => typeof t !== "object" ? false : t.TAG === "Reference") && types$1.length !== 0) {
326
+ if (types$1.every(t => {
327
+ if (typeof t !== "object") {
328
+ return false;
329
+ } else {
330
+ return t.TAG === "Reference";
331
+ }
332
+ }) && types$1.length !== 0) {
149
333
  return recurse(Stdlib_Option.getOr(types$1[types$1.length - 1 | 0], "Unknown"));
150
- } else {
151
- GenerationContext.addWarning(ctx, {
152
- TAG: "IntersectionNotFullySupported",
153
- location: ctx.path,
154
- note: "Complex intersection"
155
- });
156
- return "S.json";
157
334
  }
335
+ let match$1 = Stdlib_Array.reduce(types$1, [
336
+ [],
337
+ []
338
+ ], (param, t) => {
339
+ let nonObj = param[1];
340
+ let props = param[0];
341
+ if (typeof t !== "object") {
342
+ return [
343
+ props,
344
+ nonObj.concat([t])
345
+ ];
346
+ } else if (t.TAG === "Object") {
347
+ return [
348
+ props.concat(t.properties),
349
+ nonObj
350
+ ];
351
+ } else {
352
+ return [
353
+ props,
354
+ nonObj.concat([t])
355
+ ];
356
+ }
357
+ });
358
+ let nonObjectTypes = match$1[1];
359
+ let objectProps = match$1[0];
360
+ if (objectProps.length !== 0 && nonObjectTypes.length === 0) {
361
+ let fields$1 = objectProps.map(param => {
362
+ let fieldType = param[1];
363
+ let name = param[0];
364
+ let schemaCode = recurse(fieldType);
365
+ let camelName = CodegenUtils.escapeKeyword(JsConvertCase.toCamelCase(name));
366
+ let alreadyNullable = true;
367
+ if (!schemaCode.startsWith("S.nullableAsOption(")) {
368
+ let tmp;
369
+ if (typeof fieldType !== "object") {
370
+ tmp = false;
371
+ } else {
372
+ switch (fieldType.TAG) {
373
+ case "Union" :
374
+ tmp = fieldType._0.some(t => {
375
+ if (typeof t !== "object") {
376
+ return t === "Null";
377
+ }
378
+ if (t.TAG !== "Literal") {
379
+ return false;
380
+ }
381
+ let tmp = t._0;
382
+ return typeof tmp !== "object";
383
+ });
384
+ break;
385
+ case "Option" :
386
+ tmp = true;
387
+ break;
388
+ default:
389
+ tmp = false;
390
+ }
391
+ }
392
+ alreadyNullable = tmp;
393
+ }
394
+ if (param[2]) {
395
+ return ` ` + camelName + `: s.field("` + name + `", ` + schemaCode + `),`;
396
+ } else if (alreadyNullable) {
397
+ return ` ` + camelName + `: s.fieldOr("` + name + `", ` + schemaCode + `, None),`;
398
+ } else {
399
+ return ` ` + camelName + `: s.fieldOr("` + name + `", S.nullableAsOption(` + schemaCode + `), None),`;
400
+ }
401
+ }).join("\n");
402
+ return `S.object(s => {\n` + fields$1 + `\n })`;
403
+ }
404
+ if (nonObjectTypes.length !== 0 && objectProps.length === 0) {
405
+ return recurse(Stdlib_Option.getOr(types$1[types$1.length - 1 | 0], "Unknown"));
406
+ }
407
+ GenerationContext.addWarning(ctx, {
408
+ TAG: "IntersectionNotFullySupported",
409
+ location: ctx.path,
410
+ note: "Mixed object/non-object intersection"
411
+ });
412
+ let fields$2 = objectProps.map(param => {
413
+ let fieldType = param[1];
414
+ let name = param[0];
415
+ let schemaCode = recurse(fieldType);
416
+ let camelName = CodegenUtils.escapeKeyword(JsConvertCase.toCamelCase(name));
417
+ let alreadyNullable = true;
418
+ if (!schemaCode.startsWith("S.nullableAsOption(")) {
419
+ let tmp;
420
+ if (typeof fieldType !== "object") {
421
+ tmp = false;
422
+ } else {
423
+ switch (fieldType.TAG) {
424
+ case "Union" :
425
+ tmp = fieldType._0.some(t => {
426
+ if (typeof t !== "object") {
427
+ return t === "Null";
428
+ }
429
+ if (t.TAG !== "Literal") {
430
+ return false;
431
+ }
432
+ let tmp = t._0;
433
+ return typeof tmp !== "object";
434
+ });
435
+ break;
436
+ case "Option" :
437
+ tmp = true;
438
+ break;
439
+ default:
440
+ tmp = false;
441
+ }
442
+ }
443
+ alreadyNullable = tmp;
444
+ }
445
+ if (param[2]) {
446
+ return ` ` + camelName + `: s.field("` + name + `", ` + schemaCode + `),`;
447
+ } else if (alreadyNullable) {
448
+ return ` ` + camelName + `: s.fieldOr("` + name + `", ` + schemaCode + `, None),`;
449
+ } else {
450
+ return ` ` + camelName + `: s.fieldOr("` + name + `", S.nullableAsOption(` + schemaCode + `), None),`;
451
+ }
452
+ }).join("\n");
453
+ return `S.object(s => {\n` + fields$2 + `\n })`;
158
454
  case "Reference" :
159
455
  let ref = irType._0;
160
- let available = ctx.availableSchemas;
161
- let schemaPath;
162
- if (available !== undefined) {
163
- let name = Stdlib_Option.getOr(ref.split("/")[ref.split("/").length - 1 | 0], "");
164
- schemaPath = available.includes(name) ? JsConvertCase.toPascalCase(name) + `.schema` : `ComponentSchemas.` + JsConvertCase.toPascalCase(name) + `.schema`;
165
- } else {
166
- schemaPath = Stdlib_Option.getOr(ReferenceResolver.refToSchemaPath(ctx.insideComponentSchemas, ctx.modulePrefix, ref), "S.json");
456
+ let refName = ref.includes("/") ? Stdlib_Option.getOr(ref.split("/")[ref.split("/").length - 1 | 0], "") : ref;
457
+ let selfName = ctx.selfRefName;
458
+ let isSelfRef = selfName !== undefined ? refName === selfName : false;
459
+ if (isSelfRef) {
460
+ return "schema";
167
461
  }
462
+ let available = ctx.availableSchemas;
463
+ let schemaPath = available !== undefined ? (
464
+ available.includes(refName) ? JsConvertCase.toPascalCase(refName) + `.schema` : `ComponentSchemas.` + JsConvertCase.toPascalCase(refName) + `.schema`
465
+ ) : Stdlib_Option.getOr(ReferenceResolver.refToSchemaPath(ctx.insideComponentSchemas, ctx.modulePrefix, ref), "S.json");
168
466
  if (schemaPath === "S.json") {
169
467
  GenerationContext.addWarning(ctx, {
170
468
  TAG: "FallbackToJson",
@@ -188,21 +486,33 @@ function generateSchema(depthOpt, pathOpt, insideComponentSchemasOpt, availableS
188
486
  let path = pathOpt !== undefined ? pathOpt : "";
189
487
  let insideComponentSchemas = insideComponentSchemasOpt !== undefined ? insideComponentSchemasOpt : false;
190
488
  let modulePrefix = modulePrefixOpt !== undefined ? modulePrefixOpt : "";
191
- let ctx = GenerationContext.make(path, insideComponentSchemas, availableSchemas, modulePrefix, undefined);
489
+ let ctx = GenerationContext.make(path, insideComponentSchemas, availableSchemas, modulePrefix, undefined, undefined);
192
490
  return [
193
- generateSchemaWithContext(ctx, depth, irType),
491
+ generateSchemaWithContext(ctx, depth, undefined, irType),
194
492
  ctx.warnings
195
493
  ];
196
494
  }
197
495
 
198
- function generateNamedSchema(namedSchema, insideComponentSchemasOpt, availableSchemas, modulePrefixOpt) {
496
+ function generateNamedSchema(namedSchema, insideComponentSchemasOpt, availableSchemas, modulePrefixOpt, extractedTypesOpt) {
199
497
  let insideComponentSchemas = insideComponentSchemasOpt !== undefined ? insideComponentSchemasOpt : false;
200
498
  let modulePrefix = modulePrefixOpt !== undefined ? modulePrefixOpt : "";
201
- let ctx = GenerationContext.make(`schema.` + namedSchema.name, insideComponentSchemas, availableSchemas, modulePrefix, undefined);
499
+ let extractedTypes = extractedTypesOpt !== undefined ? extractedTypesOpt : [];
500
+ let ctx = GenerationContext.make(`schema.` + namedSchema.name, insideComponentSchemas, availableSchemas, modulePrefix, undefined, undefined);
202
501
  let d = namedSchema.description;
203
502
  let doc = d !== undefined ? CodegenUtils.generateDocComment(undefined, d, undefined) : "";
503
+ let extractedTypeMap = extractedTypes.length !== 0 ? extractedTypes : undefined;
504
+ let mainSchema = generateSchemaWithContext(ctx, 0, extractedTypeMap, namedSchema.type_);
505
+ let extractedDefs = extractedTypes.map(param => {
506
+ let typeName = param.typeName;
507
+ let auxCtx = GenerationContext.make(`schema.` + typeName, insideComponentSchemas, availableSchemas, modulePrefix, undefined, undefined);
508
+ let filteredMap = extractedTypes.filter(param => param.typeName !== typeName);
509
+ let auxExtractedTypeMap = filteredMap.length !== 0 ? filteredMap : undefined;
510
+ let auxSchema = generateSchemaWithContext(auxCtx, 0, auxExtractedTypeMap, param.irType);
511
+ return `let ` + typeName + `Schema = ` + auxSchema;
512
+ });
513
+ let allDefs = extractedDefs.concat([doc + `let ` + namedSchema.name + `Schema = ` + mainSchema]);
204
514
  return [
205
- doc + `let ` + namedSchema.name + `Schema = ` + generateSchemaWithContext(ctx, 0, namedSchema.type_),
515
+ allDefs.join("\n\n"),
206
516
  ctx.warnings
207
517
  ];
208
518
  }
@@ -210,7 +520,7 @@ function generateNamedSchema(namedSchema, insideComponentSchemasOpt, availableSc
210
520
  function generateAllSchemas(context) {
211
521
  let warnings = [];
212
522
  let schemas = Object.values(context.schemas).toSorted((a, b) => Primitive_string.compare(a.name, b.name)).map(s => {
213
- let match = generateNamedSchema(s, undefined, undefined, undefined);
523
+ let match = generateNamedSchema(s, undefined, undefined, undefined, undefined);
214
524
  warnings.push(...match[1]);
215
525
  return match[0];
216
526
  });