Skip to content

Commit 6a65d23

Browse files
committed
Add support for all JSONSchema!
1 parent c4cfcdc commit 6a65d23

File tree

7 files changed

+334
-18
lines changed

7 files changed

+334
-18
lines changed

run.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { Compiler } from "./src";
2+
3+
const spec = require('./openapi.all.json');
4+
const compiler = new Compiler(spec);
5+
compiler.build();
6+
console.log(compiler.compile());

src/compileValueSchema.ts

Lines changed: 136 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ import { namedTypes, builders } from 'ast-types';
22

33
import type { Compiler } from './compiler';
44
import {
5+
OpenAPIAllOfSchema,
56
OpenAPIAnyOfSchema,
67
OpenAPIArraySchema,
78
OpenAPIBooleanSchema,
89
OpenAPIEnumableSchema,
10+
OpenAPIIntegerSchema,
911
OpenAPINullableSchema,
1012
OpenAPINumberSchema,
1113
OpenAPIObjectSchema,
14+
OpenAPIOneOfSchema,
1215
OpenAPIStringSchema,
1316
OpenAPIValueSchema,
1417
} from './types';
@@ -26,10 +29,19 @@ export function compileValueSchema(compiler: Compiler, schema: OpenAPIValueSchem
2629
return compileAnyOfSchema(compiler, schema);
2730
}
2831

32+
if ('oneOf' in schema) {
33+
return compileOneOfSchema(compiler, schema);
34+
}
35+
36+
if ('allOf' in schema) {
37+
return compileAllOfSchema(compiler, schema);
38+
}
39+
2940
if ('type' in schema) {
3041
switch (schema.type) {
3142
case 'object':
3243
return compileObjectSchema(compiler, schema);
44+
case 'integer':
3345
case 'number':
3446
return compileNumberSchema(compiler, schema);
3547
case 'string':
@@ -38,10 +50,13 @@ export function compileValueSchema(compiler: Compiler, schema: OpenAPIValueSchem
3850
return compileBooleanSchema(compiler, schema);
3951
case 'array':
4052
return compileArraySchema(compiler, schema);
53+
default:
54+
throw new Error(`Unsupported schema: ${JSON.stringify(schema)}`);
4155
}
4256
}
4357

44-
throw new Error(`Unsupported schema: ${JSON.stringify(schema)}`);
58+
return compileAnySchema(compiler, schema);
59+
4560
}
4661

4762
function compileAnyOfSchema(compiler: Compiler, schema: OpenAPIAnyOfSchema) {
@@ -85,6 +100,113 @@ function compileAnyOfSchema(compiler: Compiler, schema: OpenAPIAnyOfSchema) {
85100
});
86101
}
87102

103+
function compileOneOfSchema(compiler: Compiler, schema: OpenAPIOneOfSchema) {
104+
return compiler.defineValidationFunction(schema, ({ value, path, error }) => {
105+
const nodes: namedTypes.BlockStatement['body'] = [];
106+
107+
// Declare the variable to use as a result, then iterate over each schema
108+
const resultIdentifier = builders.identifier('result');
109+
nodes.push(
110+
builders.variableDeclaration('let', [builders.variableDeclarator(resultIdentifier)]),
111+
);
112+
113+
schema.oneOf.forEach((subSchema, index) => {
114+
const fnIdentifier = compileValueSchema(compiler, subSchema);
115+
const altIdentifier = builders.identifier(`alt${index}`);
116+
117+
// Allocate a variable for the result of the schema alternative
118+
nodes.push(
119+
builders.variableDeclaration('const', [
120+
builders.variableDeclarator(
121+
altIdentifier,
122+
builders.callExpression(fnIdentifier, [path, value]),
123+
),
124+
]),
125+
);
126+
127+
nodes.push(
128+
builders.ifStatement(
129+
builders.unaryExpression(
130+
'!',
131+
builders.binaryExpression(
132+
'instanceof',
133+
altIdentifier,
134+
ValidationErrorIdentifier,
135+
),
136+
),
137+
builders.blockStatement([
138+
builders.expressionStatement(
139+
builders.assignmentExpression('=', resultIdentifier, altIdentifier),
140+
),
141+
...(index > 0
142+
? [
143+
builders.ifStatement(
144+
builders.binaryExpression(
145+
'!==',
146+
resultIdentifier,
147+
builders.identifier('undefined'),
148+
),
149+
builders.blockStatement([
150+
builders.returnStatement(
151+
error('Expected to only match one of the schemas'),
152+
),
153+
]),
154+
),
155+
]
156+
: []),
157+
]),
158+
),
159+
);
160+
});
161+
162+
nodes.push(builders.returnStatement(resultIdentifier));
163+
164+
return nodes;
165+
});
166+
}
167+
168+
function compileAllOfSchema(compiler: Compiler, schema: OpenAPIAllOfSchema) {
169+
return compiler.defineValidationFunction(schema, ({ value, path, error }) => {
170+
const nodes: namedTypes.BlockStatement['body'] = [];
171+
172+
const resultIdentifier = builders.identifier('result');
173+
nodes.push(
174+
builders.variableDeclaration('let', [builders.variableDeclarator(resultIdentifier, value)]),
175+
);
176+
177+
schema.allOf.forEach((subSchema, index) => {
178+
const fnIdentifier = compileValueSchema(compiler, subSchema);
179+
180+
nodes.push(
181+
builders.expressionStatement(
182+
builders.assignmentExpression(
183+
'=',
184+
resultIdentifier,
185+
builders.callExpression(fnIdentifier, [path, resultIdentifier]),
186+
),
187+
),
188+
);
189+
190+
nodes.push(
191+
builders.ifStatement(
192+
builders.binaryExpression(
193+
'instanceof',
194+
resultIdentifier,
195+
ValidationErrorIdentifier,
196+
),
197+
builders.blockStatement([
198+
builders.returnStatement(resultIdentifier),
199+
]),
200+
)
201+
)
202+
});
203+
204+
nodes.push(builders.returnStatement(resultIdentifier));
205+
206+
return nodes;
207+
});
208+
}
209+
88210
function compileObjectSchema(compiler: Compiler, schema: OpenAPIObjectSchema) {
89211
return compiler.defineValidationFunction(schema, ({ path, value, error }) => {
90212
const nodes: namedTypes.BlockStatement['body'] = [];
@@ -326,7 +448,10 @@ function compileArraySchema(compiler: Compiler, schema: OpenAPIArraySchema) {
326448
});
327449
}
328450

329-
function compileNumberSchema(compiler: Compiler, schema: OpenAPINumberSchema) {
451+
function compileNumberSchema(
452+
compiler: Compiler,
453+
schema: OpenAPINumberSchema | OpenAPIIntegerSchema,
454+
) {
330455
return compiler.defineValidationFunction(schema, ({ value, error }) => {
331456
const enumCheck = compileEnumableCheck(compiler, schema, value, error);
332457
if (enumCheck) {
@@ -413,6 +538,15 @@ function compileBooleanSchema(compiler: Compiler, schema: OpenAPIBooleanSchema)
413538
});
414539
}
415540

541+
542+
function compileAnySchema(compiler: Compiler, schema: object) {
543+
return compiler.defineValidationFunction(schema, ({ value }) => {
544+
return [
545+
builders.returnStatement(value)
546+
];
547+
});
548+
}
549+
416550
function compileNullableCheck(
417551
compiler: Compiler,
418552
schema: OpenAPINullableSchema,

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './compiler';

src/tests/__snapshots__/compileValueSchema.test.ts.snap

Lines changed: 130 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,35 @@
11
// Bun Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`Number basic 1`] = `
4+
"class ValidationError {
5+
constructor(path, message) {
6+
super(message);
7+
this.path = path;
8+
}
9+
}
10+
function obj0(path, value) {
11+
if (!(typeof value === 'number')) {
12+
return new ValidationError(path, 'Expected a number');
13+
}
14+
return value;
15+
}"
16+
`;
17+
18+
exports[`Integer basic 1`] = `
19+
"class ValidationError {
20+
constructor(path, message) {
21+
super(message);
22+
this.path = path;
23+
}
24+
}
25+
function obj0(path, value) {
26+
if (!(typeof value === 'number')) {
27+
return new ValidationError(path, 'Expected a number');
28+
}
29+
return value;
30+
}"
31+
`;
32+
333
exports[`Nullable nullable: true 1`] = `
434
"class ValidationError {
535
constructor(path, message) {
@@ -234,22 +264,39 @@ function obj0(path, value) {
234264
}"
235265
`;
236266

237-
exports[`number 1`] = `
267+
exports[`anyOf 1`] = `
238268
"class ValidationError {
239269
constructor(path, message) {
240270
super(message);
241271
this.path = path;
242272
}
243273
}
244-
function obj0(path, value) {
274+
function obj1(path, value) {
245275
if (!(typeof value === 'number')) {
246276
return new ValidationError(path, 'Expected a number');
247277
}
248278
return value;
279+
}
280+
function obj2(path, value) {
281+
if (!(typeof value === 'string')) {
282+
return new ValidationError(path, 'Expected a string');
283+
}
284+
return value;
285+
}
286+
function obj0(path, value) {
287+
const value0 = obj1(path, value);
288+
if (!(value0 instanceof ValidationError)) {
289+
return value0;
290+
}
291+
const value1 = obj2(path, value);
292+
if (!(value1 instanceof ValidationError)) {
293+
return value1;
294+
}
295+
return new ValidationError(path, 'Expected one of the anyOf schemas to match');
249296
}"
250297
`;
251298

252-
exports[`anyOf 1`] = `
299+
exports[`oneOf 1`] = `
253300
"class ValidationError {
254301
constructor(path, message) {
255302
super(message);
@@ -269,14 +316,87 @@ function obj2(path, value) {
269316
return value;
270317
}
271318
function obj0(path, value) {
272-
const value0 = obj1(path, value);
273-
if (!(value0 instanceof ValidationError)) {
274-
return value0;
319+
let result;
320+
const alt0 = obj1(path, value);
321+
if (!(alt0 instanceof ValidationError)) {
322+
result = alt0;
323+
}
324+
const alt1 = obj2(path, value);
325+
if (!(alt1 instanceof ValidationError)) {
326+
result = alt1;
327+
if (result !== undefined) {
328+
return new ValidationError(path, 'Expected to only match one of the schemas');
329+
}
275330
}
276-
const value1 = obj2(path, value);
277-
if (!(value1 instanceof ValidationError)) {
278-
return value1;
331+
return result;
332+
}"
333+
`;
334+
335+
exports[`allOf 1`] = `
336+
"class ValidationError {
337+
constructor(path, message) {
338+
super(message);
339+
this.path = path;
279340
}
280-
return new ValidationError(path, 'Expected one of the anyOf schemas to match');
341+
}
342+
function obj2(path, value) {
343+
if (!(typeof value === 'number')) {
344+
return new ValidationError(path, 'Expected a number');
345+
}
346+
return value;
347+
}
348+
function obj1(path, value) {
349+
const keys = new Set(Object.keys(value));
350+
const value0 = value['a'];
351+
if (value0 !== undefined) {
352+
const result0 = obj2([
353+
...path,
354+
'a'
355+
], value0);
356+
if (result0 instanceof ValidationError) {
357+
return result0;
358+
}
359+
value['a'] = result0;
360+
}
361+
if (keys.size > 0) {
362+
return new ValidationError(path, 'Unexpected properties');
363+
}
364+
return value;
365+
}
366+
function obj4(path, value) {
367+
if (!(typeof value === 'string')) {
368+
return new ValidationError(path, 'Expected a string');
369+
}
370+
return value;
371+
}
372+
function obj3(path, value) {
373+
const keys = new Set(Object.keys(value));
374+
const value0 = value['b'];
375+
if (value0 !== undefined) {
376+
const result0 = obj4([
377+
...path,
378+
'b'
379+
], value0);
380+
if (result0 instanceof ValidationError) {
381+
return result0;
382+
}
383+
value['b'] = result0;
384+
}
385+
if (keys.size > 0) {
386+
return new ValidationError(path, 'Unexpected properties');
387+
}
388+
return value;
389+
}
390+
function obj0(path, value) {
391+
let result = value;
392+
result = obj1(path, result);
393+
if (result instanceof ValidationError) {
394+
return result;
395+
}
396+
result = obj3(path, result);
397+
if (result instanceof ValidationError) {
398+
return result;
399+
}
400+
return result;
281401
}"
282402
`;

0 commit comments

Comments
 (0)