@jarenjs/validate 0.8.4 → 0.34.0
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/ARCHITECTURE.md +1131 -0
- package/LICENSE +21 -0
- package/README.md +796 -2
- package/dist/types/array.d.ts +2 -0
- package/dist/types/bigint.d.ts +1 -0
- package/dist/types/combine.d.ts +1 -0
- package/dist/types/condition.d.ts +1 -0
- package/dist/types/content.d.ts +3 -0
- package/dist/types/data.d.ts +7 -0
- package/dist/types/dollar-data.d.ts +11 -0
- package/dist/types/dynamic-ref.d.ts +44 -0
- package/dist/types/enum.d.ts +1 -0
- package/dist/types/format.d.ts +21 -0
- package/dist/types/index.d.ts +972 -0
- package/dist/types/messages.d.ts +142 -0
- package/dist/types/normalize.d.ts +107 -0
- package/dist/types/number.d.ts +1 -0
- package/dist/types/object.d.ts +3 -0
- package/dist/types/query-keyword.d.ts +19 -0
- package/dist/types/query.d.ts +29 -0
- package/dist/types/schema.d.ts +1 -0
- package/dist/types/string.d.ts +1 -0
- package/dist/types/tools.d.ts +109 -0
- package/dist/types/traverse.d.ts +32 -0
- package/dist/types/unevaluated.d.ts +12 -0
- package/docs/ERROR-MESSAGES.md +251 -0
- package/package.json +37 -7
- package/src/array.js +610 -0
- package/src/bigint.js +108 -0
- package/src/combine.js +276 -0
- package/src/condition.js +129 -0
- package/src/content.js +83 -0
- package/src/data.js +101 -0
- package/src/dollar-data.js +212 -0
- package/src/dynamic-ref.js +121 -0
- package/src/enum.js +147 -0
- package/src/format.js +108 -0
- package/src/index.js +1896 -0
- package/src/messages.js +497 -0
- package/src/normalize.js +585 -0
- package/src/number.js +169 -0
- package/src/object.js +848 -0
- package/src/query-keyword.js +99 -0
- package/src/query.js +85 -0
- package/src/schema.js +690 -0
- package/src/string.js +164 -0
- package/src/tools.js +397 -0
- package/src/traverse.js +442 -0
- package/src/unevaluated.js +173 -0
- package/dist/index.js +0 -1998
- package/dist/index.js.map +0 -7
- package/dist/index.min.js +0 -2
- package/dist/index.min.js.map +0 -7
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
|
|
3
|
+
//#region '$query' - Jaren JSON Query assertions inside JSON Schema
|
|
4
|
+
// The inverse arrow of ./query.js: where that module puts schemas inside
|
|
5
|
+
// queries (the compileTypeTest hook of QUERY-FORMAT.md section 8.11),
|
|
6
|
+
// this module puts queries inside schemas. The '$query' keyword's value
|
|
7
|
+
// is a Jaren JSON Query document, compiled once at schema compile time
|
|
8
|
+
// and evaluated per validation against the current instance location;
|
|
9
|
+
// the instance is valid when the query result's effective boolean value
|
|
10
|
+
// (QUERY-FORMAT.md section 2.2) is true. Like the 'data' keyword, the
|
|
11
|
+
// extension asserts only when spelled - other validators treat '$query'
|
|
12
|
+
// as an unknown-keyword annotation, so such schemas stay portable.
|
|
13
|
+
//
|
|
14
|
+
// Two externals are bound per call: 'root' (the instance root, so
|
|
15
|
+
// "$root.currency" reaches across the document) and 'path' (the current
|
|
16
|
+
// instance location as a JSON pointer string, comparable via "$path").
|
|
17
|
+
// Any other free name in the query is a schema compile error - there is
|
|
18
|
+
// nothing it could be bound to at validation time.
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
compileJsonQuery,
|
|
22
|
+
JsonQueryCompileError,
|
|
23
|
+
JsonQueryRuntimeError,
|
|
24
|
+
} from '@jarenjs/json/query';
|
|
25
|
+
|
|
26
|
+
import { createTypeTestCompiler } from './query.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Compile the '$query' keyword of a schema into a validator.
|
|
30
|
+
*
|
|
31
|
+
* The query document compiles with a `compileTypeTest` hook backed by the
|
|
32
|
+
* owning `JarenValidator` instance (threaded through `ValidationRoot`), so
|
|
33
|
+
* schema literals inside the query (`$valid`/`$assert`/`$as`) may `$ref`
|
|
34
|
+
* schemas registered on that instance with `addSchema`. Malformed query
|
|
35
|
+
* documents (`JQ0xxx`) and externals other than `root`/`path` throw here,
|
|
36
|
+
* at schema compile time. At validation time the query never throws:
|
|
37
|
+
* a `JsonQueryRuntimeError` (`JQ2xxx` - a data-shaped failure such as the
|
|
38
|
+
* EBV of a multi-item result or arithmetic on a non-number) reports as a
|
|
39
|
+
* validation failure whose error params carry the `code` and the query
|
|
40
|
+
* `docPath`.
|
|
41
|
+
*
|
|
42
|
+
* @param {object} schemaObj - The validation object
|
|
43
|
+
* @param {object} jsonSchema - The JSON schema containing the '$query' keyword
|
|
44
|
+
* @returns {function|undefined} The compiled validator function or undefined
|
|
45
|
+
*/
|
|
46
|
+
export function compileQuerySchema(schemaObj, jsonSchema) {
|
|
47
|
+
const queryDoc = jsonSchema.$query;
|
|
48
|
+
if (queryDoc === undefined) return undefined;
|
|
49
|
+
|
|
50
|
+
const owner = schemaObj.root.owner;
|
|
51
|
+
const compileTypeTest = createTypeTestCompiler(owner ?? undefined);
|
|
52
|
+
|
|
53
|
+
let query;
|
|
54
|
+
try {
|
|
55
|
+
query = compileJsonQuery(queryDoc, { compileTypeTest });
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
if (e instanceof JsonQueryCompileError)
|
|
59
|
+
throw new Error(`invalid '$query' document at '${schemaObj.path}': ${e.message}`, { cause: e });
|
|
60
|
+
throw e;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const externals = query.externals;
|
|
64
|
+
for (let i = 0; i < externals.length; ++i) {
|
|
65
|
+
const name = externals[i];
|
|
66
|
+
if (name !== 'root' && name !== 'path')
|
|
67
|
+
throw new Error(`'$query' cannot bind external '${name}' at '${schemaObj.path}' (only 'root' and 'path' are bound)`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const addError = schemaObj.createErrorHandler(queryDoc, '$query');
|
|
71
|
+
|
|
72
|
+
// The compiled query copies externals into its frame before evaluating,
|
|
73
|
+
// so one bindings object per compiled keyword is safe to reuse across
|
|
74
|
+
// validations (a nested '$query' compiles into its own closure world
|
|
75
|
+
// and owns its own object).
|
|
76
|
+
const ext = { root: null, path: '' };
|
|
77
|
+
|
|
78
|
+
return function validateQuerySchema(data, dataPath, dataRoot) {
|
|
79
|
+
if (data === undefined) return true;
|
|
80
|
+
ext.root = dataRoot;
|
|
81
|
+
ext.path = dataPath;
|
|
82
|
+
try {
|
|
83
|
+
return query.ebv(data, ext) || addError(data, dataPath);
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
// Validators must not throw on data: a runtime error is a failed
|
|
87
|
+
// assertion. Compile-time checks left JQ2003 (multi-item EBV) and
|
|
88
|
+
// JQ2001-class operator errors as the reachable conditions here.
|
|
89
|
+
if (e instanceof JsonQueryRuntimeError)
|
|
90
|
+
return addError(data, dataPath, e.code, e.docPath);
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
ext.root = null; // do not pin the last validated document
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//#endregion
|
package/src/query.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
|
|
3
|
+
//#region @jarenjs/validate/query - the Jaren JSON Query type-test bridge
|
|
4
|
+
// Wires this validator into the Jaren JSON Query engine's schema
|
|
5
|
+
// operators ($valid/$assert/$as, QUERY-FORMAT.md section 8.11). The
|
|
6
|
+
// engine (@jarenjs/json) defines only a hook contract - `compileTypeTest:
|
|
7
|
+
// (schemaJson, docPath) => (value) => boolean` - and never imports this
|
|
8
|
+
// package; the dependency runs validate -> json, one way, so no cycle
|
|
9
|
+
// exists. This module turns a JarenValidator into that hook: every schema
|
|
10
|
+
// literal in a query document compiles once, at query compile time, into
|
|
11
|
+
// the same closure world the query engine lives in.
|
|
12
|
+
|
|
13
|
+
import { JarenValidator } from './index.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Project a diagnostic string from whatever the validator threw, without
|
|
17
|
+
* reading `.message` off a raw value or coercing it.
|
|
18
|
+
* @param {unknown} e
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
function failureText(e) {
|
|
22
|
+
if (e instanceof Error && typeof e.message === 'string')
|
|
23
|
+
return e.message;
|
|
24
|
+
return typeof e === 'string' ? e : 'schema compilation failed';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a `compileTypeTest` hook for `compileJsonQuery` (see
|
|
29
|
+
* `@jarenjs/json/query`), backed by a `JarenValidator`.
|
|
30
|
+
*
|
|
31
|
+
* The hook compiles each schema literal with the validator and returns
|
|
32
|
+
* its boolean-mode validation function - errors off, the fast path of
|
|
33
|
+
* this package's architecture. Schema compile failures (an invalid
|
|
34
|
+
* schema literal, an unresolvable `$ref`) propagate as plain errors; the
|
|
35
|
+
* query engine wraps them into `JsonQueryCompileError` `JQ0009` with the
|
|
36
|
+
* operator's document pointer.
|
|
37
|
+
*
|
|
38
|
+
* @param {object | (() => object)} [validator] - a `JarenValidator`
|
|
39
|
+
* instance to compile with, or a zero-argument factory producing one.
|
|
40
|
+
* Supply an instance with registered schemas (`addSchema`) so `$ref`s
|
|
41
|
+
* in query schema literals resolve against them. Omitted, a fresh
|
|
42
|
+
* default (boolean-mode) instance is created.
|
|
43
|
+
* @returns {(schemaJson: any, docPath: string) => ((value: any) => boolean)}
|
|
44
|
+
* a hook suitable for `compileJsonQuery(doc, { compileTypeTest })`
|
|
45
|
+
* @example
|
|
46
|
+
* import { compileJsonQuery } from '@jarenjs/json/query';
|
|
47
|
+
* import { createTypeTestCompiler } from '@jarenjs/validate/query';
|
|
48
|
+
*
|
|
49
|
+
* const query = compileJsonQuery({
|
|
50
|
+
* "$for": { "b": "$.store.book[*]" },
|
|
51
|
+
* "$as": { "b": { "type": "object", "required": ["price"] } },
|
|
52
|
+
* "$return": "$b.title"
|
|
53
|
+
* }, { compileTypeTest: createTypeTestCompiler() });
|
|
54
|
+
*/
|
|
55
|
+
export function createTypeTestCompiler(validator = undefined) {
|
|
56
|
+
const instance = validator == null
|
|
57
|
+
? new JarenValidator()
|
|
58
|
+
: (typeof validator === 'function' ? validator() : validator);
|
|
59
|
+
return function compileTypeTest(schemaJson, docPath) {
|
|
60
|
+
let validate;
|
|
61
|
+
try {
|
|
62
|
+
validate = instance.compile(schemaJson);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
// The engine's JQ0009 names the operator that owns the schema; this
|
|
66
|
+
// names the schema literal itself, which is what distinguishes one
|
|
67
|
+
// failing schema from the others in the same query document.
|
|
68
|
+
const where = typeof docPath === 'string' && docPath !== '' ? docPath : '';
|
|
69
|
+
throw new Error(
|
|
70
|
+
where === ''
|
|
71
|
+
? failureText(err)
|
|
72
|
+
: `schema literal at '${where}': ${failureText(err)}`,
|
|
73
|
+
{ cause: err });
|
|
74
|
+
}
|
|
75
|
+
// The default validator options are boolean mode (skipErrors on,
|
|
76
|
+
// collectErrors off): the compiled function IS the predicate. An
|
|
77
|
+
// error-collecting instance returns { valid, errors } objects
|
|
78
|
+
// instead - detect that shape once, per schema, and unwrap it.
|
|
79
|
+
if (typeof validate(null) === 'boolean')
|
|
80
|
+
return validate;
|
|
81
|
+
return (value) => validate(value).valid === true;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//#endregion
|