@f3liz/rescript-autogen-openapi 0.3.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/LICENSE +373 -0
- package/README.md +111 -0
- package/lib/es6/src/Codegen.d.ts +28 -0
- package/lib/es6/src/Codegen.mjs +423 -0
- package/lib/es6/src/Types.d.ts +286 -0
- package/lib/es6/src/Types.mjs +20 -0
- package/lib/es6/src/bindings/Toposort.mjs +12 -0
- package/lib/es6/src/core/CodegenUtils.mjs +261 -0
- package/lib/es6/src/core/DocOverride.mjs +399 -0
- package/lib/es6/src/core/FileSystem.d.ts +4 -0
- package/lib/es6/src/core/FileSystem.mjs +78 -0
- package/lib/es6/src/core/IRBuilder.mjs +201 -0
- package/lib/es6/src/core/OpenAPIParser.mjs +168 -0
- package/lib/es6/src/core/Pipeline.d.ts +6 -0
- package/lib/es6/src/core/Pipeline.mjs +150 -0
- package/lib/es6/src/core/ReferenceResolver.mjs +41 -0
- package/lib/es6/src/core/Result.mjs +378 -0
- package/lib/es6/src/core/SchemaIR.mjs +425 -0
- package/lib/es6/src/core/SchemaIRParser.mjs +683 -0
- package/lib/es6/src/core/SchemaRefResolver.mjs +146 -0
- package/lib/es6/src/core/SchemaRegistry.mjs +92 -0
- package/lib/es6/src/core/SpecDiffer.mjs +251 -0
- package/lib/es6/src/core/SpecMerger.mjs +237 -0
- package/lib/es6/src/generators/ComponentSchemaGenerator.mjs +207 -0
- package/lib/es6/src/generators/DiffReportGenerator.mjs +155 -0
- package/lib/es6/src/generators/EndpointGenerator.mjs +173 -0
- package/lib/es6/src/generators/IRToSuryGenerator.mjs +543 -0
- package/lib/es6/src/generators/IRToTypeGenerator.mjs +592 -0
- package/lib/es6/src/generators/IRToTypeScriptGenerator.mjs +143 -0
- package/lib/es6/src/generators/ModuleGenerator.mjs +285 -0
- package/lib/es6/src/generators/SchemaCodeGenerator.mjs +77 -0
- package/lib/es6/src/generators/ThinWrapperGenerator.mjs +97 -0
- package/lib/es6/src/generators/TypeScriptDtsGenerator.mjs +172 -0
- package/lib/es6/src/generators/TypeScriptWrapperGenerator.mjs +145 -0
- package/lib/es6/src/types/CodegenError.d.ts +66 -0
- package/lib/es6/src/types/CodegenError.mjs +79 -0
- package/lib/es6/src/types/Config.d.ts +31 -0
- package/lib/es6/src/types/Config.mjs +42 -0
- package/lib/es6/src/types/GenerationContext.mjs +47 -0
- package/package.json +53 -0
- package/rescript.json +26 -0
- package/src/Codegen.res +231 -0
- package/src/Types.res +222 -0
- package/src/bindings/Toposort.res +16 -0
- package/src/core/CodegenUtils.res +180 -0
- package/src/core/DocOverride.res +504 -0
- package/src/core/FileSystem.res +63 -0
- package/src/core/IRBuilder.res +66 -0
- package/src/core/OpenAPIParser.res +144 -0
- package/src/core/Pipeline.res +52 -0
- package/src/core/ReferenceResolver.res +41 -0
- package/src/core/Result.res +187 -0
- package/src/core/SchemaIR.res +291 -0
- package/src/core/SchemaIRParser.res +454 -0
- package/src/core/SchemaRefResolver.res +143 -0
- package/src/core/SchemaRegistry.res +107 -0
- package/src/core/SpecDiffer.res +270 -0
- package/src/core/SpecMerger.res +245 -0
- package/src/generators/ComponentSchemaGenerator.res +210 -0
- package/src/generators/DiffReportGenerator.res +152 -0
- package/src/generators/EndpointGenerator.res +176 -0
- package/src/generators/IRToSuryGenerator.res +386 -0
- package/src/generators/IRToTypeGenerator.res +423 -0
- package/src/generators/IRToTypeScriptGenerator.res +77 -0
- package/src/generators/ModuleGenerator.res +363 -0
- package/src/generators/SchemaCodeGenerator.res +84 -0
- package/src/generators/ThinWrapperGenerator.res +124 -0
- package/src/generators/TypeScriptDtsGenerator.res +193 -0
- package/src/generators/TypeScriptWrapperGenerator.res +166 -0
- package/src/types/CodegenError.res +85 -0
- package/src/types/Config.res +95 -0
- package/src/types/GenerationContext.res +56 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
// IRToSuryGenerator.res - Generate Sury schema code from Schema IR
|
|
4
|
+
open Types
|
|
5
|
+
|
|
6
|
+
let addWarning = GenerationContext.addWarning
|
|
7
|
+
|
|
8
|
+
let applyConstraints = (base, min, max, toString) => {
|
|
9
|
+
let s1 = switch min {
|
|
10
|
+
| Some(v) => `${base}->S.min(${toString(v)})`
|
|
11
|
+
| None => base
|
|
12
|
+
}
|
|
13
|
+
switch max {
|
|
14
|
+
| Some(v) => `${s1}->S.max(${toString(v)})`
|
|
15
|
+
| None => s1
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// When extractedTypeMap is provided, complex inline types reference extracted schemas instead of regenerating
|
|
20
|
+
let rec generateSchemaWithContext = (~ctx: GenerationContext.t, ~depth=0, ~extractedTypeMap: option<array<GenerationContext.extractedType>>=?, irType: SchemaIR.irType): string => {
|
|
21
|
+
// We keep a high depth limit just to prevent infinite recursion on circular schemas that escaped IRBuilder
|
|
22
|
+
if depth > 100 {
|
|
23
|
+
addWarning(ctx, DepthLimitReached({depth, path: ctx.path}))
|
|
24
|
+
"S.json"
|
|
25
|
+
} else {
|
|
26
|
+
let recurse = nextIrType => generateSchemaWithContext(~ctx, ~depth=depth + 1, ~extractedTypeMap?, nextIrType)
|
|
27
|
+
|
|
28
|
+
// Check if this irType was extracted — if so, reference the schema by name
|
|
29
|
+
let foundExtracted = switch extractedTypeMap {
|
|
30
|
+
| Some(extracted) =>
|
|
31
|
+
extracted->Array.find(({irType: extractedIr}: GenerationContext.extractedType) =>
|
|
32
|
+
SchemaIR.equals(extractedIr, irType)
|
|
33
|
+
)
|
|
34
|
+
| None => None
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
switch foundExtracted {
|
|
38
|
+
| Some({typeName}) => `${typeName}Schema`
|
|
39
|
+
| None =>
|
|
40
|
+
|
|
41
|
+
switch irType {
|
|
42
|
+
| String({constraints: c}) =>
|
|
43
|
+
let s = applyConstraints("S.string", c.minLength, c.maxLength, v => Int.toString(v))
|
|
44
|
+
switch c.pattern {
|
|
45
|
+
| Some(p) => `${s}->S.pattern(%re("/${CodegenUtils.escapeString(p)}/"))`
|
|
46
|
+
| None => s
|
|
47
|
+
}
|
|
48
|
+
| Number({constraints: c}) =>
|
|
49
|
+
applyConstraints("S.float", c.minimum, c.maximum, v => Float.toInt(v)->Int.toString)
|
|
50
|
+
| Integer({constraints: c}) =>
|
|
51
|
+
applyConstraints("S.int", c.minimum, c.maximum, v => Float.toInt(v)->Int.toString)
|
|
52
|
+
| Boolean => "S.bool"
|
|
53
|
+
| Null => "S.null"
|
|
54
|
+
| Array({items, constraints: c}) =>
|
|
55
|
+
applyConstraints(`S.array(${recurse(items)})`, c.minItems, c.maxItems, v => Int.toString(v))
|
|
56
|
+
| Object({properties, additionalProperties}) =>
|
|
57
|
+
if Array.length(properties) == 0 {
|
|
58
|
+
switch additionalProperties {
|
|
59
|
+
| Some(valueType) => `S.dict(${recurse(valueType)})`
|
|
60
|
+
| None => "S.dict(S.json)"
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
let fields =
|
|
64
|
+
properties
|
|
65
|
+
->Array.map(((name, fieldType, isRequired)) => {
|
|
66
|
+
let schemaCode = recurse(fieldType)
|
|
67
|
+
let camelName = name->CodegenUtils.toCamelCase->CodegenUtils.escapeKeyword
|
|
68
|
+
let alreadyNullable = String.startsWith(schemaCode, "S.nullableAsOption(") || switch fieldType {
|
|
69
|
+
| Option(_) => true
|
|
70
|
+
| Union(types) => types->Array.some(t => switch t { | Null | Literal(NullLiteral) => true | _ => false })
|
|
71
|
+
| _ => false
|
|
72
|
+
}
|
|
73
|
+
if isRequired {
|
|
74
|
+
` ${camelName}: s.field("${name}", ${schemaCode}),`
|
|
75
|
+
} else if alreadyNullable {
|
|
76
|
+
` ${camelName}: s.fieldOr("${name}", ${schemaCode}, None),`
|
|
77
|
+
} else {
|
|
78
|
+
` ${camelName}: s.fieldOr("${name}", S.nullableAsOption(${schemaCode}), None),`
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
->Array.join("\n")
|
|
82
|
+
`S.object(s => {\n${fields}\n })`
|
|
83
|
+
}
|
|
84
|
+
| Literal(value) =>
|
|
85
|
+
switch value {
|
|
86
|
+
| StringLiteral(s) => `S.literal("${CodegenUtils.escapeString(s)}")`
|
|
87
|
+
| NumberLiteral(n) => `S.literal(${Float.toString(n)})`
|
|
88
|
+
| BooleanLiteral(b) => `S.literal(${b ? "true" : "false"})`
|
|
89
|
+
| NullLiteral => "S.literal(null)"
|
|
90
|
+
}
|
|
91
|
+
| Union(types) =>
|
|
92
|
+
// Separate Null from non-null members (handles OpenAPI 3.1 nullable via oneOf)
|
|
93
|
+
let nonNullTypes = types->Array.filter(t =>
|
|
94
|
+
switch t {
|
|
95
|
+
| Null | Literal(NullLiteral) => false
|
|
96
|
+
| _ => true
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
let hasNull = Array.length(nonNullTypes) < Array.length(types)
|
|
100
|
+
|
|
101
|
+
// If the union is just [T, null], treat as nullable
|
|
102
|
+
if hasNull && Array.length(nonNullTypes) == 1 {
|
|
103
|
+
`S.nullableAsOption(${recurse(nonNullTypes->Array.getUnsafe(0))})`
|
|
104
|
+
} else {
|
|
105
|
+
let effectiveTypes = hasNull ? nonNullTypes : types
|
|
106
|
+
|
|
107
|
+
let (hasArray, hasNonArray, arrayItemType, nonArrayType) = effectiveTypes->Array.reduce(
|
|
108
|
+
(false, false, None, None),
|
|
109
|
+
((hArr, hNonArr, arrItem, nonArr), t) =>
|
|
110
|
+
switch t {
|
|
111
|
+
| Array({items}) => (true, hNonArr, Some(items), nonArr)
|
|
112
|
+
| _ => (hArr, true, arrItem, Some(t))
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
let result = if (
|
|
117
|
+
hasArray &&
|
|
118
|
+
hasNonArray &&
|
|
119
|
+
Array.length(effectiveTypes) == 2 &&
|
|
120
|
+
SchemaIR.equals(Option.getOr(arrayItemType, Unknown), Option.getOr(nonArrayType, Unknown))
|
|
121
|
+
) {
|
|
122
|
+
`S.array(${recurse(Option.getOr(arrayItemType, Unknown))})`
|
|
123
|
+
} else if (
|
|
124
|
+
effectiveTypes->Array.every(t =>
|
|
125
|
+
switch t {
|
|
126
|
+
| Literal(StringLiteral(_)) => true
|
|
127
|
+
| _ => false
|
|
128
|
+
}
|
|
129
|
+
) &&
|
|
130
|
+
Array.length(effectiveTypes) > 0 &&
|
|
131
|
+
Array.length(effectiveTypes) <= 50
|
|
132
|
+
) {
|
|
133
|
+
`S.union([${effectiveTypes->Array.map(recurse)->Array.join(", ")}])`
|
|
134
|
+
} else if Array.length(effectiveTypes) > 0 {
|
|
135
|
+
// Check if @unboxed variant is valid (same logic as type generator)
|
|
136
|
+
let canUnbox = {
|
|
137
|
+
let runtimeKinds: Dict.t<int> = Dict.make()
|
|
138
|
+
effectiveTypes->Array.forEach(t => {
|
|
139
|
+
let kind = switch t {
|
|
140
|
+
| Boolean | Literal(BooleanLiteral(_)) => "boolean"
|
|
141
|
+
| String(_) | Literal(StringLiteral(_)) => "string"
|
|
142
|
+
| Number(_) | Integer(_) | Literal(NumberLiteral(_)) => "number"
|
|
143
|
+
| Array(_) => "array"
|
|
144
|
+
| Object(_) | Reference(_) | Intersection(_) => "object"
|
|
145
|
+
| Null | Literal(NullLiteral) => "null"
|
|
146
|
+
| _ => "unknown"
|
|
147
|
+
}
|
|
148
|
+
let count = runtimeKinds->Dict.get(kind)->Option.getOr(0)
|
|
149
|
+
runtimeKinds->Dict.set(kind, count + 1)
|
|
150
|
+
})
|
|
151
|
+
Dict.valuesToArray(runtimeKinds)->Array.every(count => count <= 1)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if canUnbox {
|
|
155
|
+
// @unboxed variant with S.union + S.shape
|
|
156
|
+
let rawNames = effectiveTypes->Array.map(CodegenUtils.variantConstructorName)
|
|
157
|
+
let names = CodegenUtils.deduplicateNames(rawNames)
|
|
158
|
+
|
|
159
|
+
let branches = effectiveTypes->Array.mapWithIndex((memberType, i) => {
|
|
160
|
+
let constructorName = names->Array.getUnsafe(i)
|
|
161
|
+
switch memberType {
|
|
162
|
+
| Object({properties, additionalProperties}) =>
|
|
163
|
+
if Array.length(properties) == 0 {
|
|
164
|
+
switch additionalProperties {
|
|
165
|
+
| Some(valueType) => `S.dict(${recurse(valueType)})->S.shape(v => ${constructorName}(v))`
|
|
166
|
+
| None => `S.dict(S.json)->S.shape(v => ${constructorName}(v))`
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
let fields = properties->Array.map(((name, fieldType, isRequired)) => {
|
|
170
|
+
let schemaCode = recurse(fieldType)
|
|
171
|
+
let camelName = name->CodegenUtils.toCamelCase->CodegenUtils.escapeKeyword
|
|
172
|
+
let alreadyNullable = String.startsWith(schemaCode, "S.nullableAsOption(") || switch fieldType {
|
|
173
|
+
| Option(_) => true
|
|
174
|
+
| Union(unionTypes) => unionTypes->Array.some(t => switch t { | Null | Literal(NullLiteral) => true | _ => false })
|
|
175
|
+
| _ => false
|
|
176
|
+
}
|
|
177
|
+
if isRequired {
|
|
178
|
+
` ${camelName}: s.field("${name}", ${schemaCode}),`
|
|
179
|
+
} else if alreadyNullable {
|
|
180
|
+
` ${camelName}: s.fieldOr("${name}", ${schemaCode}, None),`
|
|
181
|
+
} else {
|
|
182
|
+
` ${camelName}: s.fieldOr("${name}", S.nullableAsOption(${schemaCode}), None),`
|
|
183
|
+
}
|
|
184
|
+
})->Array.join("\n")
|
|
185
|
+
`S.object(s => ${constructorName}({\n${fields}\n }))`
|
|
186
|
+
}
|
|
187
|
+
| _ =>
|
|
188
|
+
let innerSchema = recurse(memberType)
|
|
189
|
+
`${innerSchema}->S.shape(v => ${constructorName}(v))`
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
`S.union([${branches->Array.join(", ")}])`
|
|
193
|
+
} else {
|
|
194
|
+
// Can't use @unboxed: pick last schema (matching type gen)
|
|
195
|
+
recurse(effectiveTypes->Array.getUnsafe(Array.length(effectiveTypes) - 1))
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
"S.json"
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
hasNull ? `S.nullableAsOption(${result})` : result
|
|
202
|
+
}
|
|
203
|
+
| Intersection(types) =>
|
|
204
|
+
if types->Array.every(t =>
|
|
205
|
+
switch t {
|
|
206
|
+
| Reference(_) => true
|
|
207
|
+
| _ => false
|
|
208
|
+
}
|
|
209
|
+
) && Array.length(types) > 0 {
|
|
210
|
+
recurse(types->Array.get(Array.length(types) - 1)->Option.getOr(Unknown))
|
|
211
|
+
} else {
|
|
212
|
+
// Try to merge all Object types in the intersection
|
|
213
|
+
let (objectProps, nonObjectTypes) = types->Array.reduce(
|
|
214
|
+
([], []),
|
|
215
|
+
((props, nonObj), t) =>
|
|
216
|
+
switch t {
|
|
217
|
+
| Object({properties}) => (Array.concat(props, properties), nonObj)
|
|
218
|
+
| _ => (props, Array.concat(nonObj, [t]))
|
|
219
|
+
},
|
|
220
|
+
)
|
|
221
|
+
if Array.length(objectProps) > 0 && Array.length(nonObjectTypes) == 0 {
|
|
222
|
+
// All objects: merge properties into single S.object
|
|
223
|
+
let fields =
|
|
224
|
+
objectProps
|
|
225
|
+
->Array.map(((name, fieldType, isRequired)) => {
|
|
226
|
+
let schemaCode = recurse(fieldType)
|
|
227
|
+
let camelName = name->CodegenUtils.toCamelCase->CodegenUtils.escapeKeyword
|
|
228
|
+
let alreadyNullable = String.startsWith(schemaCode, "S.nullableAsOption(") || switch fieldType {
|
|
229
|
+
| Option(_) => true
|
|
230
|
+
| Union(types) => types->Array.some(t => switch t { | Null | Literal(NullLiteral) => true | _ => false })
|
|
231
|
+
| _ => false
|
|
232
|
+
}
|
|
233
|
+
if isRequired {
|
|
234
|
+
` ${camelName}: s.field("${name}", ${schemaCode}),`
|
|
235
|
+
} else if alreadyNullable {
|
|
236
|
+
` ${camelName}: s.fieldOr("${name}", ${schemaCode}, None),`
|
|
237
|
+
} else {
|
|
238
|
+
` ${camelName}: s.fieldOr("${name}", S.nullableAsOption(${schemaCode}), None),`
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
->Array.join("\n")
|
|
242
|
+
`S.object(s => {\n${fields}\n })`
|
|
243
|
+
} else if Array.length(nonObjectTypes) > 0 && Array.length(objectProps) == 0 {
|
|
244
|
+
recurse(types->Array.get(Array.length(types) - 1)->Option.getOr(Unknown))
|
|
245
|
+
} else {
|
|
246
|
+
addWarning(
|
|
247
|
+
ctx,
|
|
248
|
+
IntersectionNotFullySupported({location: ctx.path, note: "Mixed object/non-object intersection"}),
|
|
249
|
+
)
|
|
250
|
+
let fields =
|
|
251
|
+
objectProps
|
|
252
|
+
->Array.map(((name, fieldType, isRequired)) => {
|
|
253
|
+
let schemaCode = recurse(fieldType)
|
|
254
|
+
let camelName = name->CodegenUtils.toCamelCase->CodegenUtils.escapeKeyword
|
|
255
|
+
let alreadyNullable = String.startsWith(schemaCode, "S.nullableAsOption(") || switch fieldType {
|
|
256
|
+
| Option(_) => true
|
|
257
|
+
| Union(types) => types->Array.some(t => switch t { | Null | Literal(NullLiteral) => true | _ => false })
|
|
258
|
+
| _ => false
|
|
259
|
+
}
|
|
260
|
+
if isRequired {
|
|
261
|
+
` ${camelName}: s.field("${name}", ${schemaCode}),`
|
|
262
|
+
} else if alreadyNullable {
|
|
263
|
+
` ${camelName}: s.fieldOr("${name}", ${schemaCode}, None),`
|
|
264
|
+
} else {
|
|
265
|
+
` ${camelName}: s.fieldOr("${name}", S.nullableAsOption(${schemaCode}), None),`
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
->Array.join("\n")
|
|
269
|
+
`S.object(s => {\n${fields}\n })`
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
| Reference(ref) =>
|
|
273
|
+
// After IR normalization, ref may be just the schema name
|
|
274
|
+
let refName = if ref->String.includes("/") {
|
|
275
|
+
ref->String.split("/")->Array.get(ref->String.split("/")->Array.length - 1)->Option.getOr("")
|
|
276
|
+
} else {
|
|
277
|
+
ref
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Detect self-reference using selfRefName from context
|
|
281
|
+
let isSelfRef = switch ctx.selfRefName {
|
|
282
|
+
| Some(selfName) => refName == selfName
|
|
283
|
+
| None => false
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if isSelfRef {
|
|
287
|
+
"schema" // Self-reference: use the recursive schema binding
|
|
288
|
+
} else {
|
|
289
|
+
let schemaPath = switch ctx.availableSchemas {
|
|
290
|
+
| Some(available) =>
|
|
291
|
+
available->Array.includes(refName)
|
|
292
|
+
? `${CodegenUtils.toPascalCase(refName)}.schema`
|
|
293
|
+
: `ComponentSchemas.${CodegenUtils.toPascalCase(refName)}.schema`
|
|
294
|
+
| None =>
|
|
295
|
+
ReferenceResolver.refToSchemaPath(
|
|
296
|
+
~insideComponentSchemas=ctx.insideComponentSchemas,
|
|
297
|
+
~modulePrefix=ctx.modulePrefix,
|
|
298
|
+
ref,
|
|
299
|
+
)->Option.getOr("S.json")
|
|
300
|
+
}
|
|
301
|
+
if schemaPath == "S.json" {
|
|
302
|
+
addWarning(
|
|
303
|
+
ctx,
|
|
304
|
+
FallbackToJson({
|
|
305
|
+
reason: `Unresolved ref: ${ref}`,
|
|
306
|
+
context: {path: ctx.path, operation: "gen ref", schema: None},
|
|
307
|
+
}),
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
schemaPath
|
|
311
|
+
}
|
|
312
|
+
| Option(inner) => `S.nullableAsOption(${recurse(inner)})`
|
|
313
|
+
| Unknown => "S.json"
|
|
314
|
+
}
|
|
315
|
+
} // end switch foundExtracted
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
let generateSchema = (
|
|
320
|
+
~depth=0,
|
|
321
|
+
~path="",
|
|
322
|
+
~insideComponentSchemas=false,
|
|
323
|
+
~availableSchemas=?,
|
|
324
|
+
~modulePrefix="",
|
|
325
|
+
irType,
|
|
326
|
+
) => {
|
|
327
|
+
let ctx = GenerationContext.make(~path, ~insideComponentSchemas, ~availableSchemas?, ~modulePrefix, ())
|
|
328
|
+
(generateSchemaWithContext(~ctx, ~depth, irType), ctx.warnings)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let generateNamedSchema = (
|
|
332
|
+
~namedSchema: SchemaIR.namedSchema,
|
|
333
|
+
~insideComponentSchemas=false,
|
|
334
|
+
~availableSchemas=?,
|
|
335
|
+
~modulePrefix="",
|
|
336
|
+
~extractedTypes: array<GenerationContext.extractedType>=[],
|
|
337
|
+
) => {
|
|
338
|
+
let ctx = GenerationContext.make(
|
|
339
|
+
~path=`schema.${namedSchema.name}`,
|
|
340
|
+
~insideComponentSchemas,
|
|
341
|
+
~availableSchemas?,
|
|
342
|
+
~modulePrefix,
|
|
343
|
+
(),
|
|
344
|
+
)
|
|
345
|
+
let doc = switch namedSchema.description {
|
|
346
|
+
| Some(d) => CodegenUtils.generateDocComment(~description=d, ())
|
|
347
|
+
| None => ""
|
|
348
|
+
}
|
|
349
|
+
let extractedTypeMap = if Array.length(extractedTypes) > 0 { Some(extractedTypes) } else { None }
|
|
350
|
+
let mainSchema = generateSchemaWithContext(~ctx, ~depth=0, ~extractedTypeMap?, namedSchema.type_)
|
|
351
|
+
|
|
352
|
+
// Generate schemas for extracted auxiliary types
|
|
353
|
+
// Exclude the type being generated from the map to avoid self-reference
|
|
354
|
+
let extractedDefs = extractedTypes->Array.map(({typeName, irType}: GenerationContext.extractedType) => {
|
|
355
|
+
let auxCtx = GenerationContext.make(
|
|
356
|
+
~path=`schema.${typeName}`,
|
|
357
|
+
~insideComponentSchemas,
|
|
358
|
+
~availableSchemas?,
|
|
359
|
+
~modulePrefix,
|
|
360
|
+
(),
|
|
361
|
+
)
|
|
362
|
+
let filteredMap = extractedTypes->Array.filter(({typeName: tn}: GenerationContext.extractedType) => tn != typeName)
|
|
363
|
+
let auxExtractedTypeMap = if Array.length(filteredMap) > 0 { Some(filteredMap) } else { None }
|
|
364
|
+
let auxSchema = generateSchemaWithContext(~ctx=auxCtx, ~depth=0, ~extractedTypeMap=?auxExtractedTypeMap, irType)
|
|
365
|
+
`let ${typeName}Schema = ${auxSchema}`
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
let allDefs = Array.concat(extractedDefs, [`${doc}let ${namedSchema.name}Schema = ${mainSchema}`])
|
|
369
|
+
(
|
|
370
|
+
allDefs->Array.join("\n\n"),
|
|
371
|
+
ctx.warnings,
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
let generateAllSchemas = (~context: SchemaIR.schemaContext) => {
|
|
376
|
+
let warnings = []
|
|
377
|
+
let schemas =
|
|
378
|
+
Dict.valuesToArray(context.schemas)
|
|
379
|
+
->Array.toSorted((a, b) => String.compare(a.name, b.name))
|
|
380
|
+
->Array.map(s => {
|
|
381
|
+
let (code, w) = generateNamedSchema(~namedSchema=s)
|
|
382
|
+
warnings->Array.pushMany(w)
|
|
383
|
+
code
|
|
384
|
+
})
|
|
385
|
+
(schemas, warnings)
|
|
386
|
+
}
|