Skip to content

Commit 81f99c3

Browse files
committed
Implement nullable and enum
1 parent 16a0b94 commit 81f99c3

File tree

4 files changed

+191
-7
lines changed

4 files changed

+191
-7
lines changed

src/compileValueSchema.ts

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@ import { namedTypes, builders } from 'ast-types';
33
import { Compiler, ValidationErrorIdentifier } from './compiler';
44
import {
55
OpenAPIAnyOfSchema,
6+
OpenAPIArraySchema,
7+
OpenAPIBooleanSchema,
8+
OpenAPIEnumableSchema,
9+
OpenAPINullableSchema,
610
OpenAPINumberSchema,
711
OpenAPIObjectSchema,
812
OpenAPIStringSchema,
913
OpenAPIValueSchema,
1014
} from './types';
1115

16+
/**
17+
* Compile a JSON schema into a validation function.
18+
*/
1219
export function compileValueSchema(compiler: Compiler, schema: OpenAPIValueSchema) {
1320
if ('anyOf' in schema) {
1421
return compileAnyOfSchema(compiler, schema);
@@ -22,6 +29,10 @@ export function compileValueSchema(compiler: Compiler, schema: OpenAPIValueSchem
2229
return compileNumberSchema(compiler, schema);
2330
case 'string':
2431
return compileStringSchema(compiler, schema);
32+
case 'boolean':
33+
return compileBooleanSchema(compiler, schema);
34+
case 'array':
35+
return compileArraySchema(compiler, schema);
2536
}
2637
}
2738

@@ -74,6 +85,8 @@ function compileObjectSchema(compiler: Compiler, schema: OpenAPIObjectSchema) {
7485
const nodes: namedTypes.BlockStatement['body'] = [];
7586
const endNodes: namedTypes.BlockStatement['body'] = [];
7687

88+
nodes.push(...compileNullableCheck(compiler, schema, value));
89+
7790
// Define a variable to be all the keys in `value`
7891
const keysIdentifier = builders.identifier('keys');
7992

@@ -296,10 +309,28 @@ function compileObjectSchema(compiler: Compiler, schema: OpenAPIObjectSchema) {
296309
});
297310
}
298311

299-
function compileNumberSchema(compiler: Compiler, schema: OpenAPINumberSchema) {
312+
function compileArraySchema(compiler: Compiler, schema: OpenAPIArraySchema) {
300313
return compiler.defineValidationFunction(schema, ({ value, error }) => {
301314
const nodes: namedTypes.BlockStatement['body'] = [];
302315

316+
nodes.push(...compileNullableCheck(compiler, schema, value));
317+
318+
319+
nodes.push(builders.returnStatement(value));
320+
321+
return nodes;
322+
});
323+
}
324+
325+
function compileNumberSchema(compiler: Compiler, schema: OpenAPINumberSchema) {
326+
return compiler.defineValidationFunction(schema, ({ value, error }) => {
327+
const enumCheck = compileEnumableCheck(compiler, schema, value, error);
328+
if (enumCheck) {
329+
return enumCheck;
330+
}
331+
332+
const nodes: namedTypes.BlockStatement['body'] = [];
333+
nodes.push(...compileNullableCheck(compiler, schema, value));
303334
nodes.push(
304335
builders.ifStatement(
305336
builders.unaryExpression(
@@ -322,8 +353,14 @@ function compileNumberSchema(compiler: Compiler, schema: OpenAPINumberSchema) {
322353

323354
function compileStringSchema(compiler: Compiler, schema: OpenAPIStringSchema) {
324355
return compiler.defineValidationFunction(schema, ({ value, error }) => {
325-
const nodes: namedTypes.BlockStatement['body'] = [];
356+
const enumCheck = compileEnumableCheck(compiler, schema, value, error);
357+
if (enumCheck) {
358+
return enumCheck;
359+
}
360+
326361

362+
const nodes: namedTypes.BlockStatement['body'] = [];
363+
nodes.push(...compileNullableCheck(compiler, schema, value));
327364
nodes.push(
328365
builders.ifStatement(
329366
builders.unaryExpression(
@@ -343,3 +380,74 @@ function compileStringSchema(compiler: Compiler, schema: OpenAPIStringSchema) {
343380
return nodes;
344381
});
345382
}
383+
384+
function compileBooleanSchema(compiler: Compiler, schema: OpenAPIBooleanSchema) {
385+
return compiler.defineValidationFunction(schema, ({ value, error }) => {
386+
const enumCheck = compileEnumableCheck(compiler, schema, value, error);
387+
if (enumCheck) {
388+
return enumCheck;
389+
}
390+
391+
const nodes: namedTypes.BlockStatement['body'] = [];
392+
nodes.push(...compileNullableCheck(compiler, schema, value));
393+
nodes.push(
394+
builders.ifStatement(
395+
builders.unaryExpression(
396+
'!',
397+
builders.binaryExpression(
398+
'===',
399+
builders.unaryExpression('typeof', value),
400+
builders.literal('boolean'),
401+
),
402+
),
403+
builders.blockStatement([builders.returnStatement(error('Expected a boolean'))]),
404+
),
405+
);
406+
407+
nodes.push(builders.returnStatement(value));
408+
409+
return nodes;
410+
});
411+
}
412+
413+
function compileNullableCheck(compiler: Compiler, schema: OpenAPINullableSchema, value: namedTypes.Identifier) {
414+
if (!schema.nullable) {
415+
return [];
416+
}
417+
418+
return [
419+
builders.ifStatement(
420+
builders.binaryExpression('===', value, builders.identifier('null')),
421+
builders.blockStatement([builders.returnStatement(value)]),
422+
)
423+
]
424+
}
425+
426+
427+
function compileEnumableCheck(compiler: Compiler, schema: OpenAPIEnumableSchema, value: namedTypes.Identifier, error: (message: string) => namedTypes.NewExpression) {
428+
if (!schema.enum) {
429+
return null;
430+
}
431+
432+
return [
433+
builders.ifStatement(
434+
schema.enum.reduce((acc, val) => {
435+
const test = builders.binaryExpression('!==', value, builders.literal(val))
436+
437+
if (!acc) {
438+
return test;
439+
}
440+
441+
return builders.logicalExpression(
442+
'&&',
443+
acc,
444+
test
445+
)
446+
}, null as (namedTypes.BinaryExpression | namedTypes.LogicalExpression | null))!,
447+
builders.blockStatement([builders.returnStatement(
448+
error('Expected one of the enum value')
449+
)]),
450+
),
451+
builders.returnStatement(value)
452+
]
453+
}

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

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

3+
exports[`Nullable nullable: true 1`] = `
4+
"class ValidationError extends Error {
5+
constructor(path, message) {
6+
super(message);
7+
this.path = path;
8+
}
9+
}
10+
function obj0(path, value) {
11+
if (value === null) {
12+
return value;
13+
}
14+
if (!(typeof value === 'number')) {
15+
return new ValidationError(path, 'Expected a number');
16+
}
17+
return value;
18+
}"
19+
`;
20+
21+
exports[`String with enum 1`] = `
22+
"class ValidationError extends Error {
23+
constructor(path, message) {
24+
super(message);
25+
this.path = path;
26+
}
27+
}
28+
function obj0(path, value) {
29+
if (value !== 'a' && value !== 'b' && value !== 'c') {
30+
return new ValidationError(path, 'Expected one of the enum value');
31+
}
32+
return value;
33+
}"
34+
`;
35+
336
exports[`Objects with a required prop 1`] = `
437
"class ValidationError extends Error {
538
constructor(path, message) {

src/tests/compileValueSchema.test.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,29 @@ test('number', () => {
1010
expect(compiler.compile()).toMatchSnapshot();
1111
});
1212

13+
describe('Nullable', () => {
14+
test('nullable: true', () => {
15+
const compiler = new Compiler();
16+
compileValueSchema(compiler, {
17+
type: 'number',
18+
nullable: true,
19+
});
20+
expect(compiler.compile()).toMatchSnapshot();
21+
});
22+
});
23+
24+
describe('String', () => {
25+
test('with enum', () => {
26+
const compiler = new Compiler();
27+
compileValueSchema(compiler, {
28+
type: 'string',
29+
enum: ['a', 'b', 'c']
30+
});
31+
console.log(compiler.compile());
32+
expect(compiler.compile()).toMatchSnapshot();
33+
});
34+
});
35+
1336
describe('Objects', () => {
1437
test('with a required prop', () => {
1538
const compiler = new Compiler();
@@ -79,7 +102,6 @@ describe('Objects', () => {
79102
type: 'string',
80103
},
81104
});
82-
console.log(compiler.compile());
83105
expect(compiler.compile()).toMatchSnapshot();
84106
});
85107

src/types.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ export type OpenAPIValueSchema =
1919
| OpenAPIOneOfSchema
2020
| OpenAPIStringSchema
2121
| OpenAPINumberSchema
22-
| OpenAPIObjectSchema;
22+
| OpenAPIBooleanSchema
23+
| OpenAPIObjectSchema
24+
| OpenAPIArraySchema;
2325

2426
export interface OpenAPIAllOfSchema {
2527
allOf: OpenAPIValueSchema[];
@@ -33,16 +35,20 @@ export interface OpenAPIOneOfSchema {
3335
oneOf: OpenAPIValueSchema[];
3436
}
3537

36-
export interface OpenAPIStringSchema {
38+
export interface OpenAPIStringSchema extends OpenAPINullableSchema, OpenAPIEnumableSchema {
3739
type: 'string';
3840
format?: 'date' | 'uri';
3941
}
4042

41-
export interface OpenAPINumberSchema {
43+
export interface OpenAPINumberSchema extends OpenAPINullableSchema, OpenAPIEnumableSchema {
4244
type: 'number';
4345
}
4446

45-
export interface OpenAPIObjectSchema {
47+
export interface OpenAPIBooleanSchema extends OpenAPINullableSchema, OpenAPIEnumableSchema {
48+
type: 'boolean';
49+
}
50+
51+
export interface OpenAPIObjectSchema extends OpenAPINullableSchema {
4652
type: 'object';
4753
required?: string[];
4854
properties?: {
@@ -53,6 +59,21 @@ export interface OpenAPIObjectSchema {
5359
maxProperties?: number;
5460
}
5561

62+
export interface OpenAPIArraySchema extends OpenAPINullableSchema {
63+
type: 'array';
64+
items: OpenAPIValueSchema;
65+
minItems?: number;
66+
maxItems?: number;
67+
}
68+
69+
export interface OpenAPINullableSchema {
70+
nullable?: boolean;
71+
}
72+
73+
export interface OpenAPIEnumableSchema {
74+
enum?: (string | number | boolean)[];
75+
}
76+
5677
export interface OpenAPIRef {
5778
$ref: string;
5879
}

0 commit comments

Comments
 (0)