osury 0.29.0 → 1.0.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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "osury",
3
3
  "type": "module",
4
4
  "description": "Generate ReScript types with Sury schemas from OpenAPI specifications",
5
- "version": "0.29.0",
5
+ "version": "1.0.0",
6
6
  "license": "MIT",
7
7
  "bin": {
8
8
  "osury": "bin/osury.mjs"
@@ -4,7 +4,7 @@ import * as Core__Array from "@rescript/core/src/Core__Array.res.mjs";
4
4
  import * as Core__Option from "@rescript/core/src/Core__Option.res.mjs";
5
5
 
6
6
  function quoteTag(tag) {
7
- let needsQuoting = tag.split("").some(c => {
7
+ let needsQuoting = tag === "" || tag.split("").some(c => {
8
8
  let code = c.charCodeAt(0);
9
9
  return !(code >= 97.0 && code <= 122.0 || code >= 65.0 && code <= 90.0 || code >= 48.0 && code <= 57.0 || code === 95.0);
10
10
  });
@@ -60,6 +60,16 @@ let hasUnion = CodegenHelpers.hasUnion;
60
60
 
61
61
  let isPrimitiveOnlyUnion = CodegenHelpers.isPrimitiveOnlyUnion;
62
62
 
63
+ let collectInlineEnums = CodegenTransforms.collectInlineEnums;
64
+
65
+ let resolveEnumNames = CodegenTransforms.resolveEnumNames;
66
+
67
+ let camelize = CodegenTransforms.camelize;
68
+
69
+ let replaceInlineEnums = CodegenTransforms.replaceInlineEnums;
70
+
71
+ let buildExtractedEnumSchemas = CodegenTransforms.buildExtractedEnumSchemas;
72
+
63
73
  let isRefPlusDictUnion = CodegenTransforms.isRefPlusDictUnion;
64
74
 
65
75
  let isPrimitivePlusDictUnion = CodegenTransforms.isPrimitivePlusDictUnion;
@@ -116,6 +126,11 @@ export {
116
126
  getTagForType,
117
127
  hasUnion,
118
128
  isPrimitiveOnlyUnion,
129
+ collectInlineEnums,
130
+ resolveEnumNames,
131
+ camelize,
132
+ replaceInlineEnums,
133
+ buildExtractedEnumSchemas,
119
134
  isRefPlusDictUnion,
120
135
  isPrimitivePlusDictUnion,
121
136
  getUnionName,
@@ -4,6 +4,246 @@ import * as Errors from "./Errors.res.mjs";
4
4
  import * as Core__Array from "@rescript/core/src/Core__Array.res.mjs";
5
5
  import * as Core__Option from "@rescript/core/src/Core__Option.res.mjs";
6
6
  import * as CodegenHelpers from "./CodegenHelpers.res.mjs";
7
+ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
8
+
9
+ function collectEnumsFromType(parentType, fieldPath, _schema) {
10
+ while (true) {
11
+ let schema = _schema;
12
+ if (typeof schema !== "object") {
13
+ return [];
14
+ }
15
+ switch (schema._tag) {
16
+ case "Object" :
17
+ return schema._0.flatMap(f => collectEnumsFromType(parentType, fieldPath.concat([f.name]), f.type));
18
+ case "Enum" :
19
+ if (fieldPath.length > 0) {
20
+ return [{
21
+ parentType: parentType,
22
+ fieldPath: fieldPath,
23
+ values: schema._0
24
+ }];
25
+ } else {
26
+ return [];
27
+ }
28
+ case "PolyVariant" :
29
+ return schema._0.flatMap(c => collectEnumsFromType(parentType, fieldPath, c.payload));
30
+ case "Optional" :
31
+ case "Nullable" :
32
+ case "Array" :
33
+ case "Dict" :
34
+ break;
35
+ case "Union" :
36
+ return schema._0.flatMap(t => collectEnumsFromType(parentType, fieldPath, t));
37
+ default:
38
+ return [];
39
+ }
40
+ _schema = schema._0;
41
+ continue;
42
+ };
43
+ }
44
+
45
+ function collectInlineEnums(schemas) {
46
+ return schemas.flatMap(s => {
47
+ let match = s.schema;
48
+ if (typeof match !== "object") {
49
+ return collectEnumsFromType(s.name, [], s.schema);
50
+ } else if (match._tag === "Enum") {
51
+ return [];
52
+ } else {
53
+ return collectEnumsFromType(s.name, [], s.schema);
54
+ }
55
+ });
56
+ }
57
+
58
+ function camelize(s) {
59
+ let parts = s.replace(/-/g, "_").split("_").filter(p => p !== "");
60
+ let first = parts[0];
61
+ if (first === undefined) {
62
+ return s;
63
+ }
64
+ let rest = parts.slice(1);
65
+ return CodegenHelpers.lcFirst(first) + rest.map(CodegenHelpers.ucFirst).join("");
66
+ }
67
+
68
+ function occurrenceKey(occ) {
69
+ return occ.parentType + "::" + occ.fieldPath.join("/");
70
+ }
71
+
72
+ function valuesCanonicalKey(values) {
73
+ let cmp = (a, b) => {
74
+ if (Primitive_object.lessthan(a, b)) {
75
+ return -1.0;
76
+ } else if (Primitive_object.greaterthan(a, b)) {
77
+ return 1.0;
78
+ } else {
79
+ return 0.0;
80
+ }
81
+ };
82
+ return values.toSorted(cmp).join("");
83
+ }
84
+
85
+ function leafFieldName(occ) {
86
+ let s = occ.fieldPath[occ.fieldPath.length - 1 | 0];
87
+ if (s !== undefined) {
88
+ return s;
89
+ } else {
90
+ return "unknown";
91
+ }
92
+ }
93
+
94
+ function replaceEnumsInType(parentType, fieldPath, names, schema) {
95
+ if (typeof schema !== "object") {
96
+ return schema;
97
+ }
98
+ switch (schema._tag) {
99
+ case "Optional" :
100
+ return {
101
+ _tag: "Optional",
102
+ _0: replaceEnumsInType(parentType, fieldPath, names, schema._0)
103
+ };
104
+ case "Nullable" :
105
+ return {
106
+ _tag: "Nullable",
107
+ _0: replaceEnumsInType(parentType, fieldPath, names, schema._0)
108
+ };
109
+ case "Object" :
110
+ return {
111
+ _tag: "Object",
112
+ _0: schema._0.map(f => {
113
+ let newType = replaceEnumsInType(parentType, fieldPath.concat([f.name]), names, f.type);
114
+ return {
115
+ name: f.name,
116
+ type: newType,
117
+ required: f.required
118
+ };
119
+ })
120
+ };
121
+ case "Array" :
122
+ return {
123
+ _tag: "Array",
124
+ _0: replaceEnumsInType(parentType, fieldPath, names, schema._0)
125
+ };
126
+ case "Enum" :
127
+ if (fieldPath.length <= 0) {
128
+ return schema;
129
+ }
130
+ let key = parentType + "::" + fieldPath.join("/");
131
+ let name = names[key];
132
+ if (name !== undefined) {
133
+ return {
134
+ _tag: "Ref",
135
+ _0: CodegenHelpers.ucFirst(name)
136
+ };
137
+ } else {
138
+ return schema;
139
+ }
140
+ case "PolyVariant" :
141
+ return {
142
+ _tag: "PolyVariant",
143
+ _0: schema._0.map(c => {
144
+ let payload = replaceEnumsInType(parentType, fieldPath, names, c.payload);
145
+ return {
146
+ _tag: c._tag,
147
+ payload: payload
148
+ };
149
+ })
150
+ };
151
+ case "Dict" :
152
+ return {
153
+ _tag: "Dict",
154
+ _0: replaceEnumsInType(parentType, fieldPath, names, schema._0)
155
+ };
156
+ case "Union" :
157
+ return {
158
+ _tag: "Union",
159
+ _0: schema._0.map(t => replaceEnumsInType(parentType, fieldPath, names, t))
160
+ };
161
+ default:
162
+ return schema;
163
+ }
164
+ }
165
+
166
+ function replaceInlineEnums(schemas, names) {
167
+ return schemas.map(s => {
168
+ let match = s.schema;
169
+ let newSchema;
170
+ let exit = 0;
171
+ if (typeof match !== "object" || match._tag !== "Enum") {
172
+ exit = 1;
173
+ } else {
174
+ newSchema = s.schema;
175
+ }
176
+ if (exit === 1) {
177
+ newSchema = replaceEnumsInType(s.name, [], names, s.schema);
178
+ }
179
+ return {
180
+ name: s.name,
181
+ schema: newSchema,
182
+ discriminatorTag: s.discriminatorTag,
183
+ discriminatorPropertyName: s.discriminatorPropertyName,
184
+ fieldDiscriminators: s.fieldDiscriminators
185
+ };
186
+ });
187
+ }
188
+
189
+ function buildExtractedEnumSchemas(occurrences, names) {
190
+ let seen = {};
191
+ let result = [];
192
+ occurrences.forEach(occ => {
193
+ let key = occurrenceKey(occ);
194
+ let name = names[key];
195
+ if (name === undefined) {
196
+ return;
197
+ }
198
+ let typeName = CodegenHelpers.ucFirst(name);
199
+ if (Core__Option.isNone(seen[typeName])) {
200
+ seen[typeName] = true;
201
+ result.push({
202
+ name: typeName,
203
+ schema: {
204
+ _tag: "Enum",
205
+ _0: occ.values
206
+ },
207
+ discriminatorTag: undefined,
208
+ discriminatorPropertyName: undefined,
209
+ fieldDiscriminators: undefined
210
+ });
211
+ return;
212
+ }
213
+ });
214
+ return result;
215
+ }
216
+
217
+ function resolveEnumNames(occurrences, topLevelNames) {
218
+ let topLevelSet = {};
219
+ topLevelNames.forEach(n => {
220
+ topLevelSet[CodegenHelpers.lcFirst(n)] = true;
221
+ });
222
+ let buckets = {};
223
+ occurrences.forEach(occ => {
224
+ let leaf = leafFieldName(occ);
225
+ let vKey = valuesCanonicalKey(occ.values);
226
+ let set = buckets[leaf];
227
+ if (set !== undefined) {
228
+ set[vKey] = true;
229
+ return;
230
+ }
231
+ let set$1 = {};
232
+ set$1[vKey] = true;
233
+ buckets[leaf] = set$1;
234
+ });
235
+ let result = {};
236
+ occurrences.forEach(occ => {
237
+ let leaf = leafFieldName(occ);
238
+ let camelized = camelize(leaf);
239
+ let distinctSets = Core__Option.mapOr(buckets[leaf], 1, set => Object.keys(set).length);
240
+ let collidesTopLevel = Core__Option.isSome(topLevelSet[camelized]);
241
+ let baseName = distinctSets > 1 || collidesTopLevel ? CodegenHelpers.lcFirst(occ.parentType) + CodegenHelpers.ucFirst(camelized) : camelized;
242
+ let name = CodegenHelpers.isReservedKeyword(baseName) ? baseName + "_" : baseName;
243
+ result[occurrenceKey(occ)] = name;
244
+ });
245
+ return result;
246
+ }
7
247
 
8
248
  function isRefPlusDictUnion(types) {
9
249
  if (types.length !== 2) {
@@ -527,6 +767,16 @@ function validateUnionDiscriminators(schemas) {
527
767
  }
528
768
 
529
769
  export {
770
+ collectEnumsFromType,
771
+ collectInlineEnums,
772
+ camelize,
773
+ occurrenceKey,
774
+ valuesCanonicalKey,
775
+ leafFieldName,
776
+ replaceEnumsInType,
777
+ replaceInlineEnums,
778
+ buildExtractedEnumSchemas,
779
+ resolveEnumNames,
530
780
  isRefPlusDictUnion,
531
781
  isPrimitivePlusDictUnion,
532
782
  getUnionName,
package/src/IRGen.res.mjs CHANGED
@@ -301,7 +301,13 @@ function generate(schemas) {
301
301
  };
302
302
  }
303
303
  let warnings = CodegenTransforms.collectUnionWarnings(schemas);
304
- let extractedUnions = schemas.flatMap(s => CodegenTransforms.extractUnions(s.name, s.schema).map(extracted => {
304
+ let enumOccurrences = CodegenTransforms.collectInlineEnums(schemas);
305
+ let topLevelNames = schemas.map(s => s.name);
306
+ let enumNames = CodegenTransforms.resolveEnumNames(enumOccurrences, topLevelNames);
307
+ let enumSchemas = CodegenTransforms.buildExtractedEnumSchemas(enumOccurrences, enumNames);
308
+ let schemasAfterEnumPromotion = CodegenTransforms.replaceInlineEnums(schemas, enumNames);
309
+ let schemas$1 = enumSchemas.concat(schemasAfterEnumPromotion);
310
+ let extractedUnions = schemas$1.flatMap(s => CodegenTransforms.extractUnions(s.name, s.schema).map(extracted => {
305
311
  let dict = s.fieldDiscriminators;
306
312
  let discriminatorPropertyName = dict !== undefined ? dict[extracted.name] : undefined;
307
313
  return {
@@ -321,7 +327,7 @@ function generate(schemas) {
321
327
  return true;
322
328
  }
323
329
  });
324
- let modifiedSchemas = schemas.map(s => ({
330
+ let modifiedSchemas = schemas$1.map(s => ({
325
331
  name: s.name,
326
332
  schema: CodegenTransforms.replaceUnions(s.name, s.schema),
327
333
  discriminatorTag: s.discriminatorTag,
@@ -516,6 +516,138 @@ function parsePathParameters(pathsJson) {
516
516
  };
517
517
  }
518
518
 
519
+ function extractAllDiscriminatorMappings(json) {
520
+ let result = {};
521
+ let walk = j => {
522
+ if (Array.isArray(j)) {
523
+ j.forEach(walk);
524
+ return;
525
+ }
526
+ switch (typeof j) {
527
+ case "object" :
528
+ let match = j["oneOf"];
529
+ let match$1 = j["discriminator"];
530
+ if (Array.isArray(match) && typeof match$1 === "object" && match$1 !== null && !Array.isArray(match$1)) {
531
+ let match$2 = match$1["mapping"];
532
+ if (typeof match$2 === "object" && match$2 !== null && !Array.isArray(match$2)) {
533
+ Object.entries(match$2).forEach(param => {
534
+ let refValue = param[1];
535
+ if (typeof refValue !== "string") {
536
+ return;
537
+ }
538
+ let parts = refValue.split("/");
539
+ let schemaName = parts[parts.length - 1 | 0];
540
+ if (schemaName !== undefined) {
541
+ result[schemaName] = param[0];
542
+ return;
543
+ }
544
+ });
545
+ }
546
+ }
547
+ Object.entries(j).forEach(param => walk(param[1]));
548
+ return;
549
+ default:
550
+ return;
551
+ }
552
+ };
553
+ walk(json);
554
+ return result;
555
+ }
556
+
557
+ function rewriteVariantTagsInType(schema, tagByRef) {
558
+ if (typeof schema !== "object") {
559
+ return schema;
560
+ }
561
+ switch (schema._tag) {
562
+ case "Optional" :
563
+ return {
564
+ _tag: "Optional",
565
+ _0: rewriteVariantTagsInType(schema._0, tagByRef)
566
+ };
567
+ case "Nullable" :
568
+ return {
569
+ _tag: "Nullable",
570
+ _0: rewriteVariantTagsInType(schema._0, tagByRef)
571
+ };
572
+ case "Object" :
573
+ return {
574
+ _tag: "Object",
575
+ _0: schema._0.map(f => ({
576
+ name: f.name,
577
+ type: rewriteVariantTagsInType(f.type, tagByRef),
578
+ required: f.required
579
+ }))
580
+ };
581
+ case "Array" :
582
+ return {
583
+ _tag: "Array",
584
+ _0: rewriteVariantTagsInType(schema._0, tagByRef)
585
+ };
586
+ case "PolyVariant" :
587
+ let newCases = schema._0.map(c => {
588
+ let refName = c.payload;
589
+ if (typeof refName !== "object") {
590
+ return {
591
+ _tag: c._tag,
592
+ payload: rewriteVariantTagsInType(refName, tagByRef)
593
+ };
594
+ }
595
+ if (refName._tag !== "Ref") {
596
+ return {
597
+ _tag: c._tag,
598
+ payload: rewriteVariantTagsInType(refName, tagByRef)
599
+ };
600
+ }
601
+ let actualTag = tagByRef[refName._0];
602
+ if (actualTag !== undefined && actualTag !== c._tag) {
603
+ return {
604
+ _tag: actualTag,
605
+ payload: c.payload
606
+ };
607
+ } else {
608
+ return c;
609
+ }
610
+ });
611
+ return {
612
+ _tag: "PolyVariant",
613
+ _0: newCases
614
+ };
615
+ case "Dict" :
616
+ return {
617
+ _tag: "Dict",
618
+ _0: rewriteVariantTagsInType(schema._0, tagByRef)
619
+ };
620
+ case "Union" :
621
+ return {
622
+ _tag: "Union",
623
+ _0: schema._0.map(t => rewriteVariantTagsInType(t, tagByRef))
624
+ };
625
+ default:
626
+ return schema;
627
+ }
628
+ }
629
+
630
+ function resolveRefTagsInPolyVariants(schemas, mappingByRef) {
631
+ let tagByRef = {};
632
+ schemas.forEach(s => {
633
+ let tag = s.discriminatorTag;
634
+ if (tag !== undefined) {
635
+ tagByRef[s.name] = tag;
636
+ return;
637
+ }
638
+ });
639
+ Object.entries(mappingByRef).forEach(param => {
640
+ tagByRef[param[0]] = param[1];
641
+ });
642
+ return schemas.map(s => ({
643
+ name: s.name,
644
+ schema: rewriteVariantTagsInType(s.schema, tagByRef),
645
+ discriminatorTag: s.discriminatorTag,
646
+ discriminatorPropertyName: s.discriminatorPropertyName,
647
+ fieldDiscriminators: s.fieldDiscriminators
648
+ }));
649
+ }
650
+
519
651
  function parseDocument(json) {
520
652
  if (typeof json === "object" && json !== null && !Array.isArray(json)) {
521
653
  let componentsJson = json["components"];
@@ -533,12 +665,13 @@ function parseDocument(json) {
533
665
  TAG: "Ok",
534
666
  _0: []
535
667
  });
668
+ let mappingByRef = extractAllDiscriminatorMappings(json);
536
669
  let exit = 0;
537
670
  if (componentSchemas.TAG === "Ok" && pathSchemas.TAG === "Ok") {
538
671
  if (paramSchemas.TAG === "Ok") {
539
672
  return {
540
673
  TAG: "Ok",
541
- _0: componentSchemas._0.concat(pathSchemas._0).concat(paramSchemas._0)
674
+ _0: resolveRefTagsInPolyVariants(componentSchemas._0.concat(pathSchemas._0).concat(paramSchemas._0), mappingByRef)
542
675
  };
543
676
  }
544
677
  exit = 2;
@@ -583,6 +716,9 @@ export {
583
716
  parseComponentSchemas,
584
717
  buildParamsObjectJson,
585
718
  parsePathParameters,
719
+ extractAllDiscriminatorMappings,
720
+ rewriteVariantTagsInType,
721
+ resolveRefTagsInPolyVariants,
586
722
  parseDocument,
587
723
  }
588
724
  /* No side effect */