@jarenjs/validate 0.8.4 → 0.9.2
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 +1067 -0
- package/LICENSE +21 -0
- package/README.md +339 -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 +20 -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 +874 -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 +51 -0
- package/dist/types/traverse.d.ts +32 -0
- package/dist/types/unevaluated.d.ts +12 -0
- package/package.json +32 -7
- package/src/array.js +565 -0
- package/src/bigint.js +97 -0
- package/src/combine.js +226 -0
- package/src/condition.js +109 -0
- package/src/content.js +83 -0
- package/src/data.js +477 -0
- package/src/dollar-data.js +629 -0
- package/src/dynamic-ref.js +121 -0
- package/src/enum.js +148 -0
- package/src/format.js +66 -0
- package/src/index.js +1854 -0
- package/src/number.js +159 -0
- package/src/object.js +755 -0
- package/src/query-keyword.js +99 -0
- package/src/query.js +59 -0
- package/src/schema.js +645 -0
- package/src/string.js +152 -0
- package/src/tools.js +205 -0
- package/src/traverse.js +433 -0
- package/src/unevaluated.js +151 -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
package/src/string.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
isStringType,
|
|
5
|
+
getStringType,
|
|
6
|
+
} from '@jarenjs/core';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
getIntishType,
|
|
10
|
+
} from '@jarenjs/core/number';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
createRegExp,
|
|
14
|
+
getStringLength,
|
|
15
|
+
getSegmenter,
|
|
16
|
+
} from '@jarenjs/core/string';
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
trueThat,
|
|
20
|
+
} from '@jarenjs/core/function';
|
|
21
|
+
|
|
22
|
+
function compileMinLength(schemaObj, jsonSchema) {
|
|
23
|
+
const min = getIntishType(jsonSchema.minLength) || 0;
|
|
24
|
+
if (min < 1) return undefined;
|
|
25
|
+
|
|
26
|
+
const addError = schemaObj.createErrorHandler(min, 'minLength');
|
|
27
|
+
|
|
28
|
+
return function validateIsMinLength(len = 0, dataPath) {
|
|
29
|
+
return len >= min
|
|
30
|
+
|| addError(len, dataPath);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function compileMaxLength(schemaObj, jsonSchema) {
|
|
35
|
+
const max = getIntishType(jsonSchema.maxLength) || -1;
|
|
36
|
+
if (max < 0) return undefined;
|
|
37
|
+
|
|
38
|
+
const addError = schemaObj.createErrorHandler(max, 'maxLength');
|
|
39
|
+
|
|
40
|
+
return function validateIsMaxLength(len = 0, dataPath) {
|
|
41
|
+
return len <= max
|
|
42
|
+
|| addError(len, dataPath);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function compilePattern(schemaObj, jsonSchema) {
|
|
47
|
+
// Skip if pattern is a $data reference object (has $data property)
|
|
48
|
+
if (jsonSchema.pattern && typeof jsonSchema.pattern === 'object' && jsonSchema.pattern.$data) return undefined;
|
|
49
|
+
|
|
50
|
+
const pattern = createRegExp(jsonSchema.pattern);
|
|
51
|
+
if (pattern == null) return undefined;
|
|
52
|
+
|
|
53
|
+
const addError = schemaObj.createErrorHandler(pattern, 'pattern');
|
|
54
|
+
|
|
55
|
+
return function validateIsMatch(str = '', dataPath) {
|
|
56
|
+
return pattern.test(str)
|
|
57
|
+
|| addError(str, dataPath);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function compileStringIntern(schemaObj, jsonSchema) {
|
|
62
|
+
const minLength = compileMinLength(schemaObj, jsonSchema);
|
|
63
|
+
const maxLength = compileMaxLength(schemaObj, jsonSchema);
|
|
64
|
+
const pattern = compilePattern(schemaObj, jsonSchema);
|
|
65
|
+
|
|
66
|
+
if ((minLength || maxLength || pattern) == null) return undefined;
|
|
67
|
+
|
|
68
|
+
const isMinLength = minLength || trueThat;
|
|
69
|
+
const isMaxLength = maxLength || trueThat;
|
|
70
|
+
const isMatch = pattern || trueThat;
|
|
71
|
+
const useGrapheme = schemaObj.options.useGrapheme;
|
|
72
|
+
|
|
73
|
+
return function validateStringIntern(data, dataPath) {
|
|
74
|
+
const len = getStringLength(data, useGrapheme);
|
|
75
|
+
return isMinLength(len, dataPath)
|
|
76
|
+
&& isMaxLength(len, dataPath)
|
|
77
|
+
&& isMatch(data, dataPath);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function compileStringBasic(schemaObj, jsonSchema) {
|
|
82
|
+
const intern = compileStringIntern(schemaObj, jsonSchema);
|
|
83
|
+
if (intern == null) return undefined;
|
|
84
|
+
|
|
85
|
+
// Fast path for simple maxLength-only schemas (most common case)
|
|
86
|
+
// This inlines the validation to reduce function call overhead
|
|
87
|
+
const max = getIntishType(jsonSchema.maxLength) ?? -1;
|
|
88
|
+
const min = getIntishType(jsonSchema.minLength) || 0;
|
|
89
|
+
const hasPattern = jsonSchema.pattern != null;
|
|
90
|
+
const useGrapheme = schemaObj.options.useGrapheme;
|
|
91
|
+
|
|
92
|
+
if (!hasPattern) {
|
|
93
|
+
if (!useGrapheme) {
|
|
94
|
+
// Simple maxLength-only without grapheme counting
|
|
95
|
+
if (max >= 0 && min < 1) {
|
|
96
|
+
const addError = schemaObj.createErrorHandler(max, 'maxLength');
|
|
97
|
+
return function validateStringMaxLength(data, dataPath) {
|
|
98
|
+
if (!isStringType(data)) return true;
|
|
99
|
+
return data.length <= max || addError(data.length, dataPath);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Simple minLength-only without grapheme counting
|
|
104
|
+
if (min > 0 && max < 0) {
|
|
105
|
+
const addError = schemaObj.createErrorHandler(min, 'minLength');
|
|
106
|
+
return function validateStringMinLength(data, dataPath) {
|
|
107
|
+
if (!isStringType(data)) return true;
|
|
108
|
+
return data.length >= min || addError(data.length, dataPath);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
// Fast path WITH grapheme counting - inline the ASCII fast-path logic
|
|
113
|
+
// This avoids the function call overhead of getStringLength for ASCII strings
|
|
114
|
+
if (max >= 0 && min < 1) {
|
|
115
|
+
const addError = schemaObj.createErrorHandler(max, 'maxLength');
|
|
116
|
+
return function validateStringMaxLengthGrapheme(data, dataPath) {
|
|
117
|
+
if (!isStringType(data)) return true;
|
|
118
|
+
const len = getStringLength(data, true);
|
|
119
|
+
return len <= max || addError(len, dataPath);
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (min > 0 && max < 0) {
|
|
124
|
+
const addError = schemaObj.createErrorHandler(min, 'minLength');
|
|
125
|
+
return function validateStringMinLengthGrapheme(data, dataPath) {
|
|
126
|
+
if (!isStringType(data)) return true;
|
|
127
|
+
const len = getStringLength(data, true);
|
|
128
|
+
return len >= min || addError(len, dataPath);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Fast path for pattern-only schemas (common in ecmascript-regex tests)
|
|
135
|
+
// This eliminates the intermediate function call overhead
|
|
136
|
+
if (hasPattern && max < 0 && min < 1) {
|
|
137
|
+
const pattern = createRegExp(jsonSchema.pattern);
|
|
138
|
+
if (pattern != null) {
|
|
139
|
+
const addError = schemaObj.createErrorHandler(pattern, 'pattern');
|
|
140
|
+
return function validateStringPatternOnly(data, dataPath) {
|
|
141
|
+
if (!isStringType(data)) return true;
|
|
142
|
+
return pattern.test(data) || addError(data, dataPath);
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Generic case
|
|
148
|
+
return function validateStringBasic(data, dataPath) {
|
|
149
|
+
return !isStringType(data)
|
|
150
|
+
|| intern(data, dataPath);
|
|
151
|
+
};
|
|
152
|
+
}
|
package/src/tools.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
isBigIntType,
|
|
5
|
+
isNullValue,
|
|
6
|
+
isBooleanType,
|
|
7
|
+
isIntegerType,
|
|
8
|
+
isNumberType,
|
|
9
|
+
isStringType,
|
|
10
|
+
isObjectClass,
|
|
11
|
+
isObjectType,
|
|
12
|
+
isMapClass,
|
|
13
|
+
isArrayClass,
|
|
14
|
+
isSetClass,
|
|
15
|
+
} from '@jarenjs/core';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
isRegExpType,
|
|
19
|
+
isStringWhiteSpace,
|
|
20
|
+
} from '@jarenjs/core/string';
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
isArrayish,
|
|
24
|
+
} from '@jarenjs/core/array';
|
|
25
|
+
|
|
26
|
+
//#region Object
|
|
27
|
+
export function isBoolOrObjectClass(obj) {
|
|
28
|
+
return isBooleanType(obj)
|
|
29
|
+
|| isObjectClass(obj);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* getBoolOrObjectClass
|
|
34
|
+
* extract the data of first parameter if data is boolean or object type otherwise return default
|
|
35
|
+
* @param {any} obj any data data has to be tested on boolean or object type
|
|
36
|
+
* @param {boolean | object | undefined} def default return type if not boolean or object type
|
|
37
|
+
* @returns {boolean | undefined} return value when boolean or object otherwise def
|
|
38
|
+
*/
|
|
39
|
+
export function getBoolOrObjectClass(obj, def = undefined) {
|
|
40
|
+
return isBoolOrObjectClass(obj) ? obj : def;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getArrayClassMinItems(obj, len = 1, def = undefined) {
|
|
44
|
+
return (isArrayClass(obj) && obj.length >= len && obj) || def;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
|
|
48
|
+
//#region Schema Helpers
|
|
49
|
+
export function isOfSchemaType(schema, type) {
|
|
50
|
+
const stype = schema.type;
|
|
51
|
+
if (stype == null) return false;
|
|
52
|
+
if (stype === type) return true;
|
|
53
|
+
if (stype.constructor === Array) {
|
|
54
|
+
return stype.includes(type);
|
|
55
|
+
}
|
|
56
|
+
if (stype.constructor === Set) {
|
|
57
|
+
return stype.has(type);
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function hasSchemaRef(schema) {
|
|
63
|
+
return isObjectClass(schema)
|
|
64
|
+
&& isStringType(schema.$ref)
|
|
65
|
+
&& !isStringWhiteSpace(schema.$ref);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function hasSchemaRecursiveRef(schema) {
|
|
69
|
+
return isObjectClass(schema)
|
|
70
|
+
&& isStringType(schema.$recursiveRef)
|
|
71
|
+
&& !isStringWhiteSpace(schema.$recursiveRef);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function hasSchemaDynamicRef(schema) {
|
|
75
|
+
return isObjectClass(schema)
|
|
76
|
+
&& isStringType(schema.$dynamicRef)
|
|
77
|
+
&& !isStringWhiteSpace(schema.$dynamicRef);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function createIsSchemaTypeHandler(type, isStrict = false) {
|
|
81
|
+
switch (type) {
|
|
82
|
+
case 'null': return isNullValue;
|
|
83
|
+
case 'boolean': return isBooleanType;
|
|
84
|
+
case 'integer': return isIntegerType;
|
|
85
|
+
case 'bigint': return isBigIntType;
|
|
86
|
+
case 'number': return isNumberType;
|
|
87
|
+
case 'string': return isStringType;
|
|
88
|
+
case 'object': return isStrict
|
|
89
|
+
? isObjectClass
|
|
90
|
+
: isObjectType;
|
|
91
|
+
case 'array': return isStrict
|
|
92
|
+
? isArrayish
|
|
93
|
+
: isArrayClass;
|
|
94
|
+
case 'set': return isSetClass;
|
|
95
|
+
case 'map': return isMapClass;
|
|
96
|
+
case 'tuple': return isArrayClass;
|
|
97
|
+
case 'regex': return isRegExpType;
|
|
98
|
+
default: break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (type === null)
|
|
102
|
+
return isNullValue;
|
|
103
|
+
|
|
104
|
+
if (typeof type === 'function')
|
|
105
|
+
throw new Error('This is interesting!');
|
|
106
|
+
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Records which properties (string keys) and items (numeric indexes) of a
|
|
114
|
+
* data instance were successfully evaluated during validation, so that
|
|
115
|
+
* unevaluatedProperties/unevaluatedItems can be checked afterwards.
|
|
116
|
+
*
|
|
117
|
+
* Entries are (data reference, key) pairs appended in application order.
|
|
118
|
+
* Applicators that discard annotations (failed anyOf/oneOf branches, not,
|
|
119
|
+
* failed if) take a mark() before running and rollback(mark) afterwards.
|
|
120
|
+
* The numeric key -1 means "all items of this array were evaluated".
|
|
121
|
+
*/
|
|
122
|
+
export class EvalLog {
|
|
123
|
+
#data = [];
|
|
124
|
+
#keys = [];
|
|
125
|
+
#len = 0;
|
|
126
|
+
|
|
127
|
+
/** Clears the log; called at the start of each root validation. */
|
|
128
|
+
reset() {
|
|
129
|
+
this.#data.length = 0;
|
|
130
|
+
this.#keys.length = 0;
|
|
131
|
+
this.#len = 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** @returns {number} The current log position */
|
|
135
|
+
mark() {
|
|
136
|
+
return this.#len;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Discards all entries recorded after the given mark. */
|
|
140
|
+
rollback(mark) {
|
|
141
|
+
this.#len = mark;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Records that `key` of instance `data` was evaluated. */
|
|
145
|
+
add(data, key) {
|
|
146
|
+
this.#data[this.#len] = data;
|
|
147
|
+
this.#keys[this.#len] = key;
|
|
148
|
+
this.#len++;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** @returns {boolean} True when property `key` of `data` was evaluated at or after `from` */
|
|
152
|
+
hasKey(data, key, from) {
|
|
153
|
+
const len = this.#len;
|
|
154
|
+
const datas = this.#data;
|
|
155
|
+
const keys = this.#keys;
|
|
156
|
+
for (let i = from; i < len; ++i) {
|
|
157
|
+
if (datas[i] === data && keys[i] === key) return true;
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** @returns {boolean} True when item `index` of `data` was evaluated at or after `from` (-1 entries cover all items) */
|
|
163
|
+
hasItem(data, index, from) {
|
|
164
|
+
const len = this.#len;
|
|
165
|
+
const datas = this.#data;
|
|
166
|
+
const keys = this.#keys;
|
|
167
|
+
for (let i = from; i < len; ++i) {
|
|
168
|
+
if (datas[i] === data) {
|
|
169
|
+
const k = keys[i];
|
|
170
|
+
if (k === index || k === -1) return true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export class ValidationResult {
|
|
178
|
+
static undefThat() {
|
|
179
|
+
return new ValidationResult();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
constructor(match = false, errors = 0) {
|
|
183
|
+
this.match = match;
|
|
184
|
+
this.errors = Number(errors);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
addValid(valid = true) {
|
|
188
|
+
if (valid === false)// this.errors += valid|0
|
|
189
|
+
this.errors++;
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
addMatch(valid = true) {
|
|
194
|
+
this.match = true;
|
|
195
|
+
if (valid === false)// this.errors += valid|0
|
|
196
|
+
this.errors++;
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
addResult(result = new ValidationResult()) {
|
|
201
|
+
this.match = this.match || result.match;
|
|
202
|
+
this.errors += result.errors;
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
}
|