Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brave-places-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'better-ajv-errors': minor
---

Adds in support for array keyword errors maxItems, minItems, and uniqueItems. Some old errors that would have printed using the default error class will now display specialized array errors.
5 changes: 5 additions & 0 deletions src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from './utils';
import {
AdditionalPropValidationError,
ArrayPropertyValidationError,
RequiredValidationError,
EnumValidationError,
DefaultValidationError,
Expand Down Expand Up @@ -142,6 +143,10 @@ export function createErrorInstances(root, options) {
);
case 'required':
return ret.concat(new RequiredValidationError(error, options));
case 'maxItems':
case 'minItems':
case 'uniqueItems':
return ret.concat(new ArrayPropertyValidationError(error, options));
default:
return ret.concat(new DefaultValidationError(error, options));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"foo": [1, 2, 3]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"type": "object",
"properties": {
"foo": {
"type": "array",
"maxItems": 2,
"items": {
"type": "number"
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[1, 2, 3]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "array",
"maxItems": 2,
"items": { "type": "number" }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"foo": [1, 2, 3]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"type": "object",
"properties": {
"foo": {
"type": "array",
"minItems": 4,
"items": {
"type": "number"
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[1, 2, 3]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "array",
"items": { "type": "number" },
"minItems": 4
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "foo": [1, 1, 3] }
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"type": "object",
"properties": {
"foo": {
"type": "array",
"uniqueItems": true,
"items": { "type": "number" }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[1, 1, 3]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "array",
"uniqueItems": true,
"items": { "type": "number" }
}
109 changes: 109 additions & 0 deletions src/validation-errors/__tests__/__snapshots__/array.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Array Properties > 'Maximum Items' > for 'at root' > prints correctly, without confusing leading keyword 1`] = `
[
"/ must NOT have more than 2 items
",
"> 1 | [
 | ^
> 2 | 1,
 | ^^^^
> 3 | 2,
 | ^^^^
> 4 | 3
 | ^^^^
> 5 | ]
 | ^^ 👈🏽 array must NOT have more than 2 items",
]
`;

exports[`Array Properties > 'Maximum Items' > for 'inside object' > prints correctly, without confusing leading keyword 1`] = `
[
"/foo must NOT have more than 2 items
",
"  1 | {
> 2 | "foo": [
 | ^
> 3 | 1,
 | ^^^^^^
> 4 | 2,
 | ^^^^^^
> 5 | 3
 | ^^^^^^
> 6 | ]
 | ^^^^ 👈🏽 array must NOT have more than 2 items
 7 | }",
]
`;

exports[`Array Properties > 'Minimum Items' > for 'at root' > prints correctly, without confusing leading keyword 1`] = `
[
"/ must NOT have fewer than 4 items
",
"> 1 | [
 | ^
> 2 | 1,
 | ^^^^
> 3 | 2,
 | ^^^^
> 4 | 3
 | ^^^^
> 5 | ]
 | ^^ 👈🏽 array must NOT have fewer than 4 items",
]
`;

exports[`Array Properties > 'Minimum Items' > for 'inside object' > prints correctly, without confusing leading keyword 1`] = `
[
"/foo must NOT have fewer than 4 items
",
"  1 | {
> 2 | "foo": [
 | ^
> 3 | 1,
 | ^^^^^^
> 4 | 2,
 | ^^^^^^
> 5 | 3
 | ^^^^^^
> 6 | ]
 | ^^^^ 👈🏽 array must NOT have fewer than 4 items
 7 | }",
]
`;

exports[`Array Properties > 'Unique Items' > for 'at root' > prints correctly, without confusing leading keyword 1`] = `
[
"/ must NOT have duplicate items (items ## 1 and 0 are identical)
",
"> 1 | [
 | ^
> 2 | 1,
 | ^^^^
> 3 | 1,
 | ^^^^
> 4 | 3
 | ^^^^
> 5 | ]
 | ^^ 👈🏽 array must NOT have duplicate items (items ## 1 and 0 are identical)",
]
`;

exports[`Array Properties > 'Unique Items' > for 'inside object' > prints correctly, without confusing leading keyword 1`] = `
[
"/foo must NOT have duplicate items (items ## 1 and 0 are identical)
",
"  1 | {
> 2 | "foo": [
 | ^
> 3 | 1,
 | ^^^^^^
> 4 | 1,
 | ^^^^^^
> 5 | 3
 | ^^^^^^
> 6 | ]
 | ^^^^ 👈🏽 array must NOT have duplicate items (items ## 1 and 0 are identical)
 7 | }",
]
`;
57 changes: 57 additions & 0 deletions src/validation-errors/__tests__/__snapshots__/main.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,62 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Main > should support js output format for 'maxItems' errors 1`] = `
[
{
"end": {
"column": 8,
"line": 1,
"offset": 7,
},
"error": "/: Array must NOT have more than 2 items",
"path": "",
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
]
`;

exports[`Main > should support js output format for 'minItems' errors 1`] = `
[
{
"end": {
"column": 8,
"line": 1,
"offset": 7,
},
"error": "/: Array must NOT have fewer than 4 items",
"path": "",
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
]
`;

exports[`Main > should support js output format for 'uniqueItem' errors 1`] = `
[
{
"end": {
"column": 8,
"line": 1,
"offset": 7,
},
"error": "/: Array must NOT have duplicate items (items ## 1 and 0 are identical)",
"path": "",
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
]
`;

exports[`Main > should support js output format for additionalProperties errors 1`] = `
[
{
Expand Down
43 changes: 43 additions & 0 deletions src/validation-errors/__tests__/array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Ajv from 'ajv';
import { describe, it, expect, beforeEach } from 'vitest';
const { parse } = require('@humanwhocodes/momoa');

import { getSchemaAndData } from '../../test-helpers';
import { ArrayPropertyValidationError } from '../array';

describe('Array Properties', () => {
const ajv = new Ajv();
let validator;

describe.each([
['Minimum Items', 'min-items'],
['Maximum Items', 'max-items'],
['Unique Items', 'unique'],
])('$0', (_title, directory) => {
describe.each([
['at root', 'root'],
['inside object', 'in-object'],
])('for $0', (_title, name) => {
let schema, data, jsonRaw, jsonAst;

beforeEach(async () => {
[schema, data] = await getSchemaAndData(`array/${directory}/${name}`, __dirname);
jsonRaw = JSON.stringify(data, null, 2);
jsonAst = parse(jsonRaw);
validator = ajv.compile(schema)
});

it('prints correctly, without confusing leading keyword', async () => {
const valid = validator(data);
const error = new ArrayPropertyValidationError(
validator.errors[0],
{ data, schema, jsonRaw, jsonAst },
);

expect(valid).toBe(false);
expect(error.print()).toMatchSnapshot();
});
});
});
});

19 changes: 19 additions & 0 deletions src/validation-errors/__tests__/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,23 @@ describe('Main', () => {
});
expect(res).toMatchSnapshot();
});

it.each([
['maxItems', 'max-items'],
['minItems', 'min-items'],
['uniqueItem', 'unique'],
])('should support js output format for $0 errors', async (_title, directory) => {
const [schema, data] = await getSchemaAndData(`array/${directory}/root`, __dirname);

const ajv = new Ajv();
const validate = ajv.compile(schema);
const valid = validate(data);
expect(valid).toBeFalsy();

const res = betterAjvErrors(schema, data, validate.errors, {
format: 'js',
});

expect(res).toMatchSnapshot();
});
});
27 changes: 27 additions & 0 deletions src/validation-errors/array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import chalk from 'chalk';
import BaseValidationError from './base';

export class ArrayPropertyValidationError extends BaseValidationError {
print() {
const { message } = this.options;
const displayPath = this.instancePath.length === 0 ? '/' : this.instancePath;

const output = [chalk`{red {bold ${displayPath}} ${message}}\n`];

return output.concat(
this.getCodeFrame(chalk`👈🏽 {magentaBright array} ${message}`)
);
}

getError() {
const { message } = this.options;
const decoratedPath = this.getDecoratedPath();
const displayPath = decoratedPath.length === 0 ? '/' : decoratedPath;

return {
...this.getLocation(),
error: `${displayPath}: Array ${message}`,
path: this.instancePath,
};
}
}
1 change: 1 addition & 0 deletions src/validation-errors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { default as RequiredValidationError } from './required';
export { default as AdditionalPropValidationError } from './additional-prop';
export { default as EnumValidationError } from './enum';
export { default as DefaultValidationError } from './default';
export { ArrayPropertyValidationError } from './array';
Loading