openapi-contract-kit 0.0.1 → 0.0.3
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/CHANGELOG.md +10 -0
- package/README.md +13 -5
- package/dist/bin/openapi-contract-kit.d.ts +2 -0
- package/dist/bin/openapi-contract-kit.js +3 -0
- package/dist/src/cli/formatOutput.d.ts +6 -0
- package/dist/src/cli/formatOutput.js +32 -0
- package/dist/src/generator/config.d.ts +3 -0
- package/dist/src/generator/config.js +72 -0
- package/dist/src/generator/documents.d.ts +11 -0
- package/dist/src/generator/documents.js +92 -0
- package/dist/src/generator/emitEndpoints.d.ts +2 -0
- package/{src/generator/emitEndpoints.mjs → dist/src/generator/emitEndpoints.js} +48 -80
- package/dist/src/generator/emitMakers.d.ts +2 -0
- package/dist/src/generator/emitMakers.js +328 -0
- package/dist/src/generator/emitTypes.d.ts +3 -0
- package/dist/src/generator/emitTypes.js +87 -0
- package/dist/src/generator/generateOpenApiRuntime.d.ts +3 -0
- package/dist/src/generator/generateOpenApiRuntime.js +41 -0
- package/dist/src/generator/model.d.ts +2 -0
- package/dist/src/generator/model.js +278 -0
- package/dist/src/generator/schemaModel.d.ts +9 -0
- package/dist/src/generator/schemaModel.js +408 -0
- package/dist/src/generator/types.d.ts +82 -0
- package/dist/src/generator/writeOutput.d.ts +5 -0
- package/dist/src/generator/writeOutput.js +141 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/runtime.d.ts +14 -0
- package/dist/src/runtime.js +1 -0
- package/package.json +30 -18
- package/bin/openapi-contract-kit.mjs +0 -5
- package/src/generator/config.mjs +0 -92
- package/src/generator/documents.mjs +0 -119
- package/src/generator/emitMakers.mjs +0 -459
- package/src/generator/emitTypes.mjs +0 -104
- package/src/generator/generateOpenApiRuntime.mjs +0 -48
- package/src/generator/model.mjs +0 -415
- package/src/generator/schemaModel.mjs +0 -497
- package/src/generator/writeOutput.mjs +0 -196
- package/src/index.d.ts +0 -16
- package/src/index.mjs +0 -4
- package/src/runtime.d.ts +0 -14
- /package/{src/runtime.mjs → dist/src/generator/types.js} +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
function indent(lines, spaces = 2) {
|
|
2
|
+
const prefix = ' '.repeat(spaces);
|
|
3
|
+
return lines.map((line) => (line.length === 0 ? line : `${prefix}${line}`));
|
|
4
|
+
}
|
|
5
|
+
function typeExpression(type, value = 'value') {
|
|
6
|
+
switch (type) {
|
|
7
|
+
case 'array':
|
|
8
|
+
return `Array.isArray(${value})`;
|
|
9
|
+
case 'boolean':
|
|
10
|
+
return `typeof ${value} === 'boolean'`;
|
|
11
|
+
case 'integer':
|
|
12
|
+
return `typeof ${value} === 'number' && Number.isFinite(${value}) && Number.isInteger(${value})`;
|
|
13
|
+
case 'null':
|
|
14
|
+
return `${value} === null`;
|
|
15
|
+
case 'number':
|
|
16
|
+
return `typeof ${value} === 'number' && Number.isFinite(${value})`;
|
|
17
|
+
case 'object':
|
|
18
|
+
return `typeof ${value} === 'object' && ${value} !== null && !Array.isArray(${value})`;
|
|
19
|
+
case 'string':
|
|
20
|
+
return `typeof ${value} === 'string'`;
|
|
21
|
+
default: {
|
|
22
|
+
const exhaustive = type;
|
|
23
|
+
throw new Error(`Unsupported normalised schema type "${exhaustive}"`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
class MakerRenderer {
|
|
28
|
+
#counter = 0;
|
|
29
|
+
#functions = [];
|
|
30
|
+
#nodeNames = new WeakMap();
|
|
31
|
+
#nodeSignatures = new Map();
|
|
32
|
+
#referencedSchemas = new Set();
|
|
33
|
+
#schemaNames;
|
|
34
|
+
constructor(model) {
|
|
35
|
+
this.#schemaNames = new Set(model.schemas.map(({ name }) => name));
|
|
36
|
+
}
|
|
37
|
+
render(name, schema, runtimeImport) {
|
|
38
|
+
const rootValidator = this.#emitNode(schema);
|
|
39
|
+
const makerImports = [...this.#referencedSchemas]
|
|
40
|
+
.sort()
|
|
41
|
+
.map((reference) => `import { make${reference} } from './${reference}';`)
|
|
42
|
+
.join('\n');
|
|
43
|
+
return `/**
|
|
44
|
+
* Generated by openapi-contract-kit.
|
|
45
|
+
* Do not edit directly.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import type { Result, ValidationIssue, ValidationPath } from ${JSON.stringify(runtimeImport)};
|
|
49
|
+
import type { ${name} } from '../quickpay-api';
|
|
50
|
+
${makerImports}${makerImports.length > 0 ? '\n' : ''}
|
|
51
|
+
|
|
52
|
+
${this.#functions.join('\n\n')}
|
|
53
|
+
|
|
54
|
+
function validate${name}(
|
|
55
|
+
value: unknown,
|
|
56
|
+
path: ValidationPath,
|
|
57
|
+
errors: ValidationIssue[]
|
|
58
|
+
): value is ${name} {
|
|
59
|
+
return ${rootValidator}(value, path, errors);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function make${name}(input: unknown): Result<${name}> {
|
|
63
|
+
const errors: ValidationIssue[] = [];
|
|
64
|
+
|
|
65
|
+
if (!validate${name}(input, [], errors)) {
|
|
66
|
+
return { ok: false, errors };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { ok: true, value: input };
|
|
70
|
+
}
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
#emitNode(schema) {
|
|
74
|
+
const existing = this.#nodeNames.get(schema);
|
|
75
|
+
if (existing !== undefined) {
|
|
76
|
+
return existing;
|
|
77
|
+
}
|
|
78
|
+
const signature = JSON.stringify(schema, (key, value) => key === 'location' ? undefined : value);
|
|
79
|
+
const matchingNode = this.#nodeSignatures.get(signature);
|
|
80
|
+
if (matchingNode !== undefined) {
|
|
81
|
+
this.#nodeNames.set(schema, matchingNode);
|
|
82
|
+
return matchingNode;
|
|
83
|
+
}
|
|
84
|
+
const functionName = `validateNode${this.#counter}`;
|
|
85
|
+
this.#counter += 1;
|
|
86
|
+
this.#nodeNames.set(schema, functionName);
|
|
87
|
+
this.#nodeSignatures.set(signature, functionName);
|
|
88
|
+
const childFunctions = this.#createChildFunctions(schema);
|
|
89
|
+
const body = this.#renderNodeBody(schema, childFunctions);
|
|
90
|
+
this.#functions.push(`function ${functionName}(\n value: unknown,\n path: ValidationPath,\n errors: ValidationIssue[]\n): boolean {\n${indent(body).join('\n')}\n}`);
|
|
91
|
+
return functionName;
|
|
92
|
+
}
|
|
93
|
+
#createChildFunctions(schema) {
|
|
94
|
+
if (schema.booleanSchema !== null) {
|
|
95
|
+
return {
|
|
96
|
+
additionalProperties: null,
|
|
97
|
+
allOf: [],
|
|
98
|
+
anyOf: [],
|
|
99
|
+
items: null,
|
|
100
|
+
oneOf: [],
|
|
101
|
+
properties: [],
|
|
102
|
+
reference: null,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
additionalProperties: typeof schema.additionalProperties === 'boolean'
|
|
107
|
+
? null
|
|
108
|
+
: this.#emitNode(schema.additionalProperties),
|
|
109
|
+
allOf: schema.allOf.map((child) => this.#emitNode(child)),
|
|
110
|
+
anyOf: schema.anyOf.map((child) => this.#emitNode(child)),
|
|
111
|
+
items: schema.items === null ? null : this.#emitNode(schema.items),
|
|
112
|
+
oneOf: schema.oneOf.map((child) => this.#emitNode(child)),
|
|
113
|
+
properties: schema.properties.map((property) => ({
|
|
114
|
+
...property,
|
|
115
|
+
functionName: this.#emitNode(property.schema),
|
|
116
|
+
})),
|
|
117
|
+
reference: schema.reference === null
|
|
118
|
+
? null
|
|
119
|
+
: this.#registerReference(schema.reference),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
#registerReference(reference) {
|
|
123
|
+
if (!this.#schemaNames.has(reference)) {
|
|
124
|
+
throw new Error(`Unknown schema model reference "${reference}"`);
|
|
125
|
+
}
|
|
126
|
+
this.#referencedSchemas.add(reference);
|
|
127
|
+
return reference;
|
|
128
|
+
}
|
|
129
|
+
#renderNodeBody(schema, children) {
|
|
130
|
+
const lines = ['const errorCount = errors.length;'];
|
|
131
|
+
if (schema.booleanSchema !== null) {
|
|
132
|
+
if (schema.booleanSchema) {
|
|
133
|
+
lines.push('return true;');
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
lines.push(`errors.push({ path, keyword: 'falseSchema', message: 'Value is not allowed' });`, 'return false;');
|
|
137
|
+
}
|
|
138
|
+
return lines;
|
|
139
|
+
}
|
|
140
|
+
if (children.reference !== null) {
|
|
141
|
+
lines.push(`const referenceResult = make${children.reference}(value);`, `if (!referenceResult.ok) {`, ...indent([
|
|
142
|
+
`for (const issue of referenceResult.errors) {`,
|
|
143
|
+
...indent([
|
|
144
|
+
`errors.push({ ...issue, path: [...path, ...issue.path] });`,
|
|
145
|
+
]),
|
|
146
|
+
`}`,
|
|
147
|
+
]), `}`);
|
|
148
|
+
}
|
|
149
|
+
if (schema.types !== null) {
|
|
150
|
+
const expected = schema.types
|
|
151
|
+
.map((type) => typeExpression(type))
|
|
152
|
+
.join(' || ');
|
|
153
|
+
lines.push(`if (!(${expected})) {`, ...indent([
|
|
154
|
+
`errors.push({ path, keyword: 'type', message: ${JSON.stringify(`Expected ${schema.types.join(' or ')}`)} });`,
|
|
155
|
+
'return false;',
|
|
156
|
+
]), '}');
|
|
157
|
+
}
|
|
158
|
+
if (schema.constValue !== undefined) {
|
|
159
|
+
lines.push(`if (!Object.is(value, ${JSON.stringify(schema.constValue)})) {`, ...indent([
|
|
160
|
+
`errors.push({ path, keyword: 'const', message: 'Expected the documented constant value' });`,
|
|
161
|
+
]), '}');
|
|
162
|
+
}
|
|
163
|
+
if (schema.enumValues !== null) {
|
|
164
|
+
const values = schema.enumValues
|
|
165
|
+
.map((value) => JSON.stringify(value))
|
|
166
|
+
.join(', ');
|
|
167
|
+
lines.push(`if (![${values}].some((candidate) => Object.is(candidate, value))) {`, ...indent([
|
|
168
|
+
`errors.push({ path, keyword: 'enum', message: 'Expected a documented enum value' });`,
|
|
169
|
+
]), '}');
|
|
170
|
+
}
|
|
171
|
+
for (const child of children.allOf) {
|
|
172
|
+
lines.push(`${child}(value, path, errors);`);
|
|
173
|
+
}
|
|
174
|
+
this.#renderUnionConstraint(lines, 'anyOf', children.anyOf, false);
|
|
175
|
+
this.#renderUnionConstraint(lines, 'oneOf', children.oneOf, true);
|
|
176
|
+
this.#renderStringConstraints(lines, schema);
|
|
177
|
+
this.#renderNumberConstraints(lines, schema);
|
|
178
|
+
this.#renderArrayConstraints(lines, children.items);
|
|
179
|
+
this.#renderObjectConstraints(lines, schema, children);
|
|
180
|
+
lines.push('return errors.length === errorCount;');
|
|
181
|
+
return lines;
|
|
182
|
+
}
|
|
183
|
+
#renderUnionConstraint(lines, keyword, branches, isExclusive) {
|
|
184
|
+
if (branches.length === 0) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const variable = `${keyword}Matches`;
|
|
188
|
+
lines.push(`let ${variable} = 0;`);
|
|
189
|
+
for (const [index, branch] of branches.entries()) {
|
|
190
|
+
const errorsName = `${keyword}Errors${index}`;
|
|
191
|
+
lines.push(`const ${errorsName}: ValidationIssue[] = [];`, `if (${branch}(value, path, ${errorsName})) {`, ...indent([`${variable} += 1;`]), '}');
|
|
192
|
+
}
|
|
193
|
+
const invalidExpression = isExclusive
|
|
194
|
+
? `${variable} !== 1`
|
|
195
|
+
: `${variable} === 0`;
|
|
196
|
+
const expectation = isExclusive ? 'exactly one' : 'at least one';
|
|
197
|
+
lines.push(`if (${invalidExpression}) {`, ...indent([
|
|
198
|
+
`errors.push({ path, keyword: '${keyword}', message: 'Expected ${expectation} matching branch' });`,
|
|
199
|
+
]), '}');
|
|
200
|
+
}
|
|
201
|
+
#renderStringConstraints(lines, schema) {
|
|
202
|
+
if (schema.format === null &&
|
|
203
|
+
schema.maxLength === null &&
|
|
204
|
+
schema.minLength === null &&
|
|
205
|
+
schema.pattern === null) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
lines.push(`if (typeof value === 'string') {`);
|
|
209
|
+
const checks = [];
|
|
210
|
+
if (schema.minLength !== null) {
|
|
211
|
+
checks.push(`if (Array.from(value).length < ${schema.minLength}) {`, ...indent([
|
|
212
|
+
`errors.push({ path, keyword: 'minLength', message: 'String is shorter than ${schema.minLength} characters' });`,
|
|
213
|
+
]), '}');
|
|
214
|
+
}
|
|
215
|
+
if (schema.maxLength !== null) {
|
|
216
|
+
checks.push(`if (Array.from(value).length > ${schema.maxLength}) {`, ...indent([
|
|
217
|
+
`errors.push({ path, keyword: 'maxLength', message: 'String is longer than ${schema.maxLength} characters' });`,
|
|
218
|
+
]), '}');
|
|
219
|
+
}
|
|
220
|
+
if (schema.pattern !== null) {
|
|
221
|
+
checks.push(`if (!new RegExp(${JSON.stringify(schema.pattern)}, 'u').test(value)) {`, ...indent([
|
|
222
|
+
`errors.push({ path, keyword: 'pattern', message: 'String does not match the documented pattern' });`,
|
|
223
|
+
]), '}');
|
|
224
|
+
}
|
|
225
|
+
if (schema.format === 'email') {
|
|
226
|
+
checks.push(`if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/u.test(value)) {`, ...indent([
|
|
227
|
+
`errors.push({ path, keyword: 'format', message: 'Expected email format' });`,
|
|
228
|
+
]), '}');
|
|
229
|
+
}
|
|
230
|
+
lines.push(...indent(checks), '}');
|
|
231
|
+
}
|
|
232
|
+
#renderNumberConstraints(lines, schema) {
|
|
233
|
+
if (schema.exclusiveMaximum === null &&
|
|
234
|
+
schema.exclusiveMinimum === null &&
|
|
235
|
+
schema.maximum === null &&
|
|
236
|
+
schema.minimum === null) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
lines.push(`if (typeof value === 'number' && Number.isFinite(value)) {`);
|
|
240
|
+
const checks = [];
|
|
241
|
+
const addCheck = (expression, keyword, message) => {
|
|
242
|
+
checks.push(`if (${expression}) {`, ...indent([
|
|
243
|
+
`errors.push({ path, keyword: '${keyword}', message: ${JSON.stringify(message)} });`,
|
|
244
|
+
]), '}');
|
|
245
|
+
};
|
|
246
|
+
if (schema.minimum !== null) {
|
|
247
|
+
addCheck(`value < ${schema.minimum}`, 'minimum', `Number must be at least ${schema.minimum}`);
|
|
248
|
+
}
|
|
249
|
+
if (schema.maximum !== null) {
|
|
250
|
+
addCheck(`value > ${schema.maximum}`, 'maximum', `Number must be at most ${schema.maximum}`);
|
|
251
|
+
}
|
|
252
|
+
if (schema.exclusiveMinimum !== null) {
|
|
253
|
+
addCheck(`value <= ${schema.exclusiveMinimum}`, 'exclusiveMinimum', `Number must be greater than ${schema.exclusiveMinimum}`);
|
|
254
|
+
}
|
|
255
|
+
if (schema.exclusiveMaximum !== null) {
|
|
256
|
+
addCheck(`value >= ${schema.exclusiveMaximum}`, 'exclusiveMaximum', `Number must be less than ${schema.exclusiveMaximum}`);
|
|
257
|
+
}
|
|
258
|
+
lines.push(...indent(checks), '}');
|
|
259
|
+
}
|
|
260
|
+
#renderArrayConstraints(lines, itemValidator) {
|
|
261
|
+
if (itemValidator === null) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
lines.push('if (Array.isArray(value)) {', ...indent([
|
|
265
|
+
'for (let index = 0; index < value.length; index += 1) {',
|
|
266
|
+
...indent(['const item = value[index];']),
|
|
267
|
+
...indent([`${itemValidator}(item, [...path, index], errors);`]),
|
|
268
|
+
'}',
|
|
269
|
+
]), '}');
|
|
270
|
+
}
|
|
271
|
+
#renderObjectConstraints(lines, schema, children) {
|
|
272
|
+
if (children.properties.length === 0 &&
|
|
273
|
+
children.additionalProperties === null &&
|
|
274
|
+
schema.additionalProperties !== false) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
lines.push(`if (typeof value === 'object' && value !== null && !Array.isArray(value)) {`);
|
|
278
|
+
const checks = [];
|
|
279
|
+
for (const property of children.properties) {
|
|
280
|
+
const hasProperty = `Object.prototype.hasOwnProperty.call(value, ${JSON.stringify(property.name)})`;
|
|
281
|
+
const propertyPath = `[...path, ${JSON.stringify(property.name)}]`;
|
|
282
|
+
if (property.required) {
|
|
283
|
+
checks.push(`if (!${hasProperty}) {`, ...indent([
|
|
284
|
+
`errors.push({ path: ${propertyPath}, keyword: 'required', message: 'Required property is missing' });`,
|
|
285
|
+
]), '} else {', ...indent([
|
|
286
|
+
`${property.functionName}(Reflect.get(value, ${JSON.stringify(property.name)}), ${propertyPath}, errors);`,
|
|
287
|
+
]), '}');
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
checks.push(`if (${hasProperty}) {`, ...indent([
|
|
291
|
+
`${property.functionName}(Reflect.get(value, ${JSON.stringify(property.name)}), ${propertyPath}, errors);`,
|
|
292
|
+
]), '}');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (schema.additionalProperties === false ||
|
|
296
|
+
children.additionalProperties !== null) {
|
|
297
|
+
const propertyNames = children.properties.map(({ name }) => name);
|
|
298
|
+
checks.push('for (const key of Object.keys(value)) {');
|
|
299
|
+
const isAdditionalProperty = propertyNames.length === 0
|
|
300
|
+
? 'true'
|
|
301
|
+
: `![${propertyNames
|
|
302
|
+
.map((name) => JSON.stringify(name))
|
|
303
|
+
.join(', ')}].includes(key)`;
|
|
304
|
+
const additionalChecks = [`if (${isAdditionalProperty}) {`];
|
|
305
|
+
if (schema.additionalProperties === false) {
|
|
306
|
+
additionalChecks.push(...indent([
|
|
307
|
+
`errors.push({ path: [...path, key], keyword: 'additionalProperties', message: 'Unknown property is not allowed' });`,
|
|
308
|
+
]));
|
|
309
|
+
}
|
|
310
|
+
else if (children.additionalProperties !== null) {
|
|
311
|
+
additionalChecks.push(...indent([
|
|
312
|
+
`${children.additionalProperties}(Reflect.get(value, key), [...path, key], errors);`,
|
|
313
|
+
]));
|
|
314
|
+
}
|
|
315
|
+
additionalChecks.push('}');
|
|
316
|
+
checks.push(...indent(additionalChecks), '}');
|
|
317
|
+
}
|
|
318
|
+
lines.push(...indent(checks), '}');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
export function emitSchemaMakers(model, config) {
|
|
322
|
+
const files = new Map();
|
|
323
|
+
for (const { name, schema } of model.schemas) {
|
|
324
|
+
const renderer = new MakerRenderer(model);
|
|
325
|
+
files.set(`schemas/${name}.ts`, renderer.render(name, schema, config.runtimeImport));
|
|
326
|
+
}
|
|
327
|
+
return files;
|
|
328
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
function renderLiteral(value) {
|
|
2
|
+
return JSON.stringify(value);
|
|
3
|
+
}
|
|
4
|
+
function parenthesise(type) {
|
|
5
|
+
return type.includes(' | ') || type.includes(' & ') ? `(${type})` : type;
|
|
6
|
+
}
|
|
7
|
+
function renderObjectType(schema) {
|
|
8
|
+
if (schema.properties.length === 0) {
|
|
9
|
+
if (typeof schema.additionalProperties !== 'boolean') {
|
|
10
|
+
return `Record<string, ${renderSchemaType(schema.additionalProperties)}>`;
|
|
11
|
+
}
|
|
12
|
+
return 'Record<string, unknown>';
|
|
13
|
+
}
|
|
14
|
+
const properties = schema.properties.map((property) => {
|
|
15
|
+
const optional = property.required ? '' : '?';
|
|
16
|
+
return ` ${JSON.stringify(property.name)}${optional}: ${renderSchemaType(property.schema)};`;
|
|
17
|
+
});
|
|
18
|
+
return `{\n${properties.join('\n')}\n}`;
|
|
19
|
+
}
|
|
20
|
+
function renderBasicType(type, schema) {
|
|
21
|
+
switch (type) {
|
|
22
|
+
case 'array': {
|
|
23
|
+
const itemType = schema.items === null ? 'unknown' : renderSchemaType(schema.items);
|
|
24
|
+
return `Array<${itemType}>`;
|
|
25
|
+
}
|
|
26
|
+
case 'boolean':
|
|
27
|
+
return 'boolean';
|
|
28
|
+
case 'integer':
|
|
29
|
+
case 'number':
|
|
30
|
+
return 'number';
|
|
31
|
+
case 'null':
|
|
32
|
+
return 'null';
|
|
33
|
+
case 'object':
|
|
34
|
+
return renderObjectType(schema);
|
|
35
|
+
case 'string':
|
|
36
|
+
return 'string';
|
|
37
|
+
default: {
|
|
38
|
+
const exhaustive = type;
|
|
39
|
+
throw new Error(`Unsupported normalised schema type "${exhaustive}"`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function renderSchemaType(schema) {
|
|
44
|
+
if (schema.booleanSchema !== null) {
|
|
45
|
+
return schema.booleanSchema ? 'unknown' : 'never';
|
|
46
|
+
}
|
|
47
|
+
const intersections = [];
|
|
48
|
+
if (schema.reference !== null) {
|
|
49
|
+
intersections.push(schema.reference);
|
|
50
|
+
}
|
|
51
|
+
if (schema.constValue !== undefined) {
|
|
52
|
+
intersections.push(renderLiteral(schema.constValue));
|
|
53
|
+
}
|
|
54
|
+
else if (schema.enumValues !== null) {
|
|
55
|
+
intersections.push(schema.enumValues.map(renderLiteral).join(' | '));
|
|
56
|
+
}
|
|
57
|
+
else if (schema.types !== null) {
|
|
58
|
+
intersections.push(schema.types.map((type) => renderBasicType(type, schema)).join(' | '));
|
|
59
|
+
}
|
|
60
|
+
for (const child of schema.allOf) {
|
|
61
|
+
intersections.push(renderSchemaType(child));
|
|
62
|
+
}
|
|
63
|
+
if (schema.oneOf.length > 0) {
|
|
64
|
+
intersections.push(schema.oneOf.map(renderSchemaType).join(' | '));
|
|
65
|
+
}
|
|
66
|
+
if (schema.anyOf.length > 0) {
|
|
67
|
+
intersections.push(schema.anyOf.map(renderSchemaType).join(' | '));
|
|
68
|
+
}
|
|
69
|
+
const meaningful = intersections.filter((type) => type !== 'unknown');
|
|
70
|
+
if (meaningful.length === 0) {
|
|
71
|
+
return 'unknown';
|
|
72
|
+
}
|
|
73
|
+
return meaningful.map(parenthesise).join(' & ');
|
|
74
|
+
}
|
|
75
|
+
export function emitRootTypes(model) {
|
|
76
|
+
const declarations = model.schemas.flatMap(({ name, schema }) => [
|
|
77
|
+
`export type ${name} = ${renderSchemaType(schema)};`,
|
|
78
|
+
`export type ShapeOf${name} = ${name};`,
|
|
79
|
+
]);
|
|
80
|
+
return `/**
|
|
81
|
+
* Generated by openapi-contract-kit.
|
|
82
|
+
* Do not edit directly.
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
${declarations.join('\n\n')}
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { GenerateOpenApiRuntimeOptions, GenerateOpenApiRuntimeResult } from './types.js';
|
|
2
|
+
export declare function generateOpenApiRuntime(options?: GenerateOpenApiRuntimeOptions): Promise<GenerateOpenApiRuntimeResult>;
|
|
3
|
+
export declare function main(argv?: readonly string[]): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { formatFailureOutput, formatSuccessOutput, } from '../cli/formatOutput.js';
|
|
3
|
+
import { resolveGeneratorConfig } from './config.js';
|
|
4
|
+
import { emitEndpointModules } from './emitEndpoints.js';
|
|
5
|
+
import { emitSchemaMakers } from './emitMakers.js';
|
|
6
|
+
import { emitRootTypes } from './emitTypes.js';
|
|
7
|
+
import { buildOpenApiModel } from './model.js';
|
|
8
|
+
import { writeGeneratedOutput } from './writeOutput.js';
|
|
9
|
+
export async function generateOpenApiRuntime(options = {}) {
|
|
10
|
+
const argv = options.argv ?? [];
|
|
11
|
+
const cwd = options.cwd ?? process.cwd();
|
|
12
|
+
const config = await resolveGeneratorConfig({ argv, cwd });
|
|
13
|
+
const model = await buildOpenApiModel(config.specPath);
|
|
14
|
+
const files = new Map([
|
|
15
|
+
['quickpay-api.ts', emitRootTypes(model)],
|
|
16
|
+
...emitSchemaMakers(model, config),
|
|
17
|
+
...emitEndpointModules(model, config),
|
|
18
|
+
]);
|
|
19
|
+
await writeGeneratedOutput(config.outDir, files, {
|
|
20
|
+
protectedPaths: [config.configPath, config.specPath],
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
operationCount: model.operations.length,
|
|
24
|
+
operationNames: model.operations.map(({ name }) => name),
|
|
25
|
+
schemaCount: model.schemas.length,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
29
|
+
try {
|
|
30
|
+
const result = await generateOpenApiRuntime({ argv });
|
|
31
|
+
process.stdout.write(formatSuccessOutput(result, process.stdout));
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
35
|
+
process.stderr.write(formatFailureOutput(message, process.stderr));
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
40
|
+
await main();
|
|
41
|
+
}
|