osury 0.29.0 → 1.0.1

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/README.md CHANGED
@@ -6,23 +6,33 @@ Generate ReScript types with [Sury](https://github.com/DZakh/sury) schemas from
6
6
 
7
7
  Huge thanks to the [ReScript](https://rescript-lang.org/) team for an amazing language, and special thanks to [@DZakh](https://github.com/DZakh) for the incredible [Sury](https://github.com/DZakh/sury) library that made this project possible.
8
8
 
9
- ## Early Stage Warning
9
+ ## Project Status
10
10
 
11
- This project is in a very early stage of development and is tailored to my specific needs. It comes with **no guarantees** of stability, correctness, or completeness.
12
-
13
- **Suggestions and contributions are very welcome!** Feel free to open issues or submit PRs.
11
+ Now at **v1.0.0**. The codegen is in production use against real-world OpenAPI specs
12
+ (both Pydantic/FastAPI and Django/DRF generated). The pipeline is feature-complete
13
+ for the OpenAPI 3.x patterns most code-first generators emit; edge cases beyond that
14
+ are added on demand. Issues and PRs welcome.
14
15
 
15
16
  ## Features
16
17
 
17
- - OpenAPI 3.x → ReScript types
18
- - `@schema` annotations for Sury PPX validation
19
- - `@genType` for TypeScript interop
20
- - Union types extracted as proper variants with `@tag("_tag")`
21
- - Automatic deduplication of identical union structures
22
- - Generates `module S = Sury` alias (required by sury-ppx)
23
- - Generates helper shims for TypeScript interop (`Dict.gen.ts`, `Nullable.shim.ts`)
24
- - Proper JSON `null` support via `Nullable.t<T>` (maps to `T | null` in TypeScript)
25
- - Path response types generation from `paths.*.responses`
18
+ - **OpenAPI 3.x → ReScript types** with full discriminated-union support
19
+ - **`@schema`** annotations for Sury PPX runtime validation
20
+ - **`@genType`** for TypeScript interop with type-safe literal unions
21
+ - **Discriminated unions** via `discriminator.mapping` (OpenAPI-standard, primary)
22
+ with fallback to `_tag.const` (Effect-style convention)
23
+ - **Custom discriminator property names** via `discriminator.propertyName`
24
+ (`@tag("type")`, `@tag("kind")`, etc., not just `_tag`)
25
+ - **Inline enum auto-promotion** to named top-level types with field-based naming
26
+ and structural deduplication
27
+ - **Path operation types** — both `Response` types from `responses[200]` and `Params`
28
+ types from query/path `parameters[]`
29
+ - **JSON `null` support** via `Nullable.t<T>` (maps to `T | null` in TypeScript,
30
+ distinct from `option<T>` for `undefined`)
31
+ - **Untyped/Unknown handling** via `JSON.t` with `@s.matches(S.json)` so untyped
32
+ fields don't poison `@schema` propagation through enclosing types
33
+ - **Automatic deduplication** of identical union/enum structures
34
+ - **TypeScript shims** generated alongside (`Dict.gen.ts`, `JSON.gen.ts`,
35
+ `Nullable.res`, `Nullable.shim.ts`)
26
36
 
27
37
  ## Installation
28
38
 
@@ -40,10 +50,14 @@ npx osury openapi.json
40
50
 
41
51
  # Generate to specific directory
42
52
  npx osury openapi.json src/API.res
43
- # Creates: src/API.res, src/Dict.gen.ts, src/Nullable.res, src/Nullable.shim.ts
53
+ # Creates: src/API.res, src/Dict.gen.ts, src/JSON.gen.ts,
54
+ # src/Nullable.res, src/Nullable.shim.ts
44
55
 
45
56
  # With explicit output flag
46
57
  npx osury generate openapi.json -o src/Schema.res
58
+
59
+ # Show help
60
+ npx osury --help
47
61
  ```
48
62
 
49
63
  ### Full Example: OpenAPI → ReScript → TypeScript
@@ -179,14 +193,19 @@ Open [http://localhost:4173/demo/](http://localhost:4173/demo/).
179
193
 
180
194
  ### Helper Files
181
195
 
182
- Also generates helper files:
196
+ Generated alongside the main `Schema.res`:
183
197
 
184
198
  **Dict.gen.ts** — TypeScript shim for dictionaries:
185
199
  ```typescript
186
200
  export type t<T> = { [key: string]: T };
187
201
  ```
188
202
 
189
- **Nullable.res** — ReScript nullable type:
203
+ **JSON.gen.ts** — TypeScript shim for untyped/Unknown fields:
204
+ ```typescript
205
+ export type t = unknown;
206
+ ```
207
+
208
+ **Nullable.res** — ReScript nullable type (`option<T>` with `T | null` TS mapping):
190
209
  ```rescript
191
210
  @genType.import(("./Nullable.shim.ts", "t"))
192
211
  type t<'a> = option<'a>
@@ -203,8 +222,9 @@ export type t<T> = T | null;
203
222
  |------------|---------|
204
223
  | `@genType` | TypeScript type generation |
205
224
  | `@schema` | Sury PPX validation schema |
206
- | `@tag("_tag")` | Discriminated union support (Effect TS compatible) |
207
- | `@s.null` | Field-level JSON `null` support |
225
+ | `@tag("_tag")` | Discriminated union tag — default Effect TS convention; overridable via `discriminator.propertyName` (e.g. `@tag("type")`) |
226
+ | `@s.null` | Field-level JSON `null` support (for `Nullable.t<T>` fields) |
227
+ | `@s.matches(S.json)` | Per-field synthesizer for `JSON.t` so untyped fields don't poison enclosing `@schema` |
208
228
  | `@unboxed` | Primitive-only union optimization |
209
229
  | `@as("name")` | Reserved keyword field mapping |
210
230
 
@@ -227,20 +247,71 @@ For the generated code to compile, your project needs:
227
247
  | `boolean` | `bool` |
228
248
  | `null` | `unit` |
229
249
  | `array` | `array<T>` |
230
- | `object` | `{ field: T }` |
250
+ | `object` | record `{ field: T }` |
231
251
  | `$ref` | type reference |
232
- | `enum` | poly variant `[#A \| #B]` |
233
- | `const` | single-value enum (for `_tag`) |
234
- | `anyOf` (nullable) | `Nullable.t<T>` `T \| null` in TS |
235
- | `anyOf` (union) | variant type with `@tag("_tag")` |
236
- | `oneOf` (discriminated) | poly variant with `_tag.const` extraction |
252
+ | `enum` (inline) | extracted to named `type sortDirection = [#asc \| #desc]` |
253
+ | `enum` (top-level) | poly variant `[#A \| #B]` |
254
+ | `const` (single string) | one-element enum (used for discriminator tags) |
255
+ | schema with no `type` | `JSON.t` (TS: `unknown`) with `@s.matches(S.json)` |
256
+ | `anyOf: [T, null]` | `Nullable.t<T>` (TS: `T \| null`) |
257
+ | `anyOf: [A, B, ...]` (no discriminator) | extracted variant type with structural name |
258
+ | `oneOf` + `discriminator` | poly variant with tags from `discriminator.mapping` |
237
259
  | `allOf` | merged object type |
238
260
  | `additionalProperties` | `Dict.t<T>` |
239
261
  | `default` value | field becomes required |
262
+ | `parameters[]` (query + path) | synthetic `<method><Path>Params` record |
263
+ | `responses[200].schema` | `<method><Path>Response` type |
264
+
265
+ ## Discriminated Unions
266
+
267
+ osury resolves variant case tags through a three-level priority chain, so the
268
+ ReScript-side tag always matches the **wire-format truth**, never the class name
269
+ on the backend:
270
+
271
+ 1. **`discriminator.mapping`** — OpenAPI 3.x standard, primary source. Works with
272
+ any property name (`_tag`, `tag`, `type`, `kind`, …).
273
+ 2. **`_tag.const`** — fallback when no explicit mapping is declared (Effect-style
274
+ implicit convention).
275
+ 3. **Ref name** — last-resort default for `$ref` items with no const information.
276
+
277
+ The practical effect: **class names on the backend can diverge from wire-format
278
+ discriminator values without breaking osury**. Pydantic's natural style
279
+ (`class MetricGridBlock` ↔ `_tag: "MetricGrid"`) works out of the box as long as
280
+ the discriminator mapping is declared in the schema.
281
+
282
+ ```yaml
283
+ Block:
284
+ oneOf:
285
+ - { $ref: "#/components/schemas/MetricGridBlock" }
286
+ - { $ref: "#/components/schemas/ProseBlock" }
287
+ discriminator:
288
+ propertyName: _tag
289
+ mapping:
290
+ MetricGrid: "#/components/schemas/MetricGridBlock"
291
+ Prose: "#/components/schemas/ProseBlock"
292
+ ```
293
+
294
+ Generates:
295
+
296
+ ```rescript
297
+ @genType @tag("_tag") @schema
298
+ type block = MetricGrid({
299
+ metrics: array<string>
300
+ }) | Prose({
301
+ text: string
302
+ })
303
+ ```
304
+
305
+ Note the case names (`MetricGrid`, `Prose`) come from the **mapping keys**, not
306
+ from the schema class names (`MetricGridBlock`, `ProseBlock`). Case payloads
307
+ are inlined records — fields are copied from the referenced schema and the
308
+ discriminator property is filtered out to avoid duplication with `@tag`.
240
309
 
241
- ## Path Responses
310
+ ## Path Types
242
311
 
243
- Types are also generated from path responses:
312
+ Types are generated from both `responses` and `parameters` of each path operation.
313
+
314
+ ### Response types
244
315
 
245
316
  ```json
246
317
  {
@@ -264,6 +335,45 @@ Types are also generated from path responses:
264
335
 
265
336
  Generates: `type getUsersResponse = userList`
266
337
 
338
+ ### Params types
339
+
340
+ Query and path parameters are folded into a synthetic object schema and pushed
341
+ through the same parsing pipeline (so all rules — `default → required`,
342
+ `anyOf [T, null] → Nullable.t<T>`, inline enum → named type — apply uniformly).
343
+
344
+ ```json
345
+ {
346
+ "paths": {
347
+ "/products": {
348
+ "get": {
349
+ "parameters": [
350
+ { "in": "query", "name": "sort_field",
351
+ "schema": { "type": "string", "enum": ["sales", "clicks", "impressions"] } },
352
+ { "in": "query", "name": "limit",
353
+ "schema": { "type": "integer", "default": 50 } }
354
+ ]
355
+ }
356
+ }
357
+ }
358
+ }
359
+ ```
360
+
361
+ Generates:
362
+
363
+ ```rescript
364
+ @genType @schema
365
+ type sortField = [#sales | #clicks | #impressions] // promoted from inline enum
366
+
367
+ @genType @schema
368
+ type getProductsParams = {
369
+ sort_field: option<sortField>,
370
+ limit: int, // has default → required
371
+ }
372
+ ```
373
+
374
+ Headers and serialization details (`style`/`explode`) are intentionally excluded —
375
+ they belong to the HTTP client layer, not the schema contract.
376
+
267
377
  ## License
268
378
 
269
379
  MIT
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.1",
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 */