@atlassian/better-ajv-errors 0.0.0-stage → 3.0.1
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/LICENSE +13 -0
- package/README.md +125 -2
- package/dist/index.cjs +418 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +390 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +82 -4
- package/typings.d.cts +24 -0
- package/typings.d.mts +24 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2018 Atlassian Pty Ltd
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,126 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<img width="570" src="media/logo.png" alt="better-ajv-errors">
|
|
3
|
+
<br>
|
|
4
|
+
</h1>
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
> JSON Schema validation for Human 👨🎤
|
|
7
|
+
|
|
8
|
+
Main goal of this library is to provide relevant error messages like the following:
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<img src="media/screenshot.svg">
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm i better-ajv-errors
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Also install [ajv](https://www.npmjs.com/package/ajv), version 4.11.8 to 8, to validate data against JSON schemas. `better-ajv-errors` requires Node.js 22 or later.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
First, you need to validate your payload with `ajv`. If it's invalid then you can pass `validate.errors` object into `better-ajv-errors`.
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
import Ajv from 'ajv';
|
|
28
|
+
import betterAjvErrors from 'better-ajv-errors';
|
|
29
|
+
// const Ajv = require('ajv');
|
|
30
|
+
// const betterAjvErrors = require('better-ajv-errors').default;
|
|
31
|
+
// Or
|
|
32
|
+
// const { default: betterAjvErrors } = require('better-ajv-errors');
|
|
33
|
+
|
|
34
|
+
// `allErrors` makes ajv report every error instead of stopping at the first one.
|
|
35
|
+
// ajv 6 and older also need `jsonPointers: true`.
|
|
36
|
+
const ajv = new Ajv({ allErrors: true });
|
|
37
|
+
|
|
38
|
+
// Load schema and data
|
|
39
|
+
const schema = ...;
|
|
40
|
+
const data = ...;
|
|
41
|
+
|
|
42
|
+
const validate = ajv.compile(schema);
|
|
43
|
+
const valid = validate(data);
|
|
44
|
+
|
|
45
|
+
if (!valid) {
|
|
46
|
+
const output = betterAjvErrors(schema, data, validate.errors);
|
|
47
|
+
console.log(output);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`better-ajv-errors` leaves out errors that another error usually explains:
|
|
52
|
+
|
|
53
|
+
- A missing `required` property hides the other errors reported for that object, such as `additionalProperties`.
|
|
54
|
+
- An `enum` error is hidden when another error is reported at the same level.
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
### betterAjvErrors(schema, data, errors, [options])
|
|
59
|
+
|
|
60
|
+
Returns formatted validation error to **print** in `console`. See [`options.format`](#format) for further details.
|
|
61
|
+
|
|
62
|
+
#### schema
|
|
63
|
+
|
|
64
|
+
Type: `Object`
|
|
65
|
+
|
|
66
|
+
The JSON Schema you used for validation with `ajv`
|
|
67
|
+
|
|
68
|
+
#### data
|
|
69
|
+
|
|
70
|
+
Type: `Object`
|
|
71
|
+
|
|
72
|
+
The JSON payload you validate against using `ajv`
|
|
73
|
+
|
|
74
|
+
#### errors
|
|
75
|
+
|
|
76
|
+
Type: `Array`
|
|
77
|
+
|
|
78
|
+
Array of [ajv validation errors](https://ajv.js.org/api.html#validation-errors)
|
|
79
|
+
|
|
80
|
+
#### options
|
|
81
|
+
|
|
82
|
+
Type: `Object`
|
|
83
|
+
|
|
84
|
+
##### format
|
|
85
|
+
|
|
86
|
+
Type: `string`
|
|
87
|
+
Default: `cli`
|
|
88
|
+
Values: `cli` `js`
|
|
89
|
+
|
|
90
|
+
Use default `cli` output format if you want to **print** beautiful validation errors like following:
|
|
91
|
+
|
|
92
|
+
<img width="620" src="media/screenshot.svg">
|
|
93
|
+
|
|
94
|
+
Or, use `js` if you are planning to use this with some API. Your output will look like following:
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
[
|
|
98
|
+
{
|
|
99
|
+
start: { line: 6, column: 15, offset: 70 },
|
|
100
|
+
end: { line: 6, column: 26, offset: 81 },
|
|
101
|
+
error:
|
|
102
|
+
'/content/0/type must be equal to one of the allowed values: paragraph, codeBlock, blockquote',
|
|
103
|
+
path: '/content/0/type',
|
|
104
|
+
suggestion: 'Did you mean paragraph?',
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`path` is the `instancePath` of the ajv error. `end` is left out for `required` errors, and `suggestion` appears on some `enum` errors, naming the allowed value closest to the actual one.
|
|
110
|
+
|
|
111
|
+
##### indent
|
|
112
|
+
|
|
113
|
+
Type: `number` `null`
|
|
114
|
+
Default: `null`
|
|
115
|
+
|
|
116
|
+
If you have an unindented JSON payload and you want the error output indented.
|
|
117
|
+
|
|
118
|
+
This option has no effect when using the `json` option.
|
|
119
|
+
|
|
120
|
+
##### json
|
|
121
|
+
|
|
122
|
+
Type: `string` `null`
|
|
123
|
+
Default: `null`
|
|
124
|
+
|
|
125
|
+
Raw JSON payload used when formatting codeframe.
|
|
126
|
+
Gives accurate line and column listings.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __create = Object.create;
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
9
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
10
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
14
|
+
key = keys[i];
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
16
|
+
get: ((k) => from[k]).bind(null, key),
|
|
17
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
//#endregion
|
|
27
|
+
let _humanwhocodes_momoa = require("@humanwhocodes/momoa");
|
|
28
|
+
let node_util = require("node:util");
|
|
29
|
+
let leven = require("leven");
|
|
30
|
+
leven = __toESM(leven, 1);
|
|
31
|
+
let jsonpointer = require("jsonpointer");
|
|
32
|
+
jsonpointer = __toESM(jsonpointer, 1);
|
|
33
|
+
//#region src/utils.js
|
|
34
|
+
const eq = (x) => (y) => x === y;
|
|
35
|
+
const not = (fn) => (x) => !fn(x);
|
|
36
|
+
const getValues = (o) => Object.values(o);
|
|
37
|
+
const notUndefined = (x) => x !== void 0;
|
|
38
|
+
const isXError = (x) => (error) => error.keyword === x;
|
|
39
|
+
const isRequiredError = isXError("required");
|
|
40
|
+
const isAnyOfError = isXError("anyOf");
|
|
41
|
+
const isEnumError = isXError("enum");
|
|
42
|
+
const getErrors = (node) => node && node.errors ? node.errors.map((e) => e.keyword === "errorMessage" ? {
|
|
43
|
+
...e.params.errors[0],
|
|
44
|
+
message: e.message
|
|
45
|
+
} : e) : [];
|
|
46
|
+
const getChildren = (node) => node && getValues(node.children) || [];
|
|
47
|
+
const getSiblings = (parent) => (node) => getChildren(parent).filter(not(eq(node)));
|
|
48
|
+
const concatAll = (xs) => (ys) => ys.reduce((zs, z) => zs.concat(z), xs);
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/code-frame.js
|
|
51
|
+
const LINES_ABOVE = 2;
|
|
52
|
+
const LINES_BELOW = 3;
|
|
53
|
+
const NEWLINE = /\r\n|[\r\n]/;
|
|
54
|
+
const TOKEN_STYLES = {
|
|
55
|
+
String: "green",
|
|
56
|
+
Number: "magenta",
|
|
57
|
+
Boolean: "cyan",
|
|
58
|
+
Null: "cyan",
|
|
59
|
+
Colon: "yellow",
|
|
60
|
+
Comma: "yellow"
|
|
61
|
+
};
|
|
62
|
+
const MARKER_STYLE = ["red", "bold"];
|
|
63
|
+
const supportsColor = () => (0, node_util.styleText)("red", "-") !== "-";
|
|
64
|
+
/**
|
|
65
|
+
* Renders the lines of `source` around `location`, with carets under the located text and
|
|
66
|
+
* `message` after the last caret.
|
|
67
|
+
*
|
|
68
|
+
* Lines and columns are 1-based and `end` is exclusive, as in momoa's locations. Without
|
|
69
|
+
* `end`, a single caret marks `start`.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} source
|
|
72
|
+
* @param {{ start: { line: number, column: number }, end?: { line: number, column: number } }} location
|
|
73
|
+
* @param {{ message?: string, color?: boolean }} [options]
|
|
74
|
+
* @return {string}
|
|
75
|
+
*/
|
|
76
|
+
function codeFrame(source, { start, end = start }, { message, color = supportsColor() } = {}) {
|
|
77
|
+
const paint = color ? (format, text) => (0, node_util.styleText)(format, text, { validateStream: false }) : (_format, text) => text;
|
|
78
|
+
const lines = source.split(NEWLINE);
|
|
79
|
+
const markers = getMarkers(lines, start, end);
|
|
80
|
+
const first = Math.max(start.line - LINES_ABOVE, 1);
|
|
81
|
+
const last = Math.min(end.line + LINES_BELOW, lines.length);
|
|
82
|
+
const width = String(last).length;
|
|
83
|
+
const rows = [];
|
|
84
|
+
for (let number = first; number <= last; number++) {
|
|
85
|
+
const line = lines[number - 1];
|
|
86
|
+
const gutter = ` ${String(number).padStart(width)} |`;
|
|
87
|
+
const code = line.length > 0 ? ` ${color ? highlight(line, paint) : line}` : "";
|
|
88
|
+
const marker = markers.get(number);
|
|
89
|
+
if (!marker) {
|
|
90
|
+
rows.push(` ${paint("gray", gutter)}${code}`);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const [column, count] = marker;
|
|
94
|
+
const spacing = line.slice(0, column - 1).replace(/[^\t]/g, " ");
|
|
95
|
+
let caretRow = ` ${paint("gray", gutter.replace(/\d/g, " "))} ${spacing}${paint(MARKER_STYLE, "^".repeat(count))}`;
|
|
96
|
+
if (message && number === end.line) caretRow += ` ${paint(MARKER_STYLE, message)}`;
|
|
97
|
+
rows.push(`${paint(MARKER_STYLE, ">")}${paint("gray", gutter)}${code}`, caretRow);
|
|
98
|
+
}
|
|
99
|
+
return rows.join("\n");
|
|
100
|
+
}
|
|
101
|
+
function getMarkers(lines, start, end) {
|
|
102
|
+
const markers = /* @__PURE__ */ new Map();
|
|
103
|
+
if (start.line === end.line) {
|
|
104
|
+
markers.set(start.line, [start.column, Math.max(end.column - start.column, 1)]);
|
|
105
|
+
return markers;
|
|
106
|
+
}
|
|
107
|
+
markers.set(start.line, [start.column, lines[start.line - 1].length - start.column + 1]);
|
|
108
|
+
for (let number = start.line + 1; number < end.line; number++) markers.set(number, [1, Math.max(lines[number - 1].length, 1)]);
|
|
109
|
+
markers.set(end.line, [1, end.column - 1]);
|
|
110
|
+
return markers;
|
|
111
|
+
}
|
|
112
|
+
function highlight(line, paint) {
|
|
113
|
+
let output = "";
|
|
114
|
+
let offset = 0;
|
|
115
|
+
for (const { type, loc } of (0, _humanwhocodes_momoa.tokenize)(line)) {
|
|
116
|
+
const style = TOKEN_STYLES[type];
|
|
117
|
+
if (style) {
|
|
118
|
+
output += line.slice(offset, loc.start.offset) + paint(style, line.slice(loc.start.offset, loc.end.offset));
|
|
119
|
+
offset = loc.end.offset;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return output + line.slice(offset);
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/json/utils.js
|
|
126
|
+
const getPointers = (dataPath) => {
|
|
127
|
+
return dataPath.split("/").slice(1).map((pointer) => pointer.split("~1").join("/").split("~0").join("~"));
|
|
128
|
+
};
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/json/get-meta-from-path.js
|
|
131
|
+
function getMetaFromPath(jsonAst, dataPath, includeIdentifierLocation) {
|
|
132
|
+
const pointers = getPointers(dataPath);
|
|
133
|
+
const lastPointerIndex = pointers.length - 1;
|
|
134
|
+
return pointers.reduce((obj, pointer, idx) => {
|
|
135
|
+
switch (obj.type) {
|
|
136
|
+
case "Object": {
|
|
137
|
+
const member = obj.members.findLast((child) => child.name.value === pointer);
|
|
138
|
+
if (!member) throw new Error(`Couldn't find property ${pointer} of ${dataPath}`);
|
|
139
|
+
const { name, value } = member;
|
|
140
|
+
return includeIdentifierLocation && idx === lastPointerIndex ? name : value;
|
|
141
|
+
}
|
|
142
|
+
case "Array": {
|
|
143
|
+
const element = obj.elements[pointer];
|
|
144
|
+
return element && element.value;
|
|
145
|
+
}
|
|
146
|
+
default: console.log(obj);
|
|
147
|
+
}
|
|
148
|
+
}, jsonAst.body);
|
|
149
|
+
}
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/json/get-decorated-data-path.js
|
|
152
|
+
function getDecoratedDataPath(jsonAst, dataPath) {
|
|
153
|
+
let decoratedPath = "";
|
|
154
|
+
getPointers(dataPath).reduce((obj, pointer) => {
|
|
155
|
+
switch (obj.type) {
|
|
156
|
+
case "Object": {
|
|
157
|
+
decoratedPath += `/${pointer}`;
|
|
158
|
+
const member = obj.members.findLast((child) => child.name.value === pointer);
|
|
159
|
+
if (!member) throw new Error(`Couldn't find property ${pointer} of ${dataPath}`);
|
|
160
|
+
return member.value;
|
|
161
|
+
}
|
|
162
|
+
case "Array": {
|
|
163
|
+
const element = obj.elements[pointer];
|
|
164
|
+
const value = element && element.value;
|
|
165
|
+
decoratedPath += `/${pointer}${getTypeName(value)}`;
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
default: console.log(obj);
|
|
169
|
+
}
|
|
170
|
+
}, jsonAst.body);
|
|
171
|
+
return decoratedPath;
|
|
172
|
+
}
|
|
173
|
+
function getTypeName(obj) {
|
|
174
|
+
if (!obj || !obj.elements) return "";
|
|
175
|
+
const type = obj.elements.filter((child) => child && child.name && child.name.value === "type");
|
|
176
|
+
if (!type.length) return "";
|
|
177
|
+
return type[0].value && `:${type[0].value.value}` || "";
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/validation-errors/base.js
|
|
181
|
+
var BaseValidationError = class {
|
|
182
|
+
constructor(options = { isIdentifierLocation: false }, { data, schema, jsonAst, jsonRaw }) {
|
|
183
|
+
this.options = options;
|
|
184
|
+
this.data = data;
|
|
185
|
+
this.schema = schema;
|
|
186
|
+
this.jsonAst = jsonAst;
|
|
187
|
+
this.jsonRaw = jsonRaw;
|
|
188
|
+
}
|
|
189
|
+
getLocation(dataPath = this.instancePath) {
|
|
190
|
+
const { isIdentifierLocation, isSkipEndLocation } = this.options;
|
|
191
|
+
const { loc } = getMetaFromPath(this.jsonAst, dataPath, isIdentifierLocation);
|
|
192
|
+
return {
|
|
193
|
+
start: loc.start,
|
|
194
|
+
end: isSkipEndLocation ? void 0 : loc.end
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
getDecoratedPath(dataPath = this.instancePath) {
|
|
198
|
+
return getDecoratedDataPath(this.jsonAst, dataPath);
|
|
199
|
+
}
|
|
200
|
+
withDecoratedPath(message, separator = " ") {
|
|
201
|
+
const decoratedPath = this.getDecoratedPath();
|
|
202
|
+
return decoratedPath ? `${decoratedPath}${separator}${message}` : message;
|
|
203
|
+
}
|
|
204
|
+
getCodeFrame(message, dataPath = this.instancePath) {
|
|
205
|
+
return codeFrame(this.jsonRaw, this.getLocation(dataPath), { message });
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* @return {string}
|
|
209
|
+
*/
|
|
210
|
+
get instancePath() {
|
|
211
|
+
return typeof this.options.instancePath !== "undefined" ? this.options.instancePath : this.options.dataPath;
|
|
212
|
+
}
|
|
213
|
+
print() {
|
|
214
|
+
throw new Error(`Implement the 'print' method inside ${this.constructor.name}!`);
|
|
215
|
+
}
|
|
216
|
+
getError() {
|
|
217
|
+
throw new Error(`Implement the 'getError' method inside ${this.constructor.name}!`);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/validation-errors/required.js
|
|
222
|
+
var RequiredValidationError = class extends BaseValidationError {
|
|
223
|
+
getLocation(dataPath = this.instancePath) {
|
|
224
|
+
const { start } = super.getLocation(dataPath);
|
|
225
|
+
return { start };
|
|
226
|
+
}
|
|
227
|
+
print() {
|
|
228
|
+
const { message, params } = this.options;
|
|
229
|
+
return [(0, node_util.styleText)("red", (0, node_util.styleText)("bold", "REQUIRED") + " " + message) + "\n"].concat(this.getCodeFrame("☹️ " + (0, node_util.styleText)("magentaBright", params.missingProperty) + " is missing here!"));
|
|
230
|
+
}
|
|
231
|
+
getError() {
|
|
232
|
+
const { message } = this.options;
|
|
233
|
+
return {
|
|
234
|
+
...this.getLocation(),
|
|
235
|
+
error: this.withDecoratedPath(message),
|
|
236
|
+
path: this.instancePath
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region src/validation-errors/additional-prop.js
|
|
242
|
+
var AdditionalPropValidationError = class extends BaseValidationError {
|
|
243
|
+
constructor(...args) {
|
|
244
|
+
super(...args);
|
|
245
|
+
this.options.isIdentifierLocation = true;
|
|
246
|
+
}
|
|
247
|
+
print() {
|
|
248
|
+
const { message, params } = this.options;
|
|
249
|
+
return [(0, node_util.styleText)("red", (0, node_util.styleText)("bold", "ADDITIONAL PROPERTY") + " " + message) + "\n"].concat(this.getCodeFrame("😲 " + (0, node_util.styleText)("magentaBright", params.additionalProperty) + " is not expected to be here!", `${this.instancePath}/${params.additionalProperty}`));
|
|
250
|
+
}
|
|
251
|
+
getError() {
|
|
252
|
+
const { params } = this.options;
|
|
253
|
+
return {
|
|
254
|
+
...this.getLocation(`${this.instancePath}/${params.additionalProperty}`),
|
|
255
|
+
error: this.withDecoratedPath(`Property ${params.additionalProperty} is not expected to be here`),
|
|
256
|
+
path: this.instancePath
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/validation-errors/enum.js
|
|
262
|
+
var EnumValidationError = class extends BaseValidationError {
|
|
263
|
+
print() {
|
|
264
|
+
const { message, params: { allowedValues } } = this.options;
|
|
265
|
+
const bestMatch = this.findBestMatch();
|
|
266
|
+
const [firstValue, ...rest] = allowedValues;
|
|
267
|
+
const allowedValuesMessage = rest.reduce((acc, value) => `${acc}, ${value}`, firstValue || "");
|
|
268
|
+
return [(0, node_util.styleText)("red", (0, node_util.styleText)("bold", "ENUM") + " " + message), (0, node_util.styleText)("red", `(${allowedValuesMessage})\n`)].concat(this.getCodeFrame(bestMatch !== null ? "👈🏽 Did you mean " + (0, node_util.styleText)("magentaBright", String(bestMatch)) + " here?" : "👈🏽 Unexpected value, should be equal to one of the allowed values"));
|
|
269
|
+
}
|
|
270
|
+
getError() {
|
|
271
|
+
const { message, params } = this.options;
|
|
272
|
+
const bestMatch = this.findBestMatch();
|
|
273
|
+
const allowedValues = params.allowedValues.join(", ");
|
|
274
|
+
const output = {
|
|
275
|
+
...this.getLocation(),
|
|
276
|
+
error: this.withDecoratedPath(`${message}: ${allowedValues}`),
|
|
277
|
+
path: this.instancePath
|
|
278
|
+
};
|
|
279
|
+
if (bestMatch !== null) output.suggestion = `Did you mean ${bestMatch}?`;
|
|
280
|
+
return output;
|
|
281
|
+
}
|
|
282
|
+
findBestMatch() {
|
|
283
|
+
const { params: { allowedValues } } = this.options;
|
|
284
|
+
const currentValue = this.instancePath === "" ? this.data : jsonpointer.default.get(this.data, this.instancePath);
|
|
285
|
+
if (!currentValue) return null;
|
|
286
|
+
const bestMatch = allowedValues.map((value) => ({
|
|
287
|
+
value,
|
|
288
|
+
weight: typeof value === "string" ? (0, leven.default)(value, currentValue.toString()) : Infinity
|
|
289
|
+
})).sort((x, y) => x.weight > y.weight ? 1 : x.weight < y.weight ? -1 : 0)[0];
|
|
290
|
+
return allowedValues.length === 1 || bestMatch.weight < bestMatch.value.length ? bestMatch.value : null;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/validation-errors/default.js
|
|
295
|
+
var DefaultValidationError = class extends BaseValidationError {
|
|
296
|
+
print() {
|
|
297
|
+
const { keyword, message } = this.options;
|
|
298
|
+
return [(0, node_util.styleText)("red", (0, node_util.styleText)("bold", keyword.toUpperCase()) + " " + message) + "\n"].concat(this.getCodeFrame("👈🏽 " + (0, node_util.styleText)("magentaBright", keyword) + " " + message));
|
|
299
|
+
}
|
|
300
|
+
getError() {
|
|
301
|
+
const { keyword, message } = this.options;
|
|
302
|
+
return {
|
|
303
|
+
...this.getLocation(),
|
|
304
|
+
error: this.withDecoratedPath(`${keyword} ${message}`, ": "),
|
|
305
|
+
path: this.instancePath
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/helpers.js
|
|
311
|
+
const JSON_POINTERS_REGEX = /\/[\w_-]+(\/\d+)?/g;
|
|
312
|
+
function makeTree(ajvErrors = []) {
|
|
313
|
+
const root = { children: {} };
|
|
314
|
+
ajvErrors.forEach((ajvError) => {
|
|
315
|
+
const instancePath = typeof ajvError.instancePath !== "undefined" ? ajvError.instancePath : ajvError.dataPath;
|
|
316
|
+
const paths = instancePath === "" ? [""] : instancePath.match(JSON_POINTERS_REGEX);
|
|
317
|
+
if (paths) paths.reduce((obj, path, i) => {
|
|
318
|
+
obj.children[path] = obj.children[path] || {
|
|
319
|
+
children: {},
|
|
320
|
+
errors: []
|
|
321
|
+
};
|
|
322
|
+
if (i === paths.length - 1) obj.children[path].errors.push(ajvError);
|
|
323
|
+
return obj.children[path];
|
|
324
|
+
}, root);
|
|
325
|
+
});
|
|
326
|
+
return root;
|
|
327
|
+
}
|
|
328
|
+
function filterRedundantErrors(root, parent, key) {
|
|
329
|
+
/**
|
|
330
|
+
* Partition out the errors by kind for ease of proceessing
|
|
331
|
+
*/
|
|
332
|
+
const { anyOfErrors, enumErrors, requiredErrors } = getErrors(root).reduce((acc, error) => {
|
|
333
|
+
if (isRequiredError(error)) {
|
|
334
|
+
acc.requiredErrors.push(error);
|
|
335
|
+
return acc;
|
|
336
|
+
}
|
|
337
|
+
if (isAnyOfError(error)) acc.anyOfErrors.push(error);
|
|
338
|
+
if (isEnumError(error)) acc.enumErrors.push(error);
|
|
339
|
+
return acc;
|
|
340
|
+
}, {
|
|
341
|
+
anyOfErrors: [],
|
|
342
|
+
enumErrors: [],
|
|
343
|
+
requiredErrors: []
|
|
344
|
+
});
|
|
345
|
+
/**
|
|
346
|
+
* If there is are `required` errors then we can just drop every non-required error.
|
|
347
|
+
* And, also `required` should have more priority than `anyOf`. @see #8
|
|
348
|
+
*/
|
|
349
|
+
if (requiredErrors.length > 0) {
|
|
350
|
+
root.errors = requiredErrors;
|
|
351
|
+
root.children = {};
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* If there is an `anyOf` error that means we have more meaningful errors
|
|
356
|
+
* inside children. So we will just remove all errors from this level.
|
|
357
|
+
*
|
|
358
|
+
* If there are no children, then we don't delete the errors since we should
|
|
359
|
+
* have at least one error to report.
|
|
360
|
+
*/
|
|
361
|
+
if (anyOfErrors.length > 0 && Object.keys(root.children).length > 0) delete root.errors;
|
|
362
|
+
/**
|
|
363
|
+
* If all errors are `enum` and siblings have any error then we can safely
|
|
364
|
+
* ignore the node. As we return early if there's required errors, we only
|
|
365
|
+
* need to check anyofErrors length
|
|
366
|
+
*
|
|
367
|
+
* **CAUTION**
|
|
368
|
+
* Need explicit `root.errors` check because `[].every(fn) === true`
|
|
369
|
+
* https://en.wikipedia.org/wiki/Vacuous_truth#Vacuous_truths_in_mathematics
|
|
370
|
+
*/
|
|
371
|
+
if (enumErrors.length > 0 && anyOfErrors.length === 0) {
|
|
372
|
+
if (getSiblings(parent)(root).filter(notUndefined).some(getErrors)) delete parent.children[key];
|
|
373
|
+
}
|
|
374
|
+
Object.entries(root.children).forEach(([key, child]) => filterRedundantErrors(child, root, key));
|
|
375
|
+
}
|
|
376
|
+
function createErrorInstances(root, options) {
|
|
377
|
+
const errors = getErrors(root);
|
|
378
|
+
if (errors.length && errors.every(isEnumError)) {
|
|
379
|
+
const allowedValues = [...new Set(concatAll([])(errors.map((e) => e.params.allowedValues)))];
|
|
380
|
+
const error = errors[0];
|
|
381
|
+
return [new EnumValidationError({
|
|
382
|
+
...error,
|
|
383
|
+
params: { allowedValues }
|
|
384
|
+
}, options)];
|
|
385
|
+
} else return concatAll(errors.reduce((ret, error) => {
|
|
386
|
+
switch (error.keyword) {
|
|
387
|
+
case "additionalProperties": return ret.concat(new AdditionalPropValidationError(error, options));
|
|
388
|
+
case "required": return ret.concat(new RequiredValidationError(error, options));
|
|
389
|
+
default: return ret.concat(new DefaultValidationError(error, options));
|
|
390
|
+
}
|
|
391
|
+
}, []))(getChildren(root).map((child) => createErrorInstances(child, options)));
|
|
392
|
+
}
|
|
393
|
+
var helpers_default = (ajvErrors, options) => {
|
|
394
|
+
const tree = makeTree(ajvErrors || []);
|
|
395
|
+
filterRedundantErrors(tree);
|
|
396
|
+
return createErrorInstances(tree, options);
|
|
397
|
+
};
|
|
398
|
+
//#endregion
|
|
399
|
+
//#region src/index.js
|
|
400
|
+
var src_default = (schema, data, errors, options = {}) => {
|
|
401
|
+
const { format = "cli", indent = null, json = null } = options;
|
|
402
|
+
const jsonRaw = json || JSON.stringify(data, null, indent);
|
|
403
|
+
const jsonAst = (0, _humanwhocodes_momoa.parse)(jsonRaw);
|
|
404
|
+
const customErrorToText = (error) => error.print().join("\n");
|
|
405
|
+
const customErrorToStructure = (error) => error.getError();
|
|
406
|
+
const customErrors = helpers_default(errors, {
|
|
407
|
+
data,
|
|
408
|
+
schema,
|
|
409
|
+
jsonAst,
|
|
410
|
+
jsonRaw
|
|
411
|
+
});
|
|
412
|
+
if (format === "cli") return customErrors.map(customErrorToText).join("\n\n");
|
|
413
|
+
else return customErrors.map(customErrorToStructure);
|
|
414
|
+
};
|
|
415
|
+
//#endregion
|
|
416
|
+
exports.default = src_default;
|
|
417
|
+
|
|
418
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["styleText","tokenize","styleText","styleText","styleText","pointer","styleText","parse","prettify"],"sources":["../src/utils.js","../src/code-frame.js","../src/json/utils.js","../src/json/get-meta-from-path.js","../src/json/get-decorated-data-path.js","../src/validation-errors/base.js","../src/validation-errors/required.js","../src/validation-errors/additional-prop.js","../src/validation-errors/enum.js","../src/validation-errors/default.js","../src/helpers.js","../src/index.js"],"sourcesContent":["// Basic\nconst eq = x => y => x === y;\nconst not = fn => x => !fn(x);\n\nconst getValues = o => Object.values(o);\n\nexport const notUndefined = x => x !== undefined;\n\n// Error\nconst isXError = x => error => error.keyword === x;\nexport const isRequiredError = isXError('required');\nexport const isAnyOfError = isXError('anyOf');\nexport const isEnumError = isXError('enum');\nexport const getErrors = node =>\n node && node.errors\n ? node.errors.map(e =>\n e.keyword === 'errorMessage' ? { ...e.params.errors[0], message: e.message } : e\n )\n : [];\n\n// Node\nexport const getChildren = node => (node && getValues(node.children)) || [];\n\nexport const getSiblings = parent => node => getChildren(parent).filter(not(eq(node)));\n\nexport const concatAll = xs => ys => ys.reduce((zs, z) => zs.concat(z), xs);\n","import { styleText } from 'node:util';\nimport { tokenize } from '@humanwhocodes/momoa';\n\nconst LINES_ABOVE = 2;\nconst LINES_BELOW = 3;\n\n// The line breaks momoa counts, so that its line numbers index into the split source.\nconst NEWLINE = /\\r\\n|[\\r\\n]/;\n\nconst TOKEN_STYLES = {\n String: 'green',\n Number: 'magenta',\n Boolean: 'cyan',\n Null: 'cyan',\n Colon: 'yellow',\n Comma: 'yellow',\n};\n\nconst MARKER_STYLE = ['red', 'bold'];\n\n// The check `styleText` makes for the headings, so that the frame is colored along with them.\nconst supportsColor = () => styleText('red', '-') !== '-';\n\n/**\n * Renders the lines of `source` around `location`, with carets under the located text and\n * `message` after the last caret.\n *\n * Lines and columns are 1-based and `end` is exclusive, as in momoa's locations. Without\n * `end`, a single caret marks `start`.\n *\n * @param {string} source\n * @param {{ start: { line: number, column: number }, end?: { line: number, column: number } }} location\n * @param {{ message?: string, color?: boolean }} [options]\n * @return {string}\n */\nexport default function codeFrame(\n source,\n { start, end = start },\n { message, color = supportsColor() } = {}\n) {\n const paint = color\n ? (format, text) => styleText(format, text, { validateStream: false })\n : (_format, text) => text;\n\n const lines = source.split(NEWLINE);\n const markers = getMarkers(lines, start, end);\n const first = Math.max(start.line - LINES_ABOVE, 1);\n const last = Math.min(end.line + LINES_BELOW, lines.length);\n const width = String(last).length;\n\n const rows = [];\n for (let number = first; number <= last; number++) {\n const line = lines[number - 1];\n const gutter = ` ${String(number).padStart(width)} |`;\n const code = line.length > 0 ? ` ${color ? highlight(line, paint) : line}` : '';\n const marker = markers.get(number);\n if (!marker) {\n rows.push(` ${paint('gray', gutter)}${code}`);\n continue;\n }\n\n const [column, count] = marker;\n const spacing = line.slice(0, column - 1).replace(/[^\\t]/g, ' ');\n let caretRow = ` ${paint('gray', gutter.replace(/\\d/g, ' '))} ${spacing}${paint(MARKER_STYLE, '^'.repeat(count))}`;\n if (message && number === end.line) {\n caretRow += ` ${paint(MARKER_STYLE, message)}`;\n }\n rows.push(`${paint(MARKER_STYLE, '>')}${paint('gray', gutter)}${code}`, caretRow);\n }\n\n return rows.join('\\n');\n}\n\n// Maps each marked line number to the column of its first caret and the number of carets.\nfunction getMarkers(lines, start, end) {\n const markers = new Map();\n if (start.line === end.line) {\n markers.set(start.line, [start.column, Math.max(end.column - start.column, 1)]);\n return markers;\n }\n\n markers.set(start.line, [start.column, lines[start.line - 1].length - start.column + 1]);\n for (let number = start.line + 1; number < end.line; number++) {\n markers.set(number, [1, Math.max(lines[number - 1].length, 1)]);\n }\n markers.set(end.line, [1, end.column - 1]);\n return markers;\n}\n\n// JSON strings can't contain raw line breaks, so each line holds whole tokens.\nfunction highlight(line, paint) {\n let output = '';\n let offset = 0;\n for (const { type, loc } of tokenize(line)) {\n const style = TOKEN_STYLES[type];\n if (style) {\n output +=\n line.slice(offset, loc.start.offset) +\n paint(style, line.slice(loc.start.offset, loc.end.offset));\n offset = loc.end.offset;\n }\n }\n return output + line.slice(offset);\n}\n","// TODO: Better error handling\nexport const getPointers = dataPath => {\n return dataPath\n .split('/')\n .slice(1)\n .map(pointer => pointer.split('~1').join('/').split('~0').join('~'));\n};\n","import { getPointers } from './utils';\n\nexport default function getMetaFromPath(jsonAst, dataPath, includeIdentifierLocation) {\n const pointers = getPointers(dataPath);\n const lastPointerIndex = pointers.length - 1;\n return pointers.reduce((obj, pointer, idx) => {\n switch (obj.type) {\n case 'Object': {\n // JSON.parse keeps the last of duplicate keys, so that is the one ajv validated.\n const member = obj.members.findLast(child => child.name.value === pointer);\n if (!member) {\n throw new Error(`Couldn't find property ${pointer} of ${dataPath}`);\n }\n\n const { name, value } = member;\n return includeIdentifierLocation && idx === lastPointerIndex ? name : value;\n }\n case 'Array': {\n const element = obj.elements[pointer];\n return element && element.value;\n }\n default:\n console.log(obj);\n }\n }, jsonAst.body);\n}\n","import { getPointers } from './utils';\n\nexport default function getDecoratedDataPath(jsonAst, dataPath) {\n let decoratedPath = '';\n getPointers(dataPath).reduce((obj, pointer) => {\n switch (obj.type) {\n case 'Object': {\n decoratedPath += `/${pointer}`;\n // JSON.parse keeps the last of duplicate keys, so that is the one ajv validated.\n const member = obj.members.findLast(child => child.name.value === pointer);\n if (!member) {\n throw new Error(`Couldn't find property ${pointer} of ${dataPath}`);\n }\n return member.value;\n }\n case 'Array': {\n const element = obj.elements[pointer];\n const value = element && element.value;\n decoratedPath += `/${pointer}${getTypeName(value)}`;\n return value;\n }\n default:\n console.log(obj);\n }\n }, jsonAst.body);\n return decoratedPath;\n}\n\nfunction getTypeName(obj) {\n if (!obj || !obj.elements) {\n return '';\n }\n const type = obj.elements.filter(child => child && child.name && child.name.value === 'type');\n\n if (!type.length) {\n return '';\n }\n\n return (type[0].value && `:${type[0].value.value}`) || '';\n}\n","import codeFrame from '../code-frame';\nimport { getMetaFromPath, getDecoratedDataPath } from '../json/index';\n\nexport default class BaseValidationError {\n constructor(options = { isIdentifierLocation: false }, { data, schema, jsonAst, jsonRaw }) {\n this.options = options;\n this.data = data;\n this.schema = schema;\n this.jsonAst = jsonAst;\n this.jsonRaw = jsonRaw;\n }\n\n getLocation(dataPath = this.instancePath) {\n const { isIdentifierLocation, isSkipEndLocation } = this.options;\n const { loc } = getMetaFromPath(this.jsonAst, dataPath, isIdentifierLocation);\n return {\n start: loc.start,\n end: isSkipEndLocation ? undefined : loc.end,\n };\n }\n\n getDecoratedPath(dataPath = this.instancePath) {\n const decoratedPath = getDecoratedDataPath(this.jsonAst, dataPath);\n return decoratedPath;\n }\n\n // The decorated path is empty at the root, where the message stands alone.\n withDecoratedPath(message, separator = ' ') {\n const decoratedPath = this.getDecoratedPath();\n return decoratedPath ? `${decoratedPath}${separator}${message}` : message;\n }\n\n getCodeFrame(message, dataPath = this.instancePath) {\n return codeFrame(this.jsonRaw, this.getLocation(dataPath), { message });\n }\n\n /**\n * @return {string}\n */\n get instancePath() {\n return typeof this.options.instancePath !== 'undefined'\n ? this.options.instancePath\n : this.options.dataPath;\n }\n\n print() {\n throw new Error(`Implement the 'print' method inside ${this.constructor.name}!`);\n }\n\n getError() {\n throw new Error(`Implement the 'getError' method inside ${this.constructor.name}!`);\n }\n}\n","import { styleText } from 'node:util';\nimport BaseValidationError from './base';\n\nexport default class RequiredValidationError extends BaseValidationError {\n getLocation(dataPath = this.instancePath) {\n const { start } = super.getLocation(dataPath);\n return { start };\n }\n\n print() {\n const { message, params } = this.options;\n const output = [styleText('red', styleText('bold', 'REQUIRED') + ' ' + message) + '\\n'];\n\n return output.concat(\n this.getCodeFrame(\n '☹️ ' + styleText('magentaBright', params.missingProperty) + ' is missing here!'\n )\n );\n }\n\n getError() {\n const { message } = this.options;\n\n return {\n ...this.getLocation(),\n error: this.withDecoratedPath(message),\n path: this.instancePath,\n };\n }\n}\n","import { styleText } from 'node:util';\nimport BaseValidationError from './base';\n\nexport default class AdditionalPropValidationError extends BaseValidationError {\n constructor(...args) {\n super(...args);\n this.options.isIdentifierLocation = true;\n }\n\n print() {\n const { message, params } = this.options;\n const output = [\n styleText('red', styleText('bold', 'ADDITIONAL PROPERTY') + ' ' + message) + '\\n',\n ];\n\n return output.concat(\n this.getCodeFrame(\n '😲 ' +\n styleText('magentaBright', params.additionalProperty) +\n ' is not expected to be here!',\n `${this.instancePath}/${params.additionalProperty}`\n )\n );\n }\n\n getError() {\n const { params } = this.options;\n\n return {\n ...this.getLocation(`${this.instancePath}/${params.additionalProperty}`),\n error: this.withDecoratedPath(\n `Property ${params.additionalProperty} is not expected to be here`\n ),\n path: this.instancePath,\n };\n }\n}\n","import { styleText } from 'node:util';\nimport leven from 'leven';\nimport pointer from 'jsonpointer';\nimport BaseValidationError from './base';\n\nexport default class EnumValidationError extends BaseValidationError {\n print() {\n const {\n message,\n params: { allowedValues },\n } = this.options;\n const bestMatch = this.findBestMatch();\n\n // Needed to handle nullable enums, as joining on null just prints \", \"\n const [firstValue, ...rest] = allowedValues;\n const allowedValuesMessage = rest.reduce((acc, value) => `${acc}, ${value}`, firstValue || '');\n\n const output = [\n styleText('red', styleText('bold', 'ENUM') + ' ' + message),\n styleText('red', `(${allowedValuesMessage})\\n`),\n ];\n\n return output.concat(\n this.getCodeFrame(\n bestMatch !== null\n ? '👈🏽 Did you mean ' + styleText('magentaBright', String(bestMatch)) + ' here?'\n : '👈🏽 Unexpected value, should be equal to one of the allowed values'\n )\n );\n }\n\n getError() {\n const { message, params } = this.options;\n const bestMatch = this.findBestMatch();\n const allowedValues = params.allowedValues.join(', ');\n\n const output = {\n ...this.getLocation(),\n error: this.withDecoratedPath(`${message}: ${allowedValues}`),\n path: this.instancePath,\n };\n\n if (bestMatch !== null) {\n output.suggestion = `Did you mean ${bestMatch}?`;\n }\n\n return output;\n }\n\n findBestMatch() {\n const {\n params: { allowedValues },\n } = this.options;\n\n const currentValue =\n this.instancePath === '' ? this.data : pointer.get(this.data, this.instancePath);\n\n if (!currentValue) {\n return null;\n }\n\n const bestMatch = allowedValues\n .map(value => ({\n value,\n // leven compares strings, so other allowed values cannot be a close match.\n weight: typeof value === 'string' ? leven(value, currentValue.toString()) : Infinity,\n }))\n .sort((x, y) => (x.weight > y.weight ? 1 : x.weight < y.weight ? -1 : 0))[0];\n\n return allowedValues.length === 1 || bestMatch.weight < bestMatch.value.length\n ? bestMatch.value\n : null;\n }\n}\n","import { styleText } from 'node:util';\nimport BaseValidationError from './base';\n\nexport default class DefaultValidationError extends BaseValidationError {\n print() {\n const { keyword, message } = this.options;\n const output = [\n styleText('red', styleText('bold', keyword.toUpperCase()) + ' ' + message) + '\\n',\n ];\n\n return output.concat(\n this.getCodeFrame('👈🏽 ' + styleText('magentaBright', keyword) + ' ' + message)\n );\n }\n\n getError() {\n const { keyword, message } = this.options;\n\n return {\n ...this.getLocation(),\n error: this.withDecoratedPath(`${keyword} ${message}`, ': '),\n path: this.instancePath,\n };\n }\n}\n","import {\n getChildren,\n getErrors,\n getSiblings,\n isAnyOfError,\n isEnumError,\n isRequiredError,\n concatAll,\n notUndefined,\n} from './utils';\nimport {\n AdditionalPropValidationError,\n RequiredValidationError,\n EnumValidationError,\n DefaultValidationError,\n} from './validation-errors/index';\n\nconst JSON_POINTERS_REGEX = /\\/[\\w_-]+(\\/\\d+)?/g;\n\n// Make a tree of errors from ajv errors array\nexport function makeTree(ajvErrors = []) {\n const root = { children: {} };\n ajvErrors.forEach(ajvError => {\n const instancePath =\n typeof ajvError.instancePath !== 'undefined' ? ajvError.instancePath : ajvError.dataPath;\n\n // `dataPath === ''` is root\n const paths = instancePath === '' ? [''] : instancePath.match(JSON_POINTERS_REGEX);\n if (paths) {\n paths.reduce((obj, path, i) => {\n obj.children[path] = obj.children[path] || { children: {}, errors: [] };\n if (i === paths.length - 1) {\n obj.children[path].errors.push(ajvError);\n }\n return obj.children[path];\n }, root);\n }\n });\n return root;\n}\n\nexport function filterRedundantErrors(root, parent, key) {\n /**\n * Partition out the errors by kind for ease of proceessing\n */\n const { anyOfErrors, enumErrors, requiredErrors } = getErrors(root).reduce(\n (acc, error) => {\n if (isRequiredError(error)) {\n acc.requiredErrors.push(error);\n\n return acc;\n }\n\n if (isAnyOfError(error)) {\n acc.anyOfErrors.push(error);\n }\n\n if (isEnumError(error)) {\n acc.enumErrors.push(error);\n }\n\n return acc;\n },\n { anyOfErrors: [], enumErrors: [], requiredErrors: [] }\n );\n\n /**\n * If there is are `required` errors then we can just drop every non-required error.\n * And, also `required` should have more priority than `anyOf`. @see #8\n */\n\n if (requiredErrors.length > 0) {\n root.errors = requiredErrors;\n root.children = {};\n\n // No need for further processing, as we've emptied out children\n return;\n }\n\n /**\n * If there is an `anyOf` error that means we have more meaningful errors\n * inside children. So we will just remove all errors from this level.\n *\n * If there are no children, then we don't delete the errors since we should\n * have at least one error to report.\n */\n if (anyOfErrors.length > 0 && Object.keys(root.children).length > 0) {\n delete root.errors;\n }\n\n /**\n * If all errors are `enum` and siblings have any error then we can safely\n * ignore the node. As we return early if there's required errors, we only\n * need to check anyofErrors length\n *\n * **CAUTION**\n * Need explicit `root.errors` check because `[].every(fn) === true`\n * https://en.wikipedia.org/wiki/Vacuous_truth#Vacuous_truths_in_mathematics\n */\n if (enumErrors.length > 0 && anyOfErrors.length === 0) {\n if (\n getSiblings(parent)(root)\n // Remove any reference which becomes `undefined` later\n .filter(notUndefined)\n .some(getErrors)\n ) {\n delete parent.children[key];\n }\n }\n\n Object.entries(root.children).forEach(([key, child]) => filterRedundantErrors(child, root, key));\n}\n\nexport function createErrorInstances(root, options) {\n const errors = getErrors(root);\n if (errors.length && errors.every(isEnumError)) {\n const uniqueValues = new Set(concatAll([])(errors.map(e => e.params.allowedValues)));\n const allowedValues = [...uniqueValues];\n const error = errors[0];\n return [\n new EnumValidationError(\n {\n ...error,\n params: { allowedValues },\n },\n options\n ),\n ];\n } else {\n return concatAll(\n errors.reduce((ret, error) => {\n switch (error.keyword) {\n case 'additionalProperties':\n return ret.concat(new AdditionalPropValidationError(error, options));\n case 'required':\n return ret.concat(new RequiredValidationError(error, options));\n default:\n return ret.concat(new DefaultValidationError(error, options));\n }\n }, [])\n )(getChildren(root).map(child => createErrorInstances(child, options)));\n }\n}\n\nexport default (ajvErrors, options) => {\n const tree = makeTree(ajvErrors || []);\n filterRedundantErrors(tree);\n return createErrorInstances(tree, options);\n};\n","import { parse } from '@humanwhocodes/momoa';\nimport prettify from './helpers';\n\nexport default (schema, data, errors, options = {}) => {\n const { format = 'cli', indent = null, json = null } = options;\n\n const jsonRaw = json || JSON.stringify(data, null, indent);\n const jsonAst = parse(jsonRaw);\n\n const customErrorToText = error => error.print().join('\\n');\n const customErrorToStructure = error => error.getError();\n const customErrors = prettify(errors, {\n data,\n schema,\n jsonAst,\n jsonRaw,\n });\n\n if (format === 'cli') {\n return customErrors.map(customErrorToText).join('\\n\\n');\n } else {\n return customErrors.map(customErrorToStructure);\n }\n};\n"],"mappings":"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"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { parse, tokenize } from "@humanwhocodes/momoa";
|
|
2
|
+
import { styleText } from "node:util";
|
|
3
|
+
import leven from "leven";
|
|
4
|
+
import pointer from "jsonpointer";
|
|
5
|
+
//#region src/utils.js
|
|
6
|
+
const eq = (x) => (y) => x === y;
|
|
7
|
+
const not = (fn) => (x) => !fn(x);
|
|
8
|
+
const getValues = (o) => Object.values(o);
|
|
9
|
+
const notUndefined = (x) => x !== void 0;
|
|
10
|
+
const isXError = (x) => (error) => error.keyword === x;
|
|
11
|
+
const isRequiredError = isXError("required");
|
|
12
|
+
const isAnyOfError = isXError("anyOf");
|
|
13
|
+
const isEnumError = isXError("enum");
|
|
14
|
+
const getErrors = (node) => node && node.errors ? node.errors.map((e) => e.keyword === "errorMessage" ? {
|
|
15
|
+
...e.params.errors[0],
|
|
16
|
+
message: e.message
|
|
17
|
+
} : e) : [];
|
|
18
|
+
const getChildren = (node) => node && getValues(node.children) || [];
|
|
19
|
+
const getSiblings = (parent) => (node) => getChildren(parent).filter(not(eq(node)));
|
|
20
|
+
const concatAll = (xs) => (ys) => ys.reduce((zs, z) => zs.concat(z), xs);
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/code-frame.js
|
|
23
|
+
const LINES_ABOVE = 2;
|
|
24
|
+
const LINES_BELOW = 3;
|
|
25
|
+
const NEWLINE = /\r\n|[\r\n]/;
|
|
26
|
+
const TOKEN_STYLES = {
|
|
27
|
+
String: "green",
|
|
28
|
+
Number: "magenta",
|
|
29
|
+
Boolean: "cyan",
|
|
30
|
+
Null: "cyan",
|
|
31
|
+
Colon: "yellow",
|
|
32
|
+
Comma: "yellow"
|
|
33
|
+
};
|
|
34
|
+
const MARKER_STYLE = ["red", "bold"];
|
|
35
|
+
const supportsColor = () => styleText("red", "-") !== "-";
|
|
36
|
+
/**
|
|
37
|
+
* Renders the lines of `source` around `location`, with carets under the located text and
|
|
38
|
+
* `message` after the last caret.
|
|
39
|
+
*
|
|
40
|
+
* Lines and columns are 1-based and `end` is exclusive, as in momoa's locations. Without
|
|
41
|
+
* `end`, a single caret marks `start`.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} source
|
|
44
|
+
* @param {{ start: { line: number, column: number }, end?: { line: number, column: number } }} location
|
|
45
|
+
* @param {{ message?: string, color?: boolean }} [options]
|
|
46
|
+
* @return {string}
|
|
47
|
+
*/
|
|
48
|
+
function codeFrame(source, { start, end = start }, { message, color = supportsColor() } = {}) {
|
|
49
|
+
const paint = color ? (format, text) => styleText(format, text, { validateStream: false }) : (_format, text) => text;
|
|
50
|
+
const lines = source.split(NEWLINE);
|
|
51
|
+
const markers = getMarkers(lines, start, end);
|
|
52
|
+
const first = Math.max(start.line - LINES_ABOVE, 1);
|
|
53
|
+
const last = Math.min(end.line + LINES_BELOW, lines.length);
|
|
54
|
+
const width = String(last).length;
|
|
55
|
+
const rows = [];
|
|
56
|
+
for (let number = first; number <= last; number++) {
|
|
57
|
+
const line = lines[number - 1];
|
|
58
|
+
const gutter = ` ${String(number).padStart(width)} |`;
|
|
59
|
+
const code = line.length > 0 ? ` ${color ? highlight(line, paint) : line}` : "";
|
|
60
|
+
const marker = markers.get(number);
|
|
61
|
+
if (!marker) {
|
|
62
|
+
rows.push(` ${paint("gray", gutter)}${code}`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const [column, count] = marker;
|
|
66
|
+
const spacing = line.slice(0, column - 1).replace(/[^\t]/g, " ");
|
|
67
|
+
let caretRow = ` ${paint("gray", gutter.replace(/\d/g, " "))} ${spacing}${paint(MARKER_STYLE, "^".repeat(count))}`;
|
|
68
|
+
if (message && number === end.line) caretRow += ` ${paint(MARKER_STYLE, message)}`;
|
|
69
|
+
rows.push(`${paint(MARKER_STYLE, ">")}${paint("gray", gutter)}${code}`, caretRow);
|
|
70
|
+
}
|
|
71
|
+
return rows.join("\n");
|
|
72
|
+
}
|
|
73
|
+
function getMarkers(lines, start, end) {
|
|
74
|
+
const markers = /* @__PURE__ */ new Map();
|
|
75
|
+
if (start.line === end.line) {
|
|
76
|
+
markers.set(start.line, [start.column, Math.max(end.column - start.column, 1)]);
|
|
77
|
+
return markers;
|
|
78
|
+
}
|
|
79
|
+
markers.set(start.line, [start.column, lines[start.line - 1].length - start.column + 1]);
|
|
80
|
+
for (let number = start.line + 1; number < end.line; number++) markers.set(number, [1, Math.max(lines[number - 1].length, 1)]);
|
|
81
|
+
markers.set(end.line, [1, end.column - 1]);
|
|
82
|
+
return markers;
|
|
83
|
+
}
|
|
84
|
+
function highlight(line, paint) {
|
|
85
|
+
let output = "";
|
|
86
|
+
let offset = 0;
|
|
87
|
+
for (const { type, loc } of tokenize(line)) {
|
|
88
|
+
const style = TOKEN_STYLES[type];
|
|
89
|
+
if (style) {
|
|
90
|
+
output += line.slice(offset, loc.start.offset) + paint(style, line.slice(loc.start.offset, loc.end.offset));
|
|
91
|
+
offset = loc.end.offset;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return output + line.slice(offset);
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/json/utils.js
|
|
98
|
+
const getPointers = (dataPath) => {
|
|
99
|
+
return dataPath.split("/").slice(1).map((pointer) => pointer.split("~1").join("/").split("~0").join("~"));
|
|
100
|
+
};
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/json/get-meta-from-path.js
|
|
103
|
+
function getMetaFromPath(jsonAst, dataPath, includeIdentifierLocation) {
|
|
104
|
+
const pointers = getPointers(dataPath);
|
|
105
|
+
const lastPointerIndex = pointers.length - 1;
|
|
106
|
+
return pointers.reduce((obj, pointer, idx) => {
|
|
107
|
+
switch (obj.type) {
|
|
108
|
+
case "Object": {
|
|
109
|
+
const member = obj.members.findLast((child) => child.name.value === pointer);
|
|
110
|
+
if (!member) throw new Error(`Couldn't find property ${pointer} of ${dataPath}`);
|
|
111
|
+
const { name, value } = member;
|
|
112
|
+
return includeIdentifierLocation && idx === lastPointerIndex ? name : value;
|
|
113
|
+
}
|
|
114
|
+
case "Array": {
|
|
115
|
+
const element = obj.elements[pointer];
|
|
116
|
+
return element && element.value;
|
|
117
|
+
}
|
|
118
|
+
default: console.log(obj);
|
|
119
|
+
}
|
|
120
|
+
}, jsonAst.body);
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/json/get-decorated-data-path.js
|
|
124
|
+
function getDecoratedDataPath(jsonAst, dataPath) {
|
|
125
|
+
let decoratedPath = "";
|
|
126
|
+
getPointers(dataPath).reduce((obj, pointer) => {
|
|
127
|
+
switch (obj.type) {
|
|
128
|
+
case "Object": {
|
|
129
|
+
decoratedPath += `/${pointer}`;
|
|
130
|
+
const member = obj.members.findLast((child) => child.name.value === pointer);
|
|
131
|
+
if (!member) throw new Error(`Couldn't find property ${pointer} of ${dataPath}`);
|
|
132
|
+
return member.value;
|
|
133
|
+
}
|
|
134
|
+
case "Array": {
|
|
135
|
+
const element = obj.elements[pointer];
|
|
136
|
+
const value = element && element.value;
|
|
137
|
+
decoratedPath += `/${pointer}${getTypeName(value)}`;
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
default: console.log(obj);
|
|
141
|
+
}
|
|
142
|
+
}, jsonAst.body);
|
|
143
|
+
return decoratedPath;
|
|
144
|
+
}
|
|
145
|
+
function getTypeName(obj) {
|
|
146
|
+
if (!obj || !obj.elements) return "";
|
|
147
|
+
const type = obj.elements.filter((child) => child && child.name && child.name.value === "type");
|
|
148
|
+
if (!type.length) return "";
|
|
149
|
+
return type[0].value && `:${type[0].value.value}` || "";
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/validation-errors/base.js
|
|
153
|
+
var BaseValidationError = class {
|
|
154
|
+
constructor(options = { isIdentifierLocation: false }, { data, schema, jsonAst, jsonRaw }) {
|
|
155
|
+
this.options = options;
|
|
156
|
+
this.data = data;
|
|
157
|
+
this.schema = schema;
|
|
158
|
+
this.jsonAst = jsonAst;
|
|
159
|
+
this.jsonRaw = jsonRaw;
|
|
160
|
+
}
|
|
161
|
+
getLocation(dataPath = this.instancePath) {
|
|
162
|
+
const { isIdentifierLocation, isSkipEndLocation } = this.options;
|
|
163
|
+
const { loc } = getMetaFromPath(this.jsonAst, dataPath, isIdentifierLocation);
|
|
164
|
+
return {
|
|
165
|
+
start: loc.start,
|
|
166
|
+
end: isSkipEndLocation ? void 0 : loc.end
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
getDecoratedPath(dataPath = this.instancePath) {
|
|
170
|
+
return getDecoratedDataPath(this.jsonAst, dataPath);
|
|
171
|
+
}
|
|
172
|
+
withDecoratedPath(message, separator = " ") {
|
|
173
|
+
const decoratedPath = this.getDecoratedPath();
|
|
174
|
+
return decoratedPath ? `${decoratedPath}${separator}${message}` : message;
|
|
175
|
+
}
|
|
176
|
+
getCodeFrame(message, dataPath = this.instancePath) {
|
|
177
|
+
return codeFrame(this.jsonRaw, this.getLocation(dataPath), { message });
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* @return {string}
|
|
181
|
+
*/
|
|
182
|
+
get instancePath() {
|
|
183
|
+
return typeof this.options.instancePath !== "undefined" ? this.options.instancePath : this.options.dataPath;
|
|
184
|
+
}
|
|
185
|
+
print() {
|
|
186
|
+
throw new Error(`Implement the 'print' method inside ${this.constructor.name}!`);
|
|
187
|
+
}
|
|
188
|
+
getError() {
|
|
189
|
+
throw new Error(`Implement the 'getError' method inside ${this.constructor.name}!`);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/validation-errors/required.js
|
|
194
|
+
var RequiredValidationError = class extends BaseValidationError {
|
|
195
|
+
getLocation(dataPath = this.instancePath) {
|
|
196
|
+
const { start } = super.getLocation(dataPath);
|
|
197
|
+
return { start };
|
|
198
|
+
}
|
|
199
|
+
print() {
|
|
200
|
+
const { message, params } = this.options;
|
|
201
|
+
return [styleText("red", styleText("bold", "REQUIRED") + " " + message) + "\n"].concat(this.getCodeFrame("☹️ " + styleText("magentaBright", params.missingProperty) + " is missing here!"));
|
|
202
|
+
}
|
|
203
|
+
getError() {
|
|
204
|
+
const { message } = this.options;
|
|
205
|
+
return {
|
|
206
|
+
...this.getLocation(),
|
|
207
|
+
error: this.withDecoratedPath(message),
|
|
208
|
+
path: this.instancePath
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/validation-errors/additional-prop.js
|
|
214
|
+
var AdditionalPropValidationError = class extends BaseValidationError {
|
|
215
|
+
constructor(...args) {
|
|
216
|
+
super(...args);
|
|
217
|
+
this.options.isIdentifierLocation = true;
|
|
218
|
+
}
|
|
219
|
+
print() {
|
|
220
|
+
const { message, params } = this.options;
|
|
221
|
+
return [styleText("red", styleText("bold", "ADDITIONAL PROPERTY") + " " + message) + "\n"].concat(this.getCodeFrame("😲 " + styleText("magentaBright", params.additionalProperty) + " is not expected to be here!", `${this.instancePath}/${params.additionalProperty}`));
|
|
222
|
+
}
|
|
223
|
+
getError() {
|
|
224
|
+
const { params } = this.options;
|
|
225
|
+
return {
|
|
226
|
+
...this.getLocation(`${this.instancePath}/${params.additionalProperty}`),
|
|
227
|
+
error: this.withDecoratedPath(`Property ${params.additionalProperty} is not expected to be here`),
|
|
228
|
+
path: this.instancePath
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/validation-errors/enum.js
|
|
234
|
+
var EnumValidationError = class extends BaseValidationError {
|
|
235
|
+
print() {
|
|
236
|
+
const { message, params: { allowedValues } } = this.options;
|
|
237
|
+
const bestMatch = this.findBestMatch();
|
|
238
|
+
const [firstValue, ...rest] = allowedValues;
|
|
239
|
+
const allowedValuesMessage = rest.reduce((acc, value) => `${acc}, ${value}`, firstValue || "");
|
|
240
|
+
return [styleText("red", styleText("bold", "ENUM") + " " + message), styleText("red", `(${allowedValuesMessage})\n`)].concat(this.getCodeFrame(bestMatch !== null ? "👈🏽 Did you mean " + styleText("magentaBright", String(bestMatch)) + " here?" : "👈🏽 Unexpected value, should be equal to one of the allowed values"));
|
|
241
|
+
}
|
|
242
|
+
getError() {
|
|
243
|
+
const { message, params } = this.options;
|
|
244
|
+
const bestMatch = this.findBestMatch();
|
|
245
|
+
const allowedValues = params.allowedValues.join(", ");
|
|
246
|
+
const output = {
|
|
247
|
+
...this.getLocation(),
|
|
248
|
+
error: this.withDecoratedPath(`${message}: ${allowedValues}`),
|
|
249
|
+
path: this.instancePath
|
|
250
|
+
};
|
|
251
|
+
if (bestMatch !== null) output.suggestion = `Did you mean ${bestMatch}?`;
|
|
252
|
+
return output;
|
|
253
|
+
}
|
|
254
|
+
findBestMatch() {
|
|
255
|
+
const { params: { allowedValues } } = this.options;
|
|
256
|
+
const currentValue = this.instancePath === "" ? this.data : pointer.get(this.data, this.instancePath);
|
|
257
|
+
if (!currentValue) return null;
|
|
258
|
+
const bestMatch = allowedValues.map((value) => ({
|
|
259
|
+
value,
|
|
260
|
+
weight: typeof value === "string" ? leven(value, currentValue.toString()) : Infinity
|
|
261
|
+
})).sort((x, y) => x.weight > y.weight ? 1 : x.weight < y.weight ? -1 : 0)[0];
|
|
262
|
+
return allowedValues.length === 1 || bestMatch.weight < bestMatch.value.length ? bestMatch.value : null;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
//#endregion
|
|
266
|
+
//#region src/validation-errors/default.js
|
|
267
|
+
var DefaultValidationError = class extends BaseValidationError {
|
|
268
|
+
print() {
|
|
269
|
+
const { keyword, message } = this.options;
|
|
270
|
+
return [styleText("red", styleText("bold", keyword.toUpperCase()) + " " + message) + "\n"].concat(this.getCodeFrame("👈🏽 " + styleText("magentaBright", keyword) + " " + message));
|
|
271
|
+
}
|
|
272
|
+
getError() {
|
|
273
|
+
const { keyword, message } = this.options;
|
|
274
|
+
return {
|
|
275
|
+
...this.getLocation(),
|
|
276
|
+
error: this.withDecoratedPath(`${keyword} ${message}`, ": "),
|
|
277
|
+
path: this.instancePath
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/helpers.js
|
|
283
|
+
const JSON_POINTERS_REGEX = /\/[\w_-]+(\/\d+)?/g;
|
|
284
|
+
function makeTree(ajvErrors = []) {
|
|
285
|
+
const root = { children: {} };
|
|
286
|
+
ajvErrors.forEach((ajvError) => {
|
|
287
|
+
const instancePath = typeof ajvError.instancePath !== "undefined" ? ajvError.instancePath : ajvError.dataPath;
|
|
288
|
+
const paths = instancePath === "" ? [""] : instancePath.match(JSON_POINTERS_REGEX);
|
|
289
|
+
if (paths) paths.reduce((obj, path, i) => {
|
|
290
|
+
obj.children[path] = obj.children[path] || {
|
|
291
|
+
children: {},
|
|
292
|
+
errors: []
|
|
293
|
+
};
|
|
294
|
+
if (i === paths.length - 1) obj.children[path].errors.push(ajvError);
|
|
295
|
+
return obj.children[path];
|
|
296
|
+
}, root);
|
|
297
|
+
});
|
|
298
|
+
return root;
|
|
299
|
+
}
|
|
300
|
+
function filterRedundantErrors(root, parent, key) {
|
|
301
|
+
/**
|
|
302
|
+
* Partition out the errors by kind for ease of proceessing
|
|
303
|
+
*/
|
|
304
|
+
const { anyOfErrors, enumErrors, requiredErrors } = getErrors(root).reduce((acc, error) => {
|
|
305
|
+
if (isRequiredError(error)) {
|
|
306
|
+
acc.requiredErrors.push(error);
|
|
307
|
+
return acc;
|
|
308
|
+
}
|
|
309
|
+
if (isAnyOfError(error)) acc.anyOfErrors.push(error);
|
|
310
|
+
if (isEnumError(error)) acc.enumErrors.push(error);
|
|
311
|
+
return acc;
|
|
312
|
+
}, {
|
|
313
|
+
anyOfErrors: [],
|
|
314
|
+
enumErrors: [],
|
|
315
|
+
requiredErrors: []
|
|
316
|
+
});
|
|
317
|
+
/**
|
|
318
|
+
* If there is are `required` errors then we can just drop every non-required error.
|
|
319
|
+
* And, also `required` should have more priority than `anyOf`. @see #8
|
|
320
|
+
*/
|
|
321
|
+
if (requiredErrors.length > 0) {
|
|
322
|
+
root.errors = requiredErrors;
|
|
323
|
+
root.children = {};
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* If there is an `anyOf` error that means we have more meaningful errors
|
|
328
|
+
* inside children. So we will just remove all errors from this level.
|
|
329
|
+
*
|
|
330
|
+
* If there are no children, then we don't delete the errors since we should
|
|
331
|
+
* have at least one error to report.
|
|
332
|
+
*/
|
|
333
|
+
if (anyOfErrors.length > 0 && Object.keys(root.children).length > 0) delete root.errors;
|
|
334
|
+
/**
|
|
335
|
+
* If all errors are `enum` and siblings have any error then we can safely
|
|
336
|
+
* ignore the node. As we return early if there's required errors, we only
|
|
337
|
+
* need to check anyofErrors length
|
|
338
|
+
*
|
|
339
|
+
* **CAUTION**
|
|
340
|
+
* Need explicit `root.errors` check because `[].every(fn) === true`
|
|
341
|
+
* https://en.wikipedia.org/wiki/Vacuous_truth#Vacuous_truths_in_mathematics
|
|
342
|
+
*/
|
|
343
|
+
if (enumErrors.length > 0 && anyOfErrors.length === 0) {
|
|
344
|
+
if (getSiblings(parent)(root).filter(notUndefined).some(getErrors)) delete parent.children[key];
|
|
345
|
+
}
|
|
346
|
+
Object.entries(root.children).forEach(([key, child]) => filterRedundantErrors(child, root, key));
|
|
347
|
+
}
|
|
348
|
+
function createErrorInstances(root, options) {
|
|
349
|
+
const errors = getErrors(root);
|
|
350
|
+
if (errors.length && errors.every(isEnumError)) {
|
|
351
|
+
const allowedValues = [...new Set(concatAll([])(errors.map((e) => e.params.allowedValues)))];
|
|
352
|
+
const error = errors[0];
|
|
353
|
+
return [new EnumValidationError({
|
|
354
|
+
...error,
|
|
355
|
+
params: { allowedValues }
|
|
356
|
+
}, options)];
|
|
357
|
+
} else return concatAll(errors.reduce((ret, error) => {
|
|
358
|
+
switch (error.keyword) {
|
|
359
|
+
case "additionalProperties": return ret.concat(new AdditionalPropValidationError(error, options));
|
|
360
|
+
case "required": return ret.concat(new RequiredValidationError(error, options));
|
|
361
|
+
default: return ret.concat(new DefaultValidationError(error, options));
|
|
362
|
+
}
|
|
363
|
+
}, []))(getChildren(root).map((child) => createErrorInstances(child, options)));
|
|
364
|
+
}
|
|
365
|
+
var helpers_default = (ajvErrors, options) => {
|
|
366
|
+
const tree = makeTree(ajvErrors || []);
|
|
367
|
+
filterRedundantErrors(tree);
|
|
368
|
+
return createErrorInstances(tree, options);
|
|
369
|
+
};
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/index.js
|
|
372
|
+
var src_default = (schema, data, errors, options = {}) => {
|
|
373
|
+
const { format = "cli", indent = null, json = null } = options;
|
|
374
|
+
const jsonRaw = json || JSON.stringify(data, null, indent);
|
|
375
|
+
const jsonAst = parse(jsonRaw);
|
|
376
|
+
const customErrorToText = (error) => error.print().join("\n");
|
|
377
|
+
const customErrorToStructure = (error) => error.getError();
|
|
378
|
+
const customErrors = helpers_default(errors, {
|
|
379
|
+
data,
|
|
380
|
+
schema,
|
|
381
|
+
jsonAst,
|
|
382
|
+
jsonRaw
|
|
383
|
+
});
|
|
384
|
+
if (format === "cli") return customErrors.map(customErrorToText).join("\n\n");
|
|
385
|
+
else return customErrors.map(customErrorToStructure);
|
|
386
|
+
};
|
|
387
|
+
//#endregion
|
|
388
|
+
export { src_default as default };
|
|
389
|
+
|
|
390
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["prettify"],"sources":["../src/utils.js","../src/code-frame.js","../src/json/utils.js","../src/json/get-meta-from-path.js","../src/json/get-decorated-data-path.js","../src/validation-errors/base.js","../src/validation-errors/required.js","../src/validation-errors/additional-prop.js","../src/validation-errors/enum.js","../src/validation-errors/default.js","../src/helpers.js","../src/index.js"],"sourcesContent":["// Basic\nconst eq = x => y => x === y;\nconst not = fn => x => !fn(x);\n\nconst getValues = o => Object.values(o);\n\nexport const notUndefined = x => x !== undefined;\n\n// Error\nconst isXError = x => error => error.keyword === x;\nexport const isRequiredError = isXError('required');\nexport const isAnyOfError = isXError('anyOf');\nexport const isEnumError = isXError('enum');\nexport const getErrors = node =>\n node && node.errors\n ? node.errors.map(e =>\n e.keyword === 'errorMessage' ? { ...e.params.errors[0], message: e.message } : e\n )\n : [];\n\n// Node\nexport const getChildren = node => (node && getValues(node.children)) || [];\n\nexport const getSiblings = parent => node => getChildren(parent).filter(not(eq(node)));\n\nexport const concatAll = xs => ys => ys.reduce((zs, z) => zs.concat(z), xs);\n","import { styleText } from 'node:util';\nimport { tokenize } from '@humanwhocodes/momoa';\n\nconst LINES_ABOVE = 2;\nconst LINES_BELOW = 3;\n\n// The line breaks momoa counts, so that its line numbers index into the split source.\nconst NEWLINE = /\\r\\n|[\\r\\n]/;\n\nconst TOKEN_STYLES = {\n String: 'green',\n Number: 'magenta',\n Boolean: 'cyan',\n Null: 'cyan',\n Colon: 'yellow',\n Comma: 'yellow',\n};\n\nconst MARKER_STYLE = ['red', 'bold'];\n\n// The check `styleText` makes for the headings, so that the frame is colored along with them.\nconst supportsColor = () => styleText('red', '-') !== '-';\n\n/**\n * Renders the lines of `source` around `location`, with carets under the located text and\n * `message` after the last caret.\n *\n * Lines and columns are 1-based and `end` is exclusive, as in momoa's locations. Without\n * `end`, a single caret marks `start`.\n *\n * @param {string} source\n * @param {{ start: { line: number, column: number }, end?: { line: number, column: number } }} location\n * @param {{ message?: string, color?: boolean }} [options]\n * @return {string}\n */\nexport default function codeFrame(\n source,\n { start, end = start },\n { message, color = supportsColor() } = {}\n) {\n const paint = color\n ? (format, text) => styleText(format, text, { validateStream: false })\n : (_format, text) => text;\n\n const lines = source.split(NEWLINE);\n const markers = getMarkers(lines, start, end);\n const first = Math.max(start.line - LINES_ABOVE, 1);\n const last = Math.min(end.line + LINES_BELOW, lines.length);\n const width = String(last).length;\n\n const rows = [];\n for (let number = first; number <= last; number++) {\n const line = lines[number - 1];\n const gutter = ` ${String(number).padStart(width)} |`;\n const code = line.length > 0 ? ` ${color ? highlight(line, paint) : line}` : '';\n const marker = markers.get(number);\n if (!marker) {\n rows.push(` ${paint('gray', gutter)}${code}`);\n continue;\n }\n\n const [column, count] = marker;\n const spacing = line.slice(0, column - 1).replace(/[^\\t]/g, ' ');\n let caretRow = ` ${paint('gray', gutter.replace(/\\d/g, ' '))} ${spacing}${paint(MARKER_STYLE, '^'.repeat(count))}`;\n if (message && number === end.line) {\n caretRow += ` ${paint(MARKER_STYLE, message)}`;\n }\n rows.push(`${paint(MARKER_STYLE, '>')}${paint('gray', gutter)}${code}`, caretRow);\n }\n\n return rows.join('\\n');\n}\n\n// Maps each marked line number to the column of its first caret and the number of carets.\nfunction getMarkers(lines, start, end) {\n const markers = new Map();\n if (start.line === end.line) {\n markers.set(start.line, [start.column, Math.max(end.column - start.column, 1)]);\n return markers;\n }\n\n markers.set(start.line, [start.column, lines[start.line - 1].length - start.column + 1]);\n for (let number = start.line + 1; number < end.line; number++) {\n markers.set(number, [1, Math.max(lines[number - 1].length, 1)]);\n }\n markers.set(end.line, [1, end.column - 1]);\n return markers;\n}\n\n// JSON strings can't contain raw line breaks, so each line holds whole tokens.\nfunction highlight(line, paint) {\n let output = '';\n let offset = 0;\n for (const { type, loc } of tokenize(line)) {\n const style = TOKEN_STYLES[type];\n if (style) {\n output +=\n line.slice(offset, loc.start.offset) +\n paint(style, line.slice(loc.start.offset, loc.end.offset));\n offset = loc.end.offset;\n }\n }\n return output + line.slice(offset);\n}\n","// TODO: Better error handling\nexport const getPointers = dataPath => {\n return dataPath\n .split('/')\n .slice(1)\n .map(pointer => pointer.split('~1').join('/').split('~0').join('~'));\n};\n","import { getPointers } from './utils';\n\nexport default function getMetaFromPath(jsonAst, dataPath, includeIdentifierLocation) {\n const pointers = getPointers(dataPath);\n const lastPointerIndex = pointers.length - 1;\n return pointers.reduce((obj, pointer, idx) => {\n switch (obj.type) {\n case 'Object': {\n // JSON.parse keeps the last of duplicate keys, so that is the one ajv validated.\n const member = obj.members.findLast(child => child.name.value === pointer);\n if (!member) {\n throw new Error(`Couldn't find property ${pointer} of ${dataPath}`);\n }\n\n const { name, value } = member;\n return includeIdentifierLocation && idx === lastPointerIndex ? name : value;\n }\n case 'Array': {\n const element = obj.elements[pointer];\n return element && element.value;\n }\n default:\n console.log(obj);\n }\n }, jsonAst.body);\n}\n","import { getPointers } from './utils';\n\nexport default function getDecoratedDataPath(jsonAst, dataPath) {\n let decoratedPath = '';\n getPointers(dataPath).reduce((obj, pointer) => {\n switch (obj.type) {\n case 'Object': {\n decoratedPath += `/${pointer}`;\n // JSON.parse keeps the last of duplicate keys, so that is the one ajv validated.\n const member = obj.members.findLast(child => child.name.value === pointer);\n if (!member) {\n throw new Error(`Couldn't find property ${pointer} of ${dataPath}`);\n }\n return member.value;\n }\n case 'Array': {\n const element = obj.elements[pointer];\n const value = element && element.value;\n decoratedPath += `/${pointer}${getTypeName(value)}`;\n return value;\n }\n default:\n console.log(obj);\n }\n }, jsonAst.body);\n return decoratedPath;\n}\n\nfunction getTypeName(obj) {\n if (!obj || !obj.elements) {\n return '';\n }\n const type = obj.elements.filter(child => child && child.name && child.name.value === 'type');\n\n if (!type.length) {\n return '';\n }\n\n return (type[0].value && `:${type[0].value.value}`) || '';\n}\n","import codeFrame from '../code-frame';\nimport { getMetaFromPath, getDecoratedDataPath } from '../json/index';\n\nexport default class BaseValidationError {\n constructor(options = { isIdentifierLocation: false }, { data, schema, jsonAst, jsonRaw }) {\n this.options = options;\n this.data = data;\n this.schema = schema;\n this.jsonAst = jsonAst;\n this.jsonRaw = jsonRaw;\n }\n\n getLocation(dataPath = this.instancePath) {\n const { isIdentifierLocation, isSkipEndLocation } = this.options;\n const { loc } = getMetaFromPath(this.jsonAst, dataPath, isIdentifierLocation);\n return {\n start: loc.start,\n end: isSkipEndLocation ? undefined : loc.end,\n };\n }\n\n getDecoratedPath(dataPath = this.instancePath) {\n const decoratedPath = getDecoratedDataPath(this.jsonAst, dataPath);\n return decoratedPath;\n }\n\n // The decorated path is empty at the root, where the message stands alone.\n withDecoratedPath(message, separator = ' ') {\n const decoratedPath = this.getDecoratedPath();\n return decoratedPath ? `${decoratedPath}${separator}${message}` : message;\n }\n\n getCodeFrame(message, dataPath = this.instancePath) {\n return codeFrame(this.jsonRaw, this.getLocation(dataPath), { message });\n }\n\n /**\n * @return {string}\n */\n get instancePath() {\n return typeof this.options.instancePath !== 'undefined'\n ? this.options.instancePath\n : this.options.dataPath;\n }\n\n print() {\n throw new Error(`Implement the 'print' method inside ${this.constructor.name}!`);\n }\n\n getError() {\n throw new Error(`Implement the 'getError' method inside ${this.constructor.name}!`);\n }\n}\n","import { styleText } from 'node:util';\nimport BaseValidationError from './base';\n\nexport default class RequiredValidationError extends BaseValidationError {\n getLocation(dataPath = this.instancePath) {\n const { start } = super.getLocation(dataPath);\n return { start };\n }\n\n print() {\n const { message, params } = this.options;\n const output = [styleText('red', styleText('bold', 'REQUIRED') + ' ' + message) + '\\n'];\n\n return output.concat(\n this.getCodeFrame(\n '☹️ ' + styleText('magentaBright', params.missingProperty) + ' is missing here!'\n )\n );\n }\n\n getError() {\n const { message } = this.options;\n\n return {\n ...this.getLocation(),\n error: this.withDecoratedPath(message),\n path: this.instancePath,\n };\n }\n}\n","import { styleText } from 'node:util';\nimport BaseValidationError from './base';\n\nexport default class AdditionalPropValidationError extends BaseValidationError {\n constructor(...args) {\n super(...args);\n this.options.isIdentifierLocation = true;\n }\n\n print() {\n const { message, params } = this.options;\n const output = [\n styleText('red', styleText('bold', 'ADDITIONAL PROPERTY') + ' ' + message) + '\\n',\n ];\n\n return output.concat(\n this.getCodeFrame(\n '😲 ' +\n styleText('magentaBright', params.additionalProperty) +\n ' is not expected to be here!',\n `${this.instancePath}/${params.additionalProperty}`\n )\n );\n }\n\n getError() {\n const { params } = this.options;\n\n return {\n ...this.getLocation(`${this.instancePath}/${params.additionalProperty}`),\n error: this.withDecoratedPath(\n `Property ${params.additionalProperty} is not expected to be here`\n ),\n path: this.instancePath,\n };\n }\n}\n","import { styleText } from 'node:util';\nimport leven from 'leven';\nimport pointer from 'jsonpointer';\nimport BaseValidationError from './base';\n\nexport default class EnumValidationError extends BaseValidationError {\n print() {\n const {\n message,\n params: { allowedValues },\n } = this.options;\n const bestMatch = this.findBestMatch();\n\n // Needed to handle nullable enums, as joining on null just prints \", \"\n const [firstValue, ...rest] = allowedValues;\n const allowedValuesMessage = rest.reduce((acc, value) => `${acc}, ${value}`, firstValue || '');\n\n const output = [\n styleText('red', styleText('bold', 'ENUM') + ' ' + message),\n styleText('red', `(${allowedValuesMessage})\\n`),\n ];\n\n return output.concat(\n this.getCodeFrame(\n bestMatch !== null\n ? '👈🏽 Did you mean ' + styleText('magentaBright', String(bestMatch)) + ' here?'\n : '👈🏽 Unexpected value, should be equal to one of the allowed values'\n )\n );\n }\n\n getError() {\n const { message, params } = this.options;\n const bestMatch = this.findBestMatch();\n const allowedValues = params.allowedValues.join(', ');\n\n const output = {\n ...this.getLocation(),\n error: this.withDecoratedPath(`${message}: ${allowedValues}`),\n path: this.instancePath,\n };\n\n if (bestMatch !== null) {\n output.suggestion = `Did you mean ${bestMatch}?`;\n }\n\n return output;\n }\n\n findBestMatch() {\n const {\n params: { allowedValues },\n } = this.options;\n\n const currentValue =\n this.instancePath === '' ? this.data : pointer.get(this.data, this.instancePath);\n\n if (!currentValue) {\n return null;\n }\n\n const bestMatch = allowedValues\n .map(value => ({\n value,\n // leven compares strings, so other allowed values cannot be a close match.\n weight: typeof value === 'string' ? leven(value, currentValue.toString()) : Infinity,\n }))\n .sort((x, y) => (x.weight > y.weight ? 1 : x.weight < y.weight ? -1 : 0))[0];\n\n return allowedValues.length === 1 || bestMatch.weight < bestMatch.value.length\n ? bestMatch.value\n : null;\n }\n}\n","import { styleText } from 'node:util';\nimport BaseValidationError from './base';\n\nexport default class DefaultValidationError extends BaseValidationError {\n print() {\n const { keyword, message } = this.options;\n const output = [\n styleText('red', styleText('bold', keyword.toUpperCase()) + ' ' + message) + '\\n',\n ];\n\n return output.concat(\n this.getCodeFrame('👈🏽 ' + styleText('magentaBright', keyword) + ' ' + message)\n );\n }\n\n getError() {\n const { keyword, message } = this.options;\n\n return {\n ...this.getLocation(),\n error: this.withDecoratedPath(`${keyword} ${message}`, ': '),\n path: this.instancePath,\n };\n }\n}\n","import {\n getChildren,\n getErrors,\n getSiblings,\n isAnyOfError,\n isEnumError,\n isRequiredError,\n concatAll,\n notUndefined,\n} from './utils';\nimport {\n AdditionalPropValidationError,\n RequiredValidationError,\n EnumValidationError,\n DefaultValidationError,\n} from './validation-errors/index';\n\nconst JSON_POINTERS_REGEX = /\\/[\\w_-]+(\\/\\d+)?/g;\n\n// Make a tree of errors from ajv errors array\nexport function makeTree(ajvErrors = []) {\n const root = { children: {} };\n ajvErrors.forEach(ajvError => {\n const instancePath =\n typeof ajvError.instancePath !== 'undefined' ? ajvError.instancePath : ajvError.dataPath;\n\n // `dataPath === ''` is root\n const paths = instancePath === '' ? [''] : instancePath.match(JSON_POINTERS_REGEX);\n if (paths) {\n paths.reduce((obj, path, i) => {\n obj.children[path] = obj.children[path] || { children: {}, errors: [] };\n if (i === paths.length - 1) {\n obj.children[path].errors.push(ajvError);\n }\n return obj.children[path];\n }, root);\n }\n });\n return root;\n}\n\nexport function filterRedundantErrors(root, parent, key) {\n /**\n * Partition out the errors by kind for ease of proceessing\n */\n const { anyOfErrors, enumErrors, requiredErrors } = getErrors(root).reduce(\n (acc, error) => {\n if (isRequiredError(error)) {\n acc.requiredErrors.push(error);\n\n return acc;\n }\n\n if (isAnyOfError(error)) {\n acc.anyOfErrors.push(error);\n }\n\n if (isEnumError(error)) {\n acc.enumErrors.push(error);\n }\n\n return acc;\n },\n { anyOfErrors: [], enumErrors: [], requiredErrors: [] }\n );\n\n /**\n * If there is are `required` errors then we can just drop every non-required error.\n * And, also `required` should have more priority than `anyOf`. @see #8\n */\n\n if (requiredErrors.length > 0) {\n root.errors = requiredErrors;\n root.children = {};\n\n // No need for further processing, as we've emptied out children\n return;\n }\n\n /**\n * If there is an `anyOf` error that means we have more meaningful errors\n * inside children. So we will just remove all errors from this level.\n *\n * If there are no children, then we don't delete the errors since we should\n * have at least one error to report.\n */\n if (anyOfErrors.length > 0 && Object.keys(root.children).length > 0) {\n delete root.errors;\n }\n\n /**\n * If all errors are `enum` and siblings have any error then we can safely\n * ignore the node. As we return early if there's required errors, we only\n * need to check anyofErrors length\n *\n * **CAUTION**\n * Need explicit `root.errors` check because `[].every(fn) === true`\n * https://en.wikipedia.org/wiki/Vacuous_truth#Vacuous_truths_in_mathematics\n */\n if (enumErrors.length > 0 && anyOfErrors.length === 0) {\n if (\n getSiblings(parent)(root)\n // Remove any reference which becomes `undefined` later\n .filter(notUndefined)\n .some(getErrors)\n ) {\n delete parent.children[key];\n }\n }\n\n Object.entries(root.children).forEach(([key, child]) => filterRedundantErrors(child, root, key));\n}\n\nexport function createErrorInstances(root, options) {\n const errors = getErrors(root);\n if (errors.length && errors.every(isEnumError)) {\n const uniqueValues = new Set(concatAll([])(errors.map(e => e.params.allowedValues)));\n const allowedValues = [...uniqueValues];\n const error = errors[0];\n return [\n new EnumValidationError(\n {\n ...error,\n params: { allowedValues },\n },\n options\n ),\n ];\n } else {\n return concatAll(\n errors.reduce((ret, error) => {\n switch (error.keyword) {\n case 'additionalProperties':\n return ret.concat(new AdditionalPropValidationError(error, options));\n case 'required':\n return ret.concat(new RequiredValidationError(error, options));\n default:\n return ret.concat(new DefaultValidationError(error, options));\n }\n }, [])\n )(getChildren(root).map(child => createErrorInstances(child, options)));\n }\n}\n\nexport default (ajvErrors, options) => {\n const tree = makeTree(ajvErrors || []);\n filterRedundantErrors(tree);\n return createErrorInstances(tree, options);\n};\n","import { parse } from '@humanwhocodes/momoa';\nimport prettify from './helpers';\n\nexport default (schema, data, errors, options = {}) => {\n const { format = 'cli', indent = null, json = null } = options;\n\n const jsonRaw = json || JSON.stringify(data, null, indent);\n const jsonAst = parse(jsonRaw);\n\n const customErrorToText = error => error.print().join('\\n');\n const customErrorToStructure = error => error.getError();\n const customErrors = prettify(errors, {\n data,\n schema,\n jsonAst,\n jsonRaw,\n });\n\n if (format === 'cli') {\n return customErrors.map(customErrorToText).join('\\n\\n');\n } else {\n return customErrors.map(customErrorToStructure);\n }\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,84 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlassian/better-ajv-errors",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"description": "JSON Schema validation for Human",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/atlassian/better-ajv-errors.git"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.cjs",
|
|
10
|
+
"types": "./typings.d.cts",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
"require": {
|
|
14
|
+
"types": "./typings.d.cts",
|
|
15
|
+
"default": "./dist/index.cjs"
|
|
16
|
+
},
|
|
17
|
+
"import": {
|
|
18
|
+
"types": "./typings.d.mts",
|
|
19
|
+
"default": "./dist/index.mjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"module": "./dist/index.mjs",
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">= 22"
|
|
26
|
+
},
|
|
27
|
+
"packageManager": "pnpm@12.11.2",
|
|
28
|
+
"keywords": [
|
|
29
|
+
"json-schema",
|
|
30
|
+
"ajv",
|
|
31
|
+
"ajv-errors"
|
|
32
|
+
],
|
|
33
|
+
"author": "Rifat Nabi <to.rifat@gmail.com>",
|
|
34
|
+
"maintainers": [
|
|
35
|
+
"Rifat Nabi <to.rifat@gmail.com>",
|
|
36
|
+
"Dmitrii Sorin <info@staypositive.ru>",
|
|
37
|
+
"Tong Li"
|
|
38
|
+
],
|
|
39
|
+
"license": "Apache-2.0",
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"typings.d.mts",
|
|
43
|
+
"typings.d.cts"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "vp pack",
|
|
47
|
+
"prerelease": "npm run build",
|
|
48
|
+
"release": "pnpm publish",
|
|
49
|
+
"release:atlassian": "node publish-atlassian.js",
|
|
50
|
+
"format": "vp fmt './src/**/*.js'",
|
|
51
|
+
"format:check": "vp fmt --check './src/**/*.js'",
|
|
52
|
+
"prelint": "npm run build",
|
|
53
|
+
"lint": "vp lint && publint",
|
|
54
|
+
"lint:actions": "actionlint && zizmor .github/workflows",
|
|
55
|
+
"test": "vp test",
|
|
56
|
+
"test-ci": "vp test --coverage",
|
|
57
|
+
"prescreenshot": "npm run build",
|
|
58
|
+
"screenshot": "FORCE_COLOR=1 node screenshot | svg-term --out=./media/screenshot.svg --padding=5 --width=80 --height=13 --at=1000 --no-cursor --term xresources --profile=./media/deep.Xresources --window",
|
|
59
|
+
"prepare": "is-ci || vp config"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@humanwhocodes/momoa": "^3.3.13",
|
|
63
|
+
"jsonpointer": "^5.0.1",
|
|
64
|
+
"leven": "^3.1.0 < 4"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@vitest/coverage-v8": "catalog:",
|
|
68
|
+
"@vitest/ui": "catalog:",
|
|
69
|
+
"ajv": "^8.20.0",
|
|
70
|
+
"is-ci": "^4.1.0",
|
|
71
|
+
"jest-fixtures": "^0.6.0",
|
|
72
|
+
"publint": "^0.3.25",
|
|
73
|
+
"svg-term-cli": "^2.1.1",
|
|
74
|
+
"vite": "catalog:",
|
|
75
|
+
"vite-plus": "catalog:",
|
|
76
|
+
"vitest": "catalog:"
|
|
77
|
+
},
|
|
78
|
+
"peerDependencies": {
|
|
79
|
+
"ajv": "4.11.8 - 8"
|
|
80
|
+
},
|
|
81
|
+
"publishConfig": {
|
|
82
|
+
"registry": "https://packages.atlassian.com/api/npm/npm-public/"
|
|
83
|
+
}
|
|
84
|
+
}
|
package/typings.d.cts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ErrorObject } from 'ajv';
|
|
2
|
+
|
|
3
|
+
export interface IOutputError {
|
|
4
|
+
start: { line: number; column: number; offset: number };
|
|
5
|
+
// Optional for required
|
|
6
|
+
end?: { line: number; column: number; offset: number };
|
|
7
|
+
error: string;
|
|
8
|
+
suggestion?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IInputOptions {
|
|
12
|
+
format?: 'cli' | 'js';
|
|
13
|
+
indent?: number | null;
|
|
14
|
+
|
|
15
|
+
/** Raw JSON used when highlighting error location */
|
|
16
|
+
json?: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function <S, T, Options extends IInputOptions>(
|
|
20
|
+
schema: S,
|
|
21
|
+
data: T,
|
|
22
|
+
errors: Array<ErrorObject>,
|
|
23
|
+
options?: Options
|
|
24
|
+
): Options extends { format: 'js' } ? Array<IOutputError> : string;
|
package/typings.d.mts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ErrorObject } from 'ajv';
|
|
2
|
+
|
|
3
|
+
export interface IOutputError {
|
|
4
|
+
start: { line: number; column: number; offset: number };
|
|
5
|
+
// Optional for required
|
|
6
|
+
end?: { line: number; column: number; offset: number };
|
|
7
|
+
error: string;
|
|
8
|
+
suggestion?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IInputOptions {
|
|
12
|
+
format?: 'cli' | 'js';
|
|
13
|
+
indent?: number | null;
|
|
14
|
+
|
|
15
|
+
/** Raw JSON used when highlighting error location */
|
|
16
|
+
json?: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function <S, T, Options extends IInputOptions>(
|
|
20
|
+
schema: S,
|
|
21
|
+
data: T,
|
|
22
|
+
errors: Array<ErrorObject>,
|
|
23
|
+
options?: Options
|
|
24
|
+
): Options extends { format: 'js' } ? Array<IOutputError> : string;
|