@f3liz/rescript-autogen-openapi 0.1.6 → 0.2.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.
- package/README.md +15 -2
- package/lib/es6/src/Codegen.d.ts +28 -0
- package/lib/es6/src/Types.d.ts +286 -0
- package/lib/es6/src/core/FileSystem.d.ts +4 -0
- package/lib/es6/src/core/Pipeline.d.ts +6 -0
- package/lib/es6/src/generators/IRToSuryGenerator.mjs +100 -27
- package/lib/es6/src/generators/IRToTypeGenerator.mjs +167 -22
- package/lib/es6/src/types/CodegenError.d.ts +66 -0
- package/lib/es6/src/types/Config.d.ts +31 -0
- package/package.json +13 -8
- package/rescript.json +6 -0
- package/src/Codegen.res +9 -0
- package/src/Types.res +27 -0
- package/src/core/FileSystem.res +1 -0
- package/src/core/Pipeline.res +1 -0
- package/src/generators/IRToSuryGenerator.res +87 -35
- package/src/generators/IRToTypeGenerator.res +121 -46
- package/src/types/CodegenError.res +3 -0
- package/src/types/Config.res +6 -0
|
@@ -75,7 +75,23 @@ function generateTypeWithContext(ctx, depthOpt, irType) {
|
|
|
75
75
|
}
|
|
76
76
|
case "Union" :
|
|
77
77
|
let types = irType._0;
|
|
78
|
-
let
|
|
78
|
+
let nonNullTypes = types.filter(t => {
|
|
79
|
+
if (typeof t !== "object") {
|
|
80
|
+
return t !== "Null";
|
|
81
|
+
}
|
|
82
|
+
if (t.TAG !== "Literal") {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
let tmp = t._0;
|
|
86
|
+
return typeof tmp === "object";
|
|
87
|
+
});
|
|
88
|
+
let hasNull = nonNullTypes.length < types.length;
|
|
89
|
+
if (hasNull && nonNullTypes.length === 1) {
|
|
90
|
+
let inner = recurse(nonNullTypes[0]);
|
|
91
|
+
return `option<` + inner + `>`;
|
|
92
|
+
}
|
|
93
|
+
let effectiveTypes = hasNull ? nonNullTypes : types;
|
|
94
|
+
let match = Stdlib_Array.reduce(effectiveTypes, [
|
|
79
95
|
false,
|
|
80
96
|
false,
|
|
81
97
|
undefined,
|
|
@@ -100,10 +116,10 @@ function generateTypeWithContext(ctx, depthOpt, irType) {
|
|
|
100
116
|
}
|
|
101
117
|
});
|
|
102
118
|
let arrayItemType = match[2];
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (
|
|
119
|
+
let result;
|
|
120
|
+
if (match[0] && match[1] && effectiveTypes.length === 2 && SchemaIR.equals(Stdlib_Option.getOr(arrayItemType, "Unknown"), Stdlib_Option.getOr(match[3], "Unknown"))) {
|
|
121
|
+
result = `array<` + recurse(Stdlib_Option.getOr(arrayItemType, "Unknown")) + `>`;
|
|
122
|
+
} else if (effectiveTypes.every(t => {
|
|
107
123
|
if (typeof t !== "object") {
|
|
108
124
|
return false;
|
|
109
125
|
}
|
|
@@ -116,8 +132,8 @@ function generateTypeWithContext(ctx, depthOpt, irType) {
|
|
|
116
132
|
} else {
|
|
117
133
|
return tmp.TAG === "StringLiteral";
|
|
118
134
|
}
|
|
119
|
-
}) &&
|
|
120
|
-
let variants =
|
|
135
|
+
}) && effectiveTypes.length !== 0 && effectiveTypes.length <= 50) {
|
|
136
|
+
let variants = effectiveTypes.map(t => {
|
|
121
137
|
if (typeof t !== "object") {
|
|
122
138
|
return "#Unknown";
|
|
123
139
|
}
|
|
@@ -131,26 +147,155 @@ function generateTypeWithContext(ctx, depthOpt, irType) {
|
|
|
131
147
|
return `#` + JsConvertCase.toPascalCase(s._0);
|
|
132
148
|
}
|
|
133
149
|
}).join(" | ");
|
|
134
|
-
|
|
150
|
+
result = `[` + variants + `]`;
|
|
151
|
+
} else if (effectiveTypes.length !== 0) {
|
|
152
|
+
let hasPrimitives = {
|
|
153
|
+
contents: false
|
|
154
|
+
};
|
|
155
|
+
let variantCases = effectiveTypes.map((t, i) => {
|
|
156
|
+
let match;
|
|
157
|
+
let exit = 0;
|
|
158
|
+
if (typeof t !== "object") {
|
|
159
|
+
if (t === "Boolean") {
|
|
160
|
+
hasPrimitives.contents = true;
|
|
161
|
+
match = [
|
|
162
|
+
"Bool",
|
|
163
|
+
"bool"
|
|
164
|
+
];
|
|
165
|
+
} else {
|
|
166
|
+
exit = 1;
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
switch (t.TAG) {
|
|
170
|
+
case "String" :
|
|
171
|
+
hasPrimitives.contents = true;
|
|
172
|
+
match = [
|
|
173
|
+
"String",
|
|
174
|
+
"string"
|
|
175
|
+
];
|
|
176
|
+
break;
|
|
177
|
+
case "Number" :
|
|
178
|
+
hasPrimitives.contents = true;
|
|
179
|
+
match = [
|
|
180
|
+
"Float",
|
|
181
|
+
"float"
|
|
182
|
+
];
|
|
183
|
+
break;
|
|
184
|
+
case "Integer" :
|
|
185
|
+
hasPrimitives.contents = true;
|
|
186
|
+
match = [
|
|
187
|
+
"Int",
|
|
188
|
+
"int"
|
|
189
|
+
];
|
|
190
|
+
break;
|
|
191
|
+
case "Array" :
|
|
192
|
+
match = [
|
|
193
|
+
"Array",
|
|
194
|
+
`array<` + recurse(t.items) + `>`
|
|
195
|
+
];
|
|
196
|
+
break;
|
|
197
|
+
case "Object" :
|
|
198
|
+
match = [
|
|
199
|
+
"Object",
|
|
200
|
+
recurse(t)
|
|
201
|
+
];
|
|
202
|
+
break;
|
|
203
|
+
case "Reference" :
|
|
204
|
+
let ref = t._0;
|
|
205
|
+
let name = Stdlib_Option.getOr(ref.split("/")[ref.split("/").length - 1 | 0], "");
|
|
206
|
+
match = [
|
|
207
|
+
JsConvertCase.toPascalCase(name),
|
|
208
|
+
recurse(t)
|
|
209
|
+
];
|
|
210
|
+
break;
|
|
211
|
+
default:
|
|
212
|
+
exit = 1;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (exit === 1) {
|
|
216
|
+
match = [
|
|
217
|
+
`V` + i.toString(),
|
|
218
|
+
recurse(t)
|
|
219
|
+
];
|
|
220
|
+
}
|
|
221
|
+
return ` | ` + match[0] + `(` + match[1] + `)`;
|
|
222
|
+
});
|
|
223
|
+
let unboxedAttr = hasPrimitives.contents ? "@unboxed " : "";
|
|
224
|
+
result = unboxedAttr + `[\n` + variantCases.join("\n") + `\n]`;
|
|
225
|
+
} else {
|
|
226
|
+
result = "JSON.t";
|
|
227
|
+
}
|
|
228
|
+
if (hasNull) {
|
|
229
|
+
return `option<` + result + `>`;
|
|
230
|
+
} else {
|
|
231
|
+
return result;
|
|
135
232
|
}
|
|
136
|
-
GenerationContext.addWarning(ctx, {
|
|
137
|
-
TAG: "ComplexUnionSimplified",
|
|
138
|
-
location: ctx.path,
|
|
139
|
-
types: types.map(SchemaIR.toString).join(" | ")
|
|
140
|
-
});
|
|
141
|
-
return "JSON.t";
|
|
142
233
|
case "Intersection" :
|
|
143
234
|
let types$1 = irType._0;
|
|
144
|
-
if (types$1.every(t =>
|
|
235
|
+
if (types$1.every(t => {
|
|
236
|
+
if (typeof t !== "object") {
|
|
237
|
+
return false;
|
|
238
|
+
} else {
|
|
239
|
+
return t.TAG === "Reference";
|
|
240
|
+
}
|
|
241
|
+
}) && types$1.length !== 0) {
|
|
145
242
|
return recurse(Stdlib_Option.getOr(types$1[types$1.length - 1 | 0], "Unknown"));
|
|
146
|
-
} else {
|
|
147
|
-
GenerationContext.addWarning(ctx, {
|
|
148
|
-
TAG: "IntersectionNotFullySupported",
|
|
149
|
-
location: ctx.path,
|
|
150
|
-
note: "Complex intersection"
|
|
151
|
-
});
|
|
152
|
-
return "JSON.t";
|
|
153
243
|
}
|
|
244
|
+
let match$1 = Stdlib_Array.reduce(types$1, [
|
|
245
|
+
[],
|
|
246
|
+
[]
|
|
247
|
+
], (param, t) => {
|
|
248
|
+
let nonObj = param[1];
|
|
249
|
+
let props = param[0];
|
|
250
|
+
if (typeof t !== "object") {
|
|
251
|
+
return [
|
|
252
|
+
props,
|
|
253
|
+
nonObj.concat([t])
|
|
254
|
+
];
|
|
255
|
+
} else if (t.TAG === "Object") {
|
|
256
|
+
return [
|
|
257
|
+
props.concat(t.properties),
|
|
258
|
+
nonObj
|
|
259
|
+
];
|
|
260
|
+
} else {
|
|
261
|
+
return [
|
|
262
|
+
props,
|
|
263
|
+
nonObj.concat([t])
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
let nonObjectTypes = match$1[1];
|
|
268
|
+
let objectProps = match$1[0];
|
|
269
|
+
if (objectProps.length !== 0 && nonObjectTypes.length === 0) {
|
|
270
|
+
let fields$1 = objectProps.map(param => {
|
|
271
|
+
let name = param[0];
|
|
272
|
+
let typeCode = recurse(param[1]);
|
|
273
|
+
let finalType = param[2] ? typeCode : `option<` + typeCode + `>`;
|
|
274
|
+
let camelName = JsConvertCase.toCamelCase(name);
|
|
275
|
+
let escapedName = CodegenUtils.escapeKeyword(camelName);
|
|
276
|
+
let aliasAnnotation = escapedName !== name ? `@as("` + name + `") ` : "";
|
|
277
|
+
return ` ` + aliasAnnotation + escapedName + `: ` + finalType + `,`;
|
|
278
|
+
}).join("\n");
|
|
279
|
+
return `{\n` + fields$1 + `\n}`;
|
|
280
|
+
}
|
|
281
|
+
if (nonObjectTypes.length !== 0 && objectProps.length === 0) {
|
|
282
|
+
return recurse(Stdlib_Option.getOr(types$1[types$1.length - 1 | 0], "Unknown"));
|
|
283
|
+
}
|
|
284
|
+
GenerationContext.addWarning(ctx, {
|
|
285
|
+
TAG: "IntersectionNotFullySupported",
|
|
286
|
+
location: ctx.path,
|
|
287
|
+
note: "Mixed object/non-object intersection"
|
|
288
|
+
});
|
|
289
|
+
let fields$2 = objectProps.map(param => {
|
|
290
|
+
let name = param[0];
|
|
291
|
+
let typeCode = recurse(param[1]);
|
|
292
|
+
let finalType = param[2] ? typeCode : `option<` + typeCode + `>`;
|
|
293
|
+
let camelName = JsConvertCase.toCamelCase(name);
|
|
294
|
+
let escapedName = CodegenUtils.escapeKeyword(camelName);
|
|
295
|
+
let aliasAnnotation = escapedName !== name ? `@as("` + name + `") ` : "";
|
|
296
|
+
return ` ` + aliasAnnotation + escapedName + `: ` + finalType + `,`;
|
|
297
|
+
}).join("\n");
|
|
298
|
+
return `{\n` + fields$2 + `\n}`;
|
|
154
299
|
case "Reference" :
|
|
155
300
|
let ref = irType._0;
|
|
156
301
|
let available = ctx.availableSchemas;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type context = {
|
|
2
|
+
readonly path: string;
|
|
3
|
+
readonly operation: string;
|
|
4
|
+
readonly schema: (undefined | unknown);
|
|
5
|
+
};
|
|
6
|
+
export type t = {
|
|
7
|
+
TAG: "SpecResolutionError";
|
|
8
|
+
readonly url: string;
|
|
9
|
+
readonly message: string;
|
|
10
|
+
} | {
|
|
11
|
+
TAG: "SchemaParseError";
|
|
12
|
+
readonly context: context;
|
|
13
|
+
readonly reason: string;
|
|
14
|
+
} | {
|
|
15
|
+
TAG: "ReferenceError";
|
|
16
|
+
readonly ref: string;
|
|
17
|
+
readonly context: context;
|
|
18
|
+
} | {
|
|
19
|
+
TAG: "ValidationError";
|
|
20
|
+
readonly schema: string;
|
|
21
|
+
readonly input: unknown;
|
|
22
|
+
readonly issues: string[];
|
|
23
|
+
} | {
|
|
24
|
+
TAG: "CircularSchemaError";
|
|
25
|
+
readonly ref: string;
|
|
26
|
+
readonly depth: number;
|
|
27
|
+
readonly path: string;
|
|
28
|
+
} | {
|
|
29
|
+
TAG: "FileWriteError";
|
|
30
|
+
readonly filePath: string;
|
|
31
|
+
readonly message: string;
|
|
32
|
+
} | {
|
|
33
|
+
TAG: "InvalidConfigError";
|
|
34
|
+
readonly field: string;
|
|
35
|
+
readonly message: string;
|
|
36
|
+
} | {
|
|
37
|
+
TAG: "UnknownError";
|
|
38
|
+
readonly message: string;
|
|
39
|
+
readonly context: (undefined | context);
|
|
40
|
+
};
|
|
41
|
+
export type Warning_t = {
|
|
42
|
+
TAG: "FallbackToJson";
|
|
43
|
+
readonly reason: string;
|
|
44
|
+
readonly context: context;
|
|
45
|
+
} | {
|
|
46
|
+
TAG: "UnsupportedFeature";
|
|
47
|
+
readonly feature: string;
|
|
48
|
+
readonly fallback: string;
|
|
49
|
+
readonly location: string;
|
|
50
|
+
} | {
|
|
51
|
+
TAG: "DepthLimitReached";
|
|
52
|
+
readonly depth: number;
|
|
53
|
+
readonly path: string;
|
|
54
|
+
} | {
|
|
55
|
+
TAG: "MissingSchema";
|
|
56
|
+
readonly ref: string;
|
|
57
|
+
readonly location: string;
|
|
58
|
+
} | {
|
|
59
|
+
TAG: "IntersectionNotFullySupported";
|
|
60
|
+
readonly location: string;
|
|
61
|
+
readonly note: string;
|
|
62
|
+
} | {
|
|
63
|
+
TAG: "ComplexUnionSimplified";
|
|
64
|
+
readonly location: string;
|
|
65
|
+
readonly types: string;
|
|
66
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type generationStrategy = "Separate" | "SharedBase";
|
|
2
|
+
export type breakingChangeHandling = "Error" | "Warn" | "Ignore";
|
|
3
|
+
export type forkSpecConfig = {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly specPath: string;
|
|
6
|
+
};
|
|
7
|
+
export type generationTargets = {
|
|
8
|
+
readonly rescriptApi: boolean;
|
|
9
|
+
readonly rescriptWrapper: boolean;
|
|
10
|
+
readonly typescriptDts: boolean;
|
|
11
|
+
readonly typescriptWrapper: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type t = {
|
|
14
|
+
readonly specPath: string;
|
|
15
|
+
readonly forkSpecs: (undefined | forkSpecConfig[]);
|
|
16
|
+
readonly outputDir: string;
|
|
17
|
+
readonly strategy: generationStrategy;
|
|
18
|
+
readonly modulePerTag: boolean;
|
|
19
|
+
readonly includeTags: (undefined | string[]);
|
|
20
|
+
readonly excludeTags: (undefined | string[]);
|
|
21
|
+
readonly generateDiffReport: boolean;
|
|
22
|
+
readonly breakingChangeHandling: breakingChangeHandling;
|
|
23
|
+
readonly generateDocOverrides: (undefined | boolean);
|
|
24
|
+
readonly docOverrideDir: (undefined | string);
|
|
25
|
+
readonly targets: (undefined | generationTargets);
|
|
26
|
+
readonly dtsOutputDir: (undefined | string);
|
|
27
|
+
readonly wrapperOutputDir: (undefined | string);
|
|
28
|
+
readonly baseInstanceName: (undefined | string);
|
|
29
|
+
readonly baseModulePrefix: (undefined | string);
|
|
30
|
+
};
|
|
31
|
+
export declare const make: (specPath: string, outputDir: string, strategy: (undefined | generationStrategy), modulePerTag: (undefined | boolean), includeTags: (undefined | string[]), excludeTags: (undefined | string[]), generateDiffReport: (undefined | boolean), breakingChangeHandling: (undefined | breakingChangeHandling), forkSpecs: (undefined | forkSpecConfig[]), generateDocOverrides: (undefined | boolean), docOverrideDir: (undefined | string), targets: (undefined | generationTargets), dtsOutputDir: (undefined | string), wrapperOutputDir: (undefined | string), baseInstanceName: (undefined | string), baseModulePrefix: (undefined | string), _17: void) => t;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@f3liz/rescript-autogen-openapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Generate ReScript code with Sury schemas from OpenAPI 3.1 specs. Supports multiple forks with diff/merge capabilities.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rescript",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"license": "MPL-2.0",
|
|
17
17
|
"files": [
|
|
18
18
|
"src/**/*.res",
|
|
19
|
-
"lib/es6/**/*.mjs",
|
|
19
|
+
"lib/es6/src/**/*.mjs",
|
|
20
|
+
"lib/es6/src/**/*.d.ts",
|
|
20
21
|
"rescript.json",
|
|
21
22
|
"README.md",
|
|
22
23
|
"LICENSE"
|
|
@@ -25,23 +26,27 @@
|
|
|
25
26
|
"url": "https://github.com/f3liz-dev/rescript-autogen-openapi"
|
|
26
27
|
},
|
|
27
28
|
"scripts": {
|
|
28
|
-
"build": "rescript",
|
|
29
|
+
"build": "rescript && node scripts/generate-dts.mjs",
|
|
29
30
|
"clean": "rescript clean",
|
|
30
31
|
"watch": "rescript build -w",
|
|
31
|
-
"test": "node --test"
|
|
32
|
+
"test": "node --test",
|
|
33
|
+
"prepublishOnly": "npm run build && npm test"
|
|
32
34
|
},
|
|
33
35
|
"main": "lib/es6/src/Codegen.mjs",
|
|
36
|
+
"types": "lib/es6/src/Codegen.d.ts",
|
|
34
37
|
"type": "module",
|
|
35
38
|
"dependencies": {
|
|
36
39
|
"@readme/openapi-parser": "^5.5.0",
|
|
37
40
|
"js-convert-case": "^4.2.0",
|
|
38
|
-
"pathe": "^2.0.3"
|
|
39
|
-
"sury": "11.0.0-alpha.4"
|
|
41
|
+
"pathe": "^2.0.3"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
|
-
"rescript": "^12.1.0"
|
|
44
|
+
"rescript": "^12.1.0",
|
|
45
|
+
"sury": "11.0.0-alpha.4",
|
|
46
|
+
"typescript": "^5.9.3"
|
|
43
47
|
},
|
|
44
48
|
"peerDependencies": {
|
|
45
|
-
"rescript": "^12.0.0"
|
|
49
|
+
"rescript": "^12.0.0",
|
|
50
|
+
"sury": ">=11.0.0-alpha.4"
|
|
46
51
|
}
|
|
47
52
|
}
|
package/rescript.json
CHANGED
package/src/Codegen.res
CHANGED
|
@@ -7,6 +7,7 @@ open Types
|
|
|
7
7
|
@val external promiseAll: array<promise<'a>> => promise<array<'a>> = "Promise.all"
|
|
8
8
|
|
|
9
9
|
// Generate code from a single spec (pure - returns data)
|
|
10
|
+
@genType
|
|
10
11
|
let generateSingleSpecPure = (~spec: openAPISpec, ~config: generationConfig): result<Pipeline.t, codegenError> => {
|
|
11
12
|
try {
|
|
12
13
|
let targets = config.targets->Option.getOr(Config.defaultTargets())
|
|
@@ -54,6 +55,7 @@ let generateSingleSpecPure = (~spec: openAPISpec, ~config: generationConfig): re
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
// Generate code from a single spec (with side effects)
|
|
58
|
+
@genType
|
|
57
59
|
let generateSingleSpec = async (~spec: openAPISpec, ~config: generationConfig): generationResult => {
|
|
58
60
|
switch generateSingleSpecPure(~spec, ~config) {
|
|
59
61
|
| Result.Error(err) => Result.Error(err)
|
|
@@ -140,6 +142,7 @@ let processForkPure = (~baseSpec: openAPISpec, ~baseEndpoints: array<endpoint>,
|
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
// Generate code from multiple specs (pure - returns data)
|
|
145
|
+
@genType
|
|
143
146
|
let generateMultiSpecPure = (~baseSpec: openAPISpec, ~forkSpecs: array<forkSpec>, ~config: generationConfig): result<Pipeline.t, codegenError> => {
|
|
144
147
|
try {
|
|
145
148
|
let baseEndpoints = OpenAPIParser.getAllEndpoints(baseSpec)
|
|
@@ -169,6 +172,7 @@ let generateMultiSpecPure = (~baseSpec: openAPISpec, ~forkSpecs: array<forkSpec>
|
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
// Generate code from multiple specs (with side effects)
|
|
175
|
+
@genType
|
|
172
176
|
let generateMultiSpec = async (~baseSpec: openAPISpec, ~forkSpecs: array<forkSpec>, ~config: generationConfig): generationResult =>
|
|
173
177
|
switch generateMultiSpecPure(~baseSpec, ~forkSpecs, ~config) {
|
|
174
178
|
| Result.Error(err) => Result.Error(err)
|
|
@@ -179,6 +183,7 @@ let generateMultiSpec = async (~baseSpec: openAPISpec, ~forkSpecs: array<forkSpe
|
|
|
179
183
|
}
|
|
180
184
|
|
|
181
185
|
// Compare two specs and generate diff report
|
|
186
|
+
@genType
|
|
182
187
|
let compareSpecs = async (~baseSpec, ~forkSpec, ~baseName="base", ~forkName="fork", ~outputPath=?) => {
|
|
183
188
|
let diff = SpecDiffer.generateDiff(~baseSpec, ~forkSpec, ~baseEndpoints=OpenAPIParser.getAllEndpoints(baseSpec), ~forkEndpoints=OpenAPIParser.getAllEndpoints(forkSpec))
|
|
184
189
|
outputPath->Option.forEach(path => {
|
|
@@ -188,6 +193,7 @@ let compareSpecs = async (~baseSpec, ~forkSpec, ~baseName="base", ~forkName="for
|
|
|
188
193
|
}
|
|
189
194
|
|
|
190
195
|
// Main generation function
|
|
196
|
+
@genType
|
|
191
197
|
let generate = async (config: generationConfig): generationResult => {
|
|
192
198
|
switch await SchemaRefResolver.resolve(config.specPath) {
|
|
193
199
|
| Result.Error(message) => Result.Error(SpecResolutionError({url: config.specPath, message}))
|
|
@@ -207,6 +213,7 @@ let generate = async (config: generationConfig): generationResult => {
|
|
|
207
213
|
}
|
|
208
214
|
}
|
|
209
215
|
|
|
216
|
+
@genType
|
|
210
217
|
let createDefaultConfig = (url, outputDir): generationConfig => ({
|
|
211
218
|
specPath: url, outputDir, strategy: SharedBase, includeTags: None, excludeTags: None,
|
|
212
219
|
modulePerTag: true, generateDiffReport: true, breakingChangeHandling: Warn,
|
|
@@ -215,8 +222,10 @@ let createDefaultConfig = (url, outputDir): generationConfig => ({
|
|
|
215
222
|
baseInstanceName: None, baseModulePrefix: None,
|
|
216
223
|
})
|
|
217
224
|
|
|
225
|
+
@genType
|
|
218
226
|
let generateFromUrl = async (~url, ~outputDir, ~config=?) =>
|
|
219
227
|
await generate({...config->Option.getOr(createDefaultConfig(url, outputDir)), specPath: url})
|
|
220
228
|
|
|
229
|
+
@genType
|
|
221
230
|
let generateFromFile = async (~filePath, ~outputDir, ~config=?) =>
|
|
222
231
|
await generate({...config->Option.getOr(createDefaultConfig(filePath, outputDir)), specPath: filePath})
|
package/src/Types.res
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Types.res - Core OpenAPI and generation types (refactored & compact)
|
|
4
4
|
|
|
5
5
|
// ============= JSON Schema Types =============
|
|
6
|
+
@genType
|
|
6
7
|
type rec jsonSchemaType =
|
|
7
8
|
| String
|
|
8
9
|
| Number
|
|
@@ -13,6 +14,7 @@ type rec jsonSchemaType =
|
|
|
13
14
|
| Null
|
|
14
15
|
| Unknown
|
|
15
16
|
|
|
17
|
+
@genType
|
|
16
18
|
and jsonSchema = {
|
|
17
19
|
@as("type") type_: option<jsonSchemaType>,
|
|
18
20
|
properties: option<dict<jsonSchema>>,
|
|
@@ -34,25 +36,30 @@ and jsonSchema = {
|
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
// ============= OpenAPI 3.1 Types =============
|
|
39
|
+
@genType
|
|
37
40
|
type httpMethod = [#GET | #POST | #PUT | #PATCH | #DELETE | #HEAD | #OPTIONS]
|
|
38
41
|
|
|
42
|
+
@genType
|
|
39
43
|
type mediaType = {
|
|
40
44
|
schema: option<jsonSchema>,
|
|
41
45
|
example: option<JSON.t>,
|
|
42
46
|
examples: option<dict<JSON.t>>,
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
@genType
|
|
45
50
|
type requestBody = {
|
|
46
51
|
description: option<string>,
|
|
47
52
|
content: dict<mediaType>,
|
|
48
53
|
required: option<bool>,
|
|
49
54
|
}
|
|
50
55
|
|
|
56
|
+
@genType
|
|
51
57
|
type response = {
|
|
52
58
|
description: string,
|
|
53
59
|
content: option<dict<mediaType>>,
|
|
54
60
|
}
|
|
55
61
|
|
|
62
|
+
@genType
|
|
56
63
|
type parameter = {
|
|
57
64
|
name: string,
|
|
58
65
|
@as("in") in_: string,
|
|
@@ -61,6 +68,7 @@ type parameter = {
|
|
|
61
68
|
schema: option<jsonSchema>,
|
|
62
69
|
}
|
|
63
70
|
|
|
71
|
+
@genType
|
|
64
72
|
type operation = {
|
|
65
73
|
operationId: option<string>,
|
|
66
74
|
summary: option<string>,
|
|
@@ -71,6 +79,7 @@ type operation = {
|
|
|
71
79
|
parameters: option<array<parameter>>,
|
|
72
80
|
}
|
|
73
81
|
|
|
82
|
+
@genType
|
|
74
83
|
type endpoint = {
|
|
75
84
|
path: string,
|
|
76
85
|
method: string,
|
|
@@ -83,6 +92,7 @@ type endpoint = {
|
|
|
83
92
|
parameters: option<array<parameter>>,
|
|
84
93
|
}
|
|
85
94
|
|
|
95
|
+
@genType
|
|
86
96
|
type pathItem = {
|
|
87
97
|
get: option<operation>,
|
|
88
98
|
post: option<operation>,
|
|
@@ -94,14 +104,17 @@ type pathItem = {
|
|
|
94
104
|
parameters: option<array<parameter>>,
|
|
95
105
|
}
|
|
96
106
|
|
|
107
|
+
@genType
|
|
97
108
|
type components = {schemas: option<dict<jsonSchema>>}
|
|
98
109
|
|
|
110
|
+
@genType
|
|
99
111
|
type info = {
|
|
100
112
|
title: string,
|
|
101
113
|
version: string,
|
|
102
114
|
description: option<string>,
|
|
103
115
|
}
|
|
104
116
|
|
|
117
|
+
@genType
|
|
105
118
|
type openAPISpec = {
|
|
106
119
|
openapi: string,
|
|
107
120
|
info: info,
|
|
@@ -111,32 +124,40 @@ type openAPISpec = {
|
|
|
111
124
|
|
|
112
125
|
// ============= Re-exports from focused modules =============
|
|
113
126
|
// Config types
|
|
127
|
+
@genType
|
|
114
128
|
type generationStrategy = Config.generationStrategy =
|
|
115
129
|
| Separate
|
|
116
130
|
| SharedBase
|
|
117
131
|
|
|
132
|
+
@genType
|
|
118
133
|
type breakingChangeHandling = Config.breakingChangeHandling = | Error | Warn | Ignore
|
|
134
|
+
@genType
|
|
119
135
|
type forkSpecConfig = Config.forkSpecConfig = {name: string, specPath: string}
|
|
136
|
+
@genType
|
|
120
137
|
type generationTargets = Config.generationTargets = {
|
|
121
138
|
rescriptApi: bool,
|
|
122
139
|
rescriptWrapper: bool,
|
|
123
140
|
typescriptDts: bool,
|
|
124
141
|
typescriptWrapper: bool,
|
|
125
142
|
}
|
|
143
|
+
@genType
|
|
126
144
|
type generationConfig = Config.t
|
|
127
145
|
|
|
146
|
+
@genType
|
|
128
147
|
type forkSpec = {
|
|
129
148
|
name: string,
|
|
130
149
|
spec: openAPISpec,
|
|
131
150
|
}
|
|
132
151
|
|
|
133
152
|
// Error types - use `=` syntax to re-export constructors
|
|
153
|
+
@genType
|
|
134
154
|
type errorContext = CodegenError.context = {
|
|
135
155
|
path: string,
|
|
136
156
|
operation: string,
|
|
137
157
|
schema: option<JSON.t>,
|
|
138
158
|
}
|
|
139
159
|
|
|
160
|
+
@genType
|
|
140
161
|
type codegenError = CodegenError.t =
|
|
141
162
|
| SpecResolutionError({url: string, message: string})
|
|
142
163
|
| SchemaParseError({context: errorContext, reason: string})
|
|
@@ -147,6 +168,7 @@ type codegenError = CodegenError.t =
|
|
|
147
168
|
| InvalidConfigError({field: string, message: string})
|
|
148
169
|
| UnknownError({message: string, context: option<errorContext>})
|
|
149
170
|
|
|
171
|
+
@genType
|
|
150
172
|
type warning = CodegenError.Warning.t =
|
|
151
173
|
| FallbackToJson({reason: string, context: errorContext})
|
|
152
174
|
| UnsupportedFeature({feature: string, fallback: string, location: string})
|
|
@@ -156,6 +178,7 @@ type warning = CodegenError.Warning.t =
|
|
|
156
178
|
| ComplexUnionSimplified({location: string, types: string})
|
|
157
179
|
|
|
158
180
|
// ============= Diff Types =============
|
|
181
|
+
@genType
|
|
159
182
|
type endpointDiff = {
|
|
160
183
|
path: string,
|
|
161
184
|
method: string,
|
|
@@ -164,11 +187,13 @@ type endpointDiff = {
|
|
|
164
187
|
breakingChange: bool,
|
|
165
188
|
}
|
|
166
189
|
|
|
190
|
+
@genType
|
|
167
191
|
type schemaDiff = {
|
|
168
192
|
name: string,
|
|
169
193
|
breakingChange: bool,
|
|
170
194
|
}
|
|
171
195
|
|
|
196
|
+
@genType
|
|
172
197
|
type specDiff = {
|
|
173
198
|
addedEndpoints: array<endpoint>,
|
|
174
199
|
removedEndpoints: array<endpoint>,
|
|
@@ -179,12 +204,14 @@ type specDiff = {
|
|
|
179
204
|
}
|
|
180
205
|
|
|
181
206
|
// ============= Generation Result Types =============
|
|
207
|
+
@genType
|
|
182
208
|
type generationSuccess = {
|
|
183
209
|
generatedFiles: array<string>,
|
|
184
210
|
diff: option<specDiff>,
|
|
185
211
|
warnings: array<warning>,
|
|
186
212
|
}
|
|
187
213
|
|
|
214
|
+
@genType
|
|
188
215
|
type generationResult = result<generationSuccess, codegenError>
|
|
189
216
|
|
|
190
217
|
// ============= Re-export helper modules =============
|
package/src/core/FileSystem.res
CHANGED