Skip to content

Commit b46b102

Browse files
committed
Add support for arrays
1 parent 442c301 commit b46b102

11 files changed

+500
-43
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ CLI to compile an OpenAPI specification as JavaScript validation file, optimized
1212
- [ ] `format`
1313
- [x] `pattern`
1414
- JSONSchema `array` validation of:
15-
- [ ] `minItems`
16-
- [ ] `maxItems`
17-
- [ ] `uniqueItems`
15+
- [x] `minItems`
16+
- [x] `maxItems`
17+
- [x] `uniqueItems`
1818
- JSONSchema `integer`
1919
- [ ] no float

src/compileValueSchema.ts

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,11 +451,149 @@ function compileObjectSchema(compiler: Compiler, schema: OpenAPIObjectSchema) {
451451
}
452452

453453
function compileArraySchema(compiler: Compiler, schema: OpenAPIArraySchema) {
454-
return compiler.declareValidationFunction(schema, ({ value, error }) => {
454+
return compiler.declareValidationFunction(schema, ({ path, value, error }) => {
455455
const nodes: namedTypes.BlockStatement['body'] = [];
456456

457457
nodes.push(...compileNullableCheck(compiler, schema, value));
458458

459+
nodes.push(
460+
builders.ifStatement(
461+
builders.unaryExpression('!',
462+
builders.callExpression(
463+
builders.memberExpression(
464+
builders.identifier('Array'),
465+
builders.identifier('isArray'),
466+
),
467+
[value],
468+
)
469+
),
470+
builders.blockStatement([builders.returnStatement(error('Expected an array'))]),
471+
),
472+
);
473+
474+
if (schema.minItems) {
475+
nodes.push(
476+
builders.ifStatement(
477+
builders.binaryExpression(
478+
'<',
479+
builders.memberExpression(value, builders.identifier('length')),
480+
builders.literal(schema.minItems),
481+
),
482+
builders.blockStatement([
483+
builders.returnStatement(
484+
error(`Expected at least ${schema.minItems} items`),
485+
),
486+
]),
487+
),
488+
);
489+
}
490+
491+
if (schema.maxItems) {
492+
nodes.push(
493+
builders.ifStatement(
494+
builders.binaryExpression(
495+
'>',
496+
builders.memberExpression(value, builders.identifier('length')),
497+
builders.literal(schema.maxItems),
498+
),
499+
builders.blockStatement([
500+
builders.returnStatement(
501+
error(`Expected at most ${schema.maxItems} items`),
502+
),
503+
]),
504+
),
505+
);
506+
}
507+
508+
const valueSet = builders.identifier('valueSet');
509+
510+
if (schema.uniqueItems) {
511+
nodes.push(
512+
builders.variableDeclaration('const', [
513+
builders.variableDeclarator(
514+
valueSet,
515+
builders.newExpression(builders.identifier('Set'), []),
516+
),
517+
]),
518+
)
519+
}
520+
521+
const index = builders.identifier('i');
522+
const itemResult = builders.identifier('itemResult');
523+
const fnSchema = compileValueSchema(compiler, schema.items);
524+
525+
nodes.push(
526+
builders.forStatement(
527+
builders.variableDeclaration('let', [
528+
builders.variableDeclarator(index, builders.literal(0)),
529+
]),
530+
builders.binaryExpression(
531+
'<',
532+
index,
533+
builders.memberExpression(value, builders.identifier('length')),
534+
),
535+
builders.updateExpression('++', index, false),
536+
builders.blockStatement([
537+
builders.variableDeclaration('const', [
538+
builders.variableDeclarator(
539+
itemResult,
540+
builders.callExpression(
541+
fnSchema,
542+
[
543+
builders.arrayExpression([
544+
builders.spreadElement(path),
545+
index,
546+
]),
547+
builders.memberExpression(value, index, true),
548+
],
549+
),
550+
),
551+
]),
552+
...(schema.uniqueItems ? [
553+
builders.ifStatement(
554+
builders.callExpression(
555+
builders.memberExpression(
556+
valueSet,
557+
builders.identifier('has'),
558+
),
559+
[itemResult],
560+
),
561+
builders.blockStatement([
562+
builders.returnStatement(
563+
error(`Expected unique items`),
564+
),
565+
])
566+
),
567+
builders.expressionStatement(
568+
builders.callExpression(
569+
builders.memberExpression(
570+
valueSet,
571+
builders.identifier('add'),
572+
),
573+
[itemResult],
574+
)
575+
)
576+
] : []),
577+
builders.ifStatement(
578+
builders.binaryExpression(
579+
'instanceof',
580+
itemResult,
581+
ValidationErrorIdentifier,
582+
),
583+
builders.blockStatement([builders.returnStatement(itemResult)]),
584+
),
585+
builders.expressionStatement(
586+
builders.assignmentExpression(
587+
'=',
588+
builders.memberExpression(value, index, true),
589+
itemResult,
590+
),
591+
)
592+
])
593+
)
594+
)
595+
596+
459597
nodes.push(builders.returnStatement(value));
460598

461599
return nodes;

src/compiler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,9 @@ export class Compiler {
4848
const identifier = builders.identifier(hash);
4949

5050
if (!this.processedHashes.has(hash)) {
51+
this.processedHashes.add(hash);
5152
const fn = gen(identifier);
5253
this.declareGlobally(fn);
53-
54-
this.processedHashes.add(hash);
5554
}
5655

5756
return identifier;
@@ -136,6 +135,7 @@ export class Compiler {
136135
}
137136

138137
const hashValue = hash(input);
138+
139139
if (this.hashes.has(hashValue)) {
140140
return this.hashes.get(hashValue)!;
141141
}

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

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

33
exports[`With body without 1`] = `
4-
"class ValidationError {
4+
"export function validateRequest(request) {
5+
return new ValidationError([], 'no operation match path');
6+
}
7+
class ValidationError extends Error {
58
constructor(path, message) {
69
super(message);
710
this.path = path;
@@ -16,19 +19,25 @@ function obj0(path, value) {
1619
`;
1720

1821
exports[`With body required 1`] = `
19-
"class ValidationError {
22+
"export function validateRequest(request) {
23+
return new ValidationError([], 'no operation match path');
24+
}
25+
class ValidationError extends Error {
2026
constructor(path, message) {
2127
super(message);
2228
this.path = path;
2329
}
2430
}
2531
function obj2(path, value) {
26-
if (!(typeof value === 'number')) {
32+
if (typeof value !== 'number') {
2733
return new ValidationError(path, 'Expected a number');
2834
}
2935
return value;
3036
}
3137
function obj1(path, value) {
38+
if (typeof value !== 'object' || value === null) {
39+
return new ValidationError(path, 'Expected an object');
40+
}
3241
const keys = new Set(Object.keys(value));
3342
const value0 = value['foo'];
3443
if (value0 !== undefined) {
@@ -40,6 +49,7 @@ function obj1(path, value) {
4049
return result0;
4150
}
4251
value['foo'] = result0;
52+
keys.delete('foo');
4353
}
4454
if (keys.size > 0) {
4555
return new ValidationError(path, 'Unexpected properties');

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

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

33
exports[`with get and post 1`] = `
4-
"class ValidationError {
4+
"export function validateRequest(request) {
5+
return new ValidationError([], 'no operation match path');
6+
}
7+
class ValidationError extends Error {
58
constructor(path, message) {
69
super(message);
710
this.path = path;
@@ -14,12 +17,15 @@ function obj1(path, value) {
1417
return value;
1518
}
1619
function obj4(path, value) {
17-
if (!(typeof value === 'number')) {
20+
if (typeof value !== 'number') {
1821
return new ValidationError(path, 'Expected a number');
1922
}
2023
return value;
2124
}
2225
function obj3(path, value) {
26+
if (typeof value !== 'object' || value === null) {
27+
return new ValidationError(path, 'Expected an object');
28+
}
2329
const keys = new Set(Object.keys(value));
2430
const value0 = value['foo'];
2531
if (value0 !== undefined) {
@@ -31,6 +37,7 @@ function obj3(path, value) {
3137
return result0;
3238
}
3339
value['foo'] = result0;
40+
keys.delete('foo');
3441
}
3542
if (keys.size > 0) {
3643
return new ValidationError(path, 'Unexpected properties');

0 commit comments

Comments
 (0)