@clementine-solutions/jane-io 1.0.1 → 1.0.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/dist/core/analysis/diff.js +88 -0
- package/dist/core/analysis/explain.js +117 -0
- package/dist/core/analysis/index.js +26 -0
- package/dist/core/analysis/replay.js +68 -0
- package/dist/core/analysis/telemetry.js +123 -0
- package/dist/core/boundary-rules/at-most-one.js +38 -0
- package/dist/core/boundary-rules/conditionally-required.js +44 -0
- package/dist/core/boundary-rules/date-range.js +39 -0
- package/dist/core/boundary-rules/index.js +21 -0
- package/dist/core/boundary-rules/mutually-exclusive.js +38 -0
- package/dist/core/boundary-rules/no-unknown-fields.js +39 -0
- package/dist/core/boundary-rules/require-all.js +39 -0
- package/dist/core/boundary-rules/require-one.js +41 -0
- package/dist/core/common/events.js +82 -0
- package/dist/core/common/fluent.js +429 -0
- package/dist/core/common/index.js +31 -0
- package/dist/core/common/policy.js +550 -0
- package/dist/core/common/utilities.js +177 -0
- package/dist/core/common/wildcard.js +63 -0
- package/dist/core/field-path/construct.js +189 -0
- package/dist/core/field-path/format.js +154 -0
- package/dist/core/field-path/index.js +26 -0
- package/dist/core/field-path/utilities.js +138 -0
- package/dist/core/field-path/walk.js +80 -0
- package/dist/core/fluent-registry.js +151 -0
- package/dist/core/normalizers/array/compact-sparse-array.js +40 -0
- package/dist/core/normalizers/array/flatten-one-level.js +45 -0
- package/dist/core/normalizers/array/remove-empty-string-items.js +34 -0
- package/dist/core/normalizers/array/remove-null-items.js +34 -0
- package/dist/core/normalizers/array/remove-undefined-items.js +34 -0
- package/dist/core/normalizers/date/invalid-date-to-undefined.js +35 -0
- package/dist/core/normalizers/index.js +46 -0
- package/dist/core/normalizers/normalizer-register.js +41 -0
- package/dist/core/normalizers/number/infinity-to-undefined.js +35 -0
- package/dist/core/normalizers/number/nan-to-undefined.js +34 -0
- package/dist/core/normalizers/number/normalize-negative-zero.js +33 -0
- package/dist/core/normalizers/object/remove-empty-array-keys.js +38 -0
- package/dist/core/normalizers/object/remove-empty-object-keys.js +42 -0
- package/dist/core/normalizers/object/remove-empty-string-keys.js +37 -0
- package/dist/core/normalizers/object/remove-null-keys.js +37 -0
- package/dist/core/normalizers/object/remove-undefined-keys.js +37 -0
- package/dist/core/normalizers/string/collapse-whitespace.js +35 -0
- package/dist/core/normalizers/string/empty-to-undefined.js +33 -0
- package/dist/core/normalizers/string/trim.js +34 -0
- package/dist/core/parsers/index.js +43 -0
- package/dist/core/parsers/parse-array-string.js +36 -0
- package/dist/core/parsers/parse-bigint-string.js +33 -0
- package/dist/core/parsers/parse-binary-string.js +33 -0
- package/dist/core/parsers/parse-boolean-string.js +27 -0
- package/dist/core/parsers/parse-date-string.js +30 -0
- package/dist/core/parsers/parse-duration-string.js +37 -0
- package/dist/core/parsers/parse-hex-string.js +29 -0
- package/dist/core/parsers/parse-integer-string.js +30 -0
- package/dist/core/parsers/parse-json-string.js +35 -0
- package/dist/core/parsers/parse-numeric-string.js +29 -0
- package/dist/core/parsers/parse-object-string.js +36 -0
- package/dist/core/parsers/parse-octal-string.js +33 -0
- package/dist/core/parsers/parse-scientific-notation-string.js +30 -0
- package/dist/core/parsers/parse-url-string.js +36 -0
- package/dist/core/pipeline/boundary.js +256 -0
- package/dist/core/pipeline/contain.js +339 -0
- package/dist/core/pipeline/index.js +37 -0
- package/dist/core/pipeline/normalize.js +76 -0
- package/dist/core/pipeline/parse.js +91 -0
- package/dist/core/pipeline/pipeline.js +418 -0
- package/dist/core/pipeline/scan.js +115 -0
- package/dist/core/pipeline/validate.js +74 -0
- package/dist/core/scanners/any/scan-for-sentinels.js +35 -0
- package/dist/core/scanners/array/array-is-deep.js +38 -0
- package/dist/core/scanners/array/array-is-heterogenous.js +39 -0
- package/dist/core/scanners/array/array-is-large.js +32 -0
- package/dist/core/scanners/bigint/bigint-is-large.js +34 -0
- package/dist/core/scanners/bigint/bigint-not-safe.js +34 -0
- package/dist/core/scanners/date/date-is-before-epoch.js +32 -0
- package/dist/core/scanners/date/date-is-far-future.js +32 -0
- package/dist/core/scanners/date/date-is-invalid.js +31 -0
- package/dist/core/scanners/index.js +58 -0
- package/dist/core/scanners/number/number-is-infinite.js +31 -0
- package/dist/core/scanners/number/number-is-nan.js +31 -0
- package/dist/core/scanners/number/number-is-too-large.js +33 -0
- package/dist/core/scanners/number/number-is-unsafe-integer.js +31 -0
- package/dist/core/scanners/object/object-has-circular-references.js +43 -0
- package/dist/core/scanners/object/object-has-many-keys.js +33 -0
- package/dist/core/scanners/object/object-is-deep.js +38 -0
- package/dist/core/scanners/scanner-registry.js +36 -0
- package/dist/core/scanners/string/string-has-unsafe-unicode.js +32 -0
- package/dist/core/scanners/string/string-has-whitespace-edges.js +31 -0
- package/dist/core/scanners/string/string-is-long.js +32 -0
- package/dist/core/scanners/unknown/unknown-not-scannable.js +34 -0
- package/dist/core/shapes/analysis.js +11 -0
- package/dist/core/shapes/boundary.js +11 -0
- package/dist/core/shapes/events.js +10 -0
- package/dist/core/shapes/field-path.js +10 -0
- package/dist/core/shapes/index.js +11 -0
- package/dist/core/shapes/normalize.js +11 -0
- package/dist/core/shapes/parse.js +11 -0
- package/dist/core/shapes/pipeline.js +11 -0
- package/dist/core/shapes/policy.js +11 -0
- package/dist/core/shapes/public.js +10 -0
- package/dist/core/shapes/scan.js +11 -0
- package/dist/core/shapes/validate.js +11 -0
- package/dist/core/validators/array/array-max-items.js +42 -0
- package/dist/core/validators/array/array-min-items.js +42 -0
- package/dist/core/validators/array/array.js +34 -0
- package/dist/core/validators/array/excludes.js +47 -0
- package/dist/core/validators/array/has-unique-items.js +46 -0
- package/dist/core/validators/array/includes.js +46 -0
- package/dist/core/validators/array/items-equal.js +42 -0
- package/dist/core/validators/array/no-empty-string-items.js +46 -0
- package/dist/core/validators/array/no-null-items.js +46 -0
- package/dist/core/validators/array/no-undefined-items.js +45 -0
- package/dist/core/validators/array/non-empty-array.js +45 -0
- package/dist/core/validators/array/not-sparse.js +44 -0
- package/dist/core/validators/bigint/bigint-equals.js +57 -0
- package/dist/core/validators/bigint/bigint-max.js +63 -0
- package/dist/core/validators/bigint/bigint-min.js +87 -0
- package/dist/core/validators/bigint/bigint-negative.js +73 -0
- package/dist/core/validators/bigint/bigint-non-negative.js +72 -0
- package/dist/core/validators/bigint/bigint-non-positive.js +72 -0
- package/dist/core/validators/bigint/bigint-positive.js +72 -0
- package/dist/core/validators/bigint/bigint-safe.js +75 -0
- package/dist/core/validators/bigint/bigint.js +38 -0
- package/dist/core/validators/boolean/boolean.js +39 -0
- package/dist/core/validators/boolean/is-false.js +48 -0
- package/dist/core/validators/boolean/is-true.js +48 -0
- package/dist/core/validators/common/is-country-code.js +36 -0
- package/dist/core/validators/common/is-currency-code.js +36 -0
- package/dist/core/validators/common/is-email-strict.js +36 -0
- package/dist/core/validators/common/is-email.js +36 -0
- package/dist/core/validators/common/is-ip.js +37 -0
- package/dist/core/validators/common/is-phone-strict.js +36 -0
- package/dist/core/validators/common/is-phone.js +36 -0
- package/dist/core/validators/common/is-port.js +35 -0
- package/dist/core/validators/common/is-postal-code.js +36 -0
- package/dist/core/validators/common/is-url.js +38 -0
- package/dist/core/validators/common/is-uuid.js +36 -0
- package/dist/core/validators/date/before-epoch.js +56 -0
- package/dist/core/validators/date/date-now-required.js +48 -0
- package/dist/core/validators/date/is-date.js +47 -0
- package/dist/core/validators/date/is-far-future.js +46 -0
- package/dist/core/validators/date/is-future.js +59 -0
- package/dist/core/validators/date/is-past.js +59 -0
- package/dist/core/validators/date/not-after.js +66 -0
- package/dist/core/validators/date/not-before.js +66 -0
- package/dist/core/validators/date/same-day.js +60 -0
- package/dist/core/validators/date/same-month.js +59 -0
- package/dist/core/validators/date/same-year.js +56 -0
- package/dist/core/validators/date/too-early.js +57 -0
- package/dist/core/validators/date/too-late.js +57 -0
- package/dist/core/validators/date/weekday.js +65 -0
- package/dist/core/validators/date/weekend.js +56 -0
- package/dist/core/validators/index.js +139 -0
- package/dist/core/validators/nullish/is-null-or-undefined.js +40 -0
- package/dist/core/validators/nullish/is-null.js +39 -0
- package/dist/core/validators/nullish/is-undefined.js +39 -0
- package/dist/core/validators/number/finite.js +40 -0
- package/dist/core/validators/number/integer.js +40 -0
- package/dist/core/validators/number/less-than.js +39 -0
- package/dist/core/validators/number/max.js +39 -0
- package/dist/core/validators/number/min.js +39 -0
- package/dist/core/validators/number/more-than.js +39 -0
- package/dist/core/validators/number/negative.js +38 -0
- package/dist/core/validators/number/non-negative.js +37 -0
- package/dist/core/validators/number/non-positive.js +37 -0
- package/dist/core/validators/number/number.js +31 -0
- package/dist/core/validators/number/positive.js +38 -0
- package/dist/core/validators/number/safe-integer.js +42 -0
- package/dist/core/validators/object/deep-equals.js +47 -0
- package/dist/core/validators/object/has-key.js +42 -0
- package/dist/core/validators/object/has-value.js +59 -0
- package/dist/core/validators/object/keys-equal.js +47 -0
- package/dist/core/validators/object/max-keys.js +43 -0
- package/dist/core/validators/object/min-keys.js +43 -0
- package/dist/core/validators/object/missing-key.js +42 -0
- package/dist/core/validators/object/no-empty-array-values.js +44 -0
- package/dist/core/validators/object/no-empty-object-values.js +44 -0
- package/dist/core/validators/object/no-null-values.js +44 -0
- package/dist/core/validators/object/no-undefined-values.js +44 -0
- package/dist/core/validators/object/non-empty-object.js +40 -0
- package/dist/core/validators/object/only-keys.js +43 -0
- package/dist/core/validators/object/plain-object.js +35 -0
- package/dist/core/validators/string/alpha-num.js +50 -0
- package/dist/core/validators/string/alpha.js +51 -0
- package/dist/core/validators/string/chars-equal.js +49 -0
- package/dist/core/validators/string/ends-with.js +50 -0
- package/dist/core/validators/string/is-ascii.js +53 -0
- package/dist/core/validators/string/is-printable.js +53 -0
- package/dist/core/validators/string/matches.js +50 -0
- package/dist/core/validators/string/max-length.js +50 -0
- package/dist/core/validators/string/min-length.js +50 -0
- package/dist/core/validators/string/no-lead-space.js +50 -0
- package/dist/core/validators/string/no-repeat-space.js +52 -0
- package/dist/core/validators/string/no-space.js +51 -0
- package/dist/core/validators/string/no-trail-space.js +50 -0
- package/dist/core/validators/string/non-empty.js +48 -0
- package/dist/core/validators/string/not-one-of.js +51 -0
- package/dist/core/validators/string/num-string.js +50 -0
- package/dist/core/validators/string/one-of.js +50 -0
- package/dist/core/validators/string/starts-with.js +50 -0
- package/dist/core/validators/string/string.js +39 -0
- package/dist/core/validators/string/trimmed.js +51 -0
- package/dist/index.js +26 -0
- package/dist/test.js +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Less Than
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that the value is a number strictly less than the
|
|
7
|
+
* given limit.
|
|
8
|
+
* @see https://jane-io.com
|
|
9
|
+
* ----------------------------------------------------------------------------
|
|
10
|
+
*/
|
|
11
|
+
import { validationEvent } from '../../pipeline';
|
|
12
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
13
|
+
import { safeStringify } from '../../common';
|
|
14
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
15
|
+
|* Implementation *|
|
|
16
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
17
|
+
/**
|
|
18
|
+
* Validate that the value is a number strictly less than the given limit.
|
|
19
|
+
*
|
|
20
|
+
* This rule accepts only JavaScript numbers. Non-number values are rejected.
|
|
21
|
+
* If the value is numeric but not less than the provided limit, a
|
|
22
|
+
* number.too.high event is emitted.
|
|
23
|
+
*
|
|
24
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
25
|
+
*/
|
|
26
|
+
export const lessThan = (limit) => async (value, path) => {
|
|
27
|
+
const structuralType = detectStructuralType(value);
|
|
28
|
+
if (typeof value !== 'number') {
|
|
29
|
+
return [
|
|
30
|
+
validationEvent('error', 'type.not.valid', path, `Expected 'number' but received '${structuralType}'.`, `Please enter a valid number.`, { expected: 'number', actual: structuralType }),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
if (!(value < limit)) {
|
|
34
|
+
return [
|
|
35
|
+
validationEvent('error', 'number.too.high', path, `${safeStringify(value)} must be < ${limit}.`, `Please enter a number less than ${limit}.`, { expected: `< ${limit}`, actual: value }),
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
return [];
|
|
39
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Maximum (Max)
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that the value is a number less than or equal to
|
|
7
|
+
* the given limit.
|
|
8
|
+
* @see https://jane-io.com
|
|
9
|
+
* ----------------------------------------------------------------------------
|
|
10
|
+
*/
|
|
11
|
+
import { validationEvent } from '../../pipeline';
|
|
12
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
13
|
+
import { safeStringify } from '../../common';
|
|
14
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
15
|
+
|* Implementation *|
|
|
16
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
17
|
+
/**
|
|
18
|
+
* Validate that the value is a number less than or equal to the given limit.
|
|
19
|
+
*
|
|
20
|
+
* This rule accepts only JavaScript numbers. Non-number values are rejected.
|
|
21
|
+
* If the value is numeric but greater than the provided limit, a
|
|
22
|
+
* number.too.high event is emitted.
|
|
23
|
+
*
|
|
24
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
25
|
+
*/
|
|
26
|
+
export const max = (limit) => async (value, path) => {
|
|
27
|
+
const structuralType = detectStructuralType(value);
|
|
28
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
29
|
+
return [
|
|
30
|
+
validationEvent('error', 'type.not.valid', path, `Expected 'number' but received '${structuralType}'.`, `Please enter a valid number.`, { expected: 'number', actual: structuralType }),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
if (value > limit) {
|
|
34
|
+
return [
|
|
35
|
+
validationEvent('error', 'number.too.high', path, `${safeStringify(value)} must be ≤ ${limit}.`, `Please enter a number no greater than ${limit}.`, { expected: `≤ ${limit}`, actual: value }),
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
return [];
|
|
39
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Minimum (Min)
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that the value is a number greater than or equal
|
|
7
|
+
* to the given limit.
|
|
8
|
+
* @see https://jane-io.com
|
|
9
|
+
* ----------------------------------------------------------------------------
|
|
10
|
+
*/
|
|
11
|
+
import { validationEvent } from '../../pipeline';
|
|
12
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
13
|
+
import { safeStringify } from '../../common';
|
|
14
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
15
|
+
|* Implementation *|
|
|
16
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
17
|
+
/**
|
|
18
|
+
* Validate that the value is a number greater than or equal to the given limit.
|
|
19
|
+
*
|
|
20
|
+
* This rule accepts only JavaScript numbers. Non-number values are rejected.
|
|
21
|
+
* If the value is numeric but less than the provided limit, a
|
|
22
|
+
* number.too.low event is emitted.
|
|
23
|
+
*
|
|
24
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
25
|
+
*/
|
|
26
|
+
export const min = (limit) => async (value, path) => {
|
|
27
|
+
const structuralType = detectStructuralType(value);
|
|
28
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
29
|
+
return [
|
|
30
|
+
validationEvent('error', 'type.not.valid', path, `Expected 'number' but received '${structuralType}'.`, `Please enter a valid number.`, { expected: 'number', actual: structuralType }),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
if (value < limit) {
|
|
34
|
+
return [
|
|
35
|
+
validationEvent('error', 'number.too.low', path, `${safeStringify(value)} must be ≥ ${limit}.`, `Please enter a number at least ${limit}.`, { expected: `≥ ${limit}`, actual: value }),
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
return [];
|
|
39
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | More Than
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that the value is a number strictly greater than
|
|
7
|
+
* the given limit.
|
|
8
|
+
* @see https://jane-io.com
|
|
9
|
+
* ----------------------------------------------------------------------------
|
|
10
|
+
*/
|
|
11
|
+
import { validationEvent } from '../../pipeline';
|
|
12
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
13
|
+
import { safeStringify } from '../../common';
|
|
14
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
15
|
+
|* Implementation *|
|
|
16
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
17
|
+
/**
|
|
18
|
+
* Validate that the value is a number strictly greater than the given limit.
|
|
19
|
+
*
|
|
20
|
+
* This rule accepts only JavaScript numbers. Non-number values and NaN are rejected.
|
|
21
|
+
* If the value is numeric but not greater than the provided limit, a
|
|
22
|
+
* number.too.low event is emitted.
|
|
23
|
+
*
|
|
24
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
25
|
+
*/
|
|
26
|
+
export const moreThan = (limit) => async (value, path) => {
|
|
27
|
+
const structuralType = detectStructuralType(value);
|
|
28
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
29
|
+
return [
|
|
30
|
+
validationEvent('error', 'type.not.valid', path, `Expected 'number' but received '${structuralType}'.`, `Please enter a valid number.`, { expected: 'number', actual: structuralType }),
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
if (!(value > limit)) {
|
|
34
|
+
return [
|
|
35
|
+
validationEvent('error', 'number.too.low', path, `${safeStringify(value)} must be > ${limit}.`, `Please enter a number greater than ${limit}.`, { expected: `> ${limit}`, actual: value }),
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
return [];
|
|
39
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Negative
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that the value is a negative number.
|
|
7
|
+
* @see https://jane-io.com
|
|
8
|
+
* ----------------------------------------------------------------------------
|
|
9
|
+
*/
|
|
10
|
+
import { validationEvent } from '../../pipeline';
|
|
11
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
12
|
+
import { safeStringify } from '../../common';
|
|
13
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
14
|
+
|* Implementation *|
|
|
15
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
16
|
+
/**
|
|
17
|
+
* Validate that the value is a negative number.
|
|
18
|
+
*
|
|
19
|
+
* This rule accepts only JavaScript numbers. Non-number values and NaN are rejected.
|
|
20
|
+
* If the value is numeric but not strictly less than zero, a number.not.negative
|
|
21
|
+
* event is emitted.
|
|
22
|
+
*
|
|
23
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
24
|
+
*/
|
|
25
|
+
export const negative = async (value, path) => {
|
|
26
|
+
const structuralType = detectStructuralType(value);
|
|
27
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
28
|
+
return [
|
|
29
|
+
validationEvent('error', 'type.not.valid', path, `Expected 'number' but received '${structuralType}'.`, `Please enter a valid number.`, { expected: 'number', actual: structuralType }),
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
if (!(value < 0)) {
|
|
33
|
+
return [
|
|
34
|
+
validationEvent('error', 'number.not.negative', path, `${safeStringify(value)} must be < 0.`, `Please enter a negative number.`, { expected: `< 0`, actual: value }),
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
return [];
|
|
38
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Non-Negative
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that the value is a non‑negative number.
|
|
7
|
+
* @see https://jane-io.com
|
|
8
|
+
* ----------------------------------------------------------------------------
|
|
9
|
+
*/
|
|
10
|
+
import { validationEvent } from '../../pipeline';
|
|
11
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
12
|
+
import { safeStringify } from '../../common';
|
|
13
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
14
|
+
|* Implementation *|
|
|
15
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
16
|
+
/**
|
|
17
|
+
* Validate that the value is a non‑negative number.
|
|
18
|
+
*
|
|
19
|
+
* This rule accepts only JavaScript numbers. Non-number values and NaN are rejected.
|
|
20
|
+
* If the value is numeric but less than zero, a number.not.non-negative event is emitted.
|
|
21
|
+
*
|
|
22
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
23
|
+
*/
|
|
24
|
+
export const nonNegative = async (value, path) => {
|
|
25
|
+
const structuralType = detectStructuralType(value);
|
|
26
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
27
|
+
return [
|
|
28
|
+
validationEvent('error', 'type.not.valid', path, `Expected 'number' but received '${structuralType}'.`, `Please enter a valid number.`, { expected: 'number', actual: structuralType }),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
if (value < 0) {
|
|
32
|
+
return [
|
|
33
|
+
validationEvent('error', 'number.not.non-negative', path, `${safeStringify(value)} must be ≥ 0.`, `Please enter zero or a positive number.`, { expected: `≥ 0`, actual: value }),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Non Positive
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that the value is a non‑positive number.
|
|
7
|
+
* @see https://jane-io.com
|
|
8
|
+
* ----------------------------------------------------------------------------
|
|
9
|
+
*/
|
|
10
|
+
import { validationEvent } from '../../pipeline';
|
|
11
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
12
|
+
import { safeStringify } from '../../common';
|
|
13
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
14
|
+
|* Implementation *|
|
|
15
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
16
|
+
/**
|
|
17
|
+
* Validate that the value is a non‑positive number.
|
|
18
|
+
*
|
|
19
|
+
* This rule accepts only JavaScript numbers. Non-number values and NaN are rejected.
|
|
20
|
+
* If the value is numeric but greater than zero, a number.not.non-positive event is emitted.
|
|
21
|
+
*
|
|
22
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
23
|
+
*/
|
|
24
|
+
export const nonPositive = async (value, path) => {
|
|
25
|
+
const structuralType = detectStructuralType(value);
|
|
26
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
27
|
+
return [
|
|
28
|
+
validationEvent('error', 'type.not.valid', path, `Expected 'number' but received '${structuralType}'.`, `Please enter a valid number.`, { expected: 'number', actual: structuralType }),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
if (value > 0) {
|
|
32
|
+
return [
|
|
33
|
+
validationEvent('error', 'number.not.non-positive', path, `${safeStringify(value)} must be ≤ 0.`, `Please enter zero or a negative number.`, { expected: `≤ 0`, actual: value }),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Number
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that the value is a JavaScript number.
|
|
7
|
+
* @see https://jane-io.com
|
|
8
|
+
* ----------------------------------------------------------------------------
|
|
9
|
+
*/
|
|
10
|
+
import { validationEvent } from '../../pipeline';
|
|
11
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
12
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
13
|
+
|* Implementation *|
|
|
14
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
15
|
+
/**
|
|
16
|
+
* Validate that the value is a JavaScript number.
|
|
17
|
+
*
|
|
18
|
+
* This rule accepts only JavaScript numbers. Non-number values and NaN are rejected.
|
|
19
|
+
* If the value is not a number, a type.not.valid event is emitted.
|
|
20
|
+
*
|
|
21
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
22
|
+
*/
|
|
23
|
+
export const number = async (value, path) => {
|
|
24
|
+
const structuralType = detectStructuralType(value);
|
|
25
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
26
|
+
return [
|
|
27
|
+
validationEvent('error', 'type.not.valid', path, `Expected 'number' but received '${structuralType}'.`, `Please enter a valid number.`, { expected: 'number', actual: structuralType }),
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
return [];
|
|
31
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Positive
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that the value is a positive number.
|
|
7
|
+
* @see https://jane-io.com
|
|
8
|
+
* ----------------------------------------------------------------------------
|
|
9
|
+
*/
|
|
10
|
+
import { validationEvent } from '../../pipeline';
|
|
11
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
12
|
+
import { safeStringify } from '../../common';
|
|
13
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
14
|
+
|* Implementation *|
|
|
15
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
16
|
+
/**
|
|
17
|
+
* Validate that the value is a positive number.
|
|
18
|
+
*
|
|
19
|
+
* This rule accepts only JavaScript numbers. Non-number values and NaN are rejected.
|
|
20
|
+
* If the value is numeric but not strictly greater than zero, a number.not.positive
|
|
21
|
+
* event is emitted.
|
|
22
|
+
*
|
|
23
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
24
|
+
*/
|
|
25
|
+
export const positive = async (value, path) => {
|
|
26
|
+
const structuralType = detectStructuralType(value);
|
|
27
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
28
|
+
return [
|
|
29
|
+
validationEvent('error', 'type.not.valid', path, `Expected 'number' but received '${structuralType}'.`, `Please enter a valid number.`, { expected: 'number', actual: structuralType }),
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
if (!(value > 0)) {
|
|
33
|
+
return [
|
|
34
|
+
validationEvent('error', 'number.not.positive', path, `${safeStringify(value)} must be > 0.`, `Please enter a positive number.`, { expected: `> 0`, actual: value }),
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
return [];
|
|
38
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Safe Integer
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validates that a value is a safe integer.
|
|
7
|
+
* @see https://jane-io.com
|
|
8
|
+
* ----------------------------------------------------------------------------
|
|
9
|
+
*/
|
|
10
|
+
import { validationEvent } from '../../pipeline';
|
|
11
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
12
|
+
import { safeStringify } from '../../common';
|
|
13
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
14
|
+
|* Implementation *|
|
|
15
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
16
|
+
/**
|
|
17
|
+
* Validates that a value is a safe integer.
|
|
18
|
+
*
|
|
19
|
+
* Safe integers must be whole numbers that fall within JavaScript’s
|
|
20
|
+
* IEEE‑754 safe range. This protects downstream logic from precision loss,
|
|
21
|
+
* overflow‑adjacent behavior, and malformed numeric input that appears valid
|
|
22
|
+
* but cannot be represented exactly.
|
|
23
|
+
*/
|
|
24
|
+
export const safeInteger = async (value, path) => {
|
|
25
|
+
const type = detectStructuralType(value);
|
|
26
|
+
if (typeof value !== 'number') {
|
|
27
|
+
return [
|
|
28
|
+
validationEvent('error', 'type.not.valid', path, `Expected 'number' but received '${type}'.`, `Please enter a valid number.`, { value, expected: 'number', actual: type }),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
if (!Number.isInteger(value)) {
|
|
32
|
+
return [
|
|
33
|
+
validationEvent('error', 'number.not.integer', path, `${safeStringify(value)} must be an integer.`, `Please enter a whole number.`, { value, expected: 'integer', actual: value }),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
if (!Number.isSafeInteger(value)) {
|
|
37
|
+
return [
|
|
38
|
+
validationEvent('error', 'number.not.safe-integer', path, `${safeStringify(value)} must be a safe integer.`, `Please enter a whole number within safe bounds.`, { value, expected: 'safe integer', actual: value }),
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
return [];
|
|
42
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Deep Equals
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that a value is deeply equal to an expected
|
|
7
|
+
* structural value.
|
|
8
|
+
* @see https://jane-io.com
|
|
9
|
+
* ----------------------------------------------------------------------------
|
|
10
|
+
*/
|
|
11
|
+
import { validationEvent } from '../../pipeline';
|
|
12
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
13
|
+
import { deepEqual, isPlainObject } from '../../common';
|
|
14
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
15
|
+
|* Implementation *|
|
|
16
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
17
|
+
/**
|
|
18
|
+
* Validate that a value is deeply equal to an expected structural value.
|
|
19
|
+
*
|
|
20
|
+
* Deep equality is performed against JSON-compatible values only:
|
|
21
|
+
* - primitives
|
|
22
|
+
* - arrays
|
|
23
|
+
* - plain objects (no class instances, Maps, Sets, Dates, etc.)
|
|
24
|
+
*
|
|
25
|
+
* If the value is not structural, an object.not.plain-object event is emitted.
|
|
26
|
+
* If the value is structural but not deeply equal, an object.not.deeply-equal event is emitted.
|
|
27
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
28
|
+
*/
|
|
29
|
+
export const deepEquals = (expected) => async (value, path) => {
|
|
30
|
+
const structuralType = detectStructuralType(value);
|
|
31
|
+
// Accept primitives, arrays, and plain objects
|
|
32
|
+
const isStructuralValue = value === null ||
|
|
33
|
+
typeof value !== 'object' ||
|
|
34
|
+
Array.isArray(value) ||
|
|
35
|
+
isPlainObject(value);
|
|
36
|
+
if (!isStructuralValue) {
|
|
37
|
+
return [
|
|
38
|
+
validationEvent('error', 'object.not.plain-object', path, `Deep equality requires a structural value, but received '${structuralType}'.`, `Please provide a JSON-compatible value.`, { expected, actual: structuralType }),
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
if (!deepEqual(value, expected)) {
|
|
42
|
+
return [
|
|
43
|
+
validationEvent('error', 'object.not.deeply-equal', path, `Value does not match the expected structure.`, `Please provide the correct value.`, { expected, actual: value }),
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
return [];
|
|
47
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Has Key
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that a plain object contains a required own
|
|
7
|
+
* enumerable key.
|
|
8
|
+
* @see https://jane-io.com
|
|
9
|
+
* ----------------------------------------------------------------------------
|
|
10
|
+
*/
|
|
11
|
+
import { validationEvent } from '../../pipeline';
|
|
12
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
13
|
+
import { isPlainObject } from '../../common';
|
|
14
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
15
|
+
|* Implementation *|
|
|
16
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
17
|
+
/**
|
|
18
|
+
* Validate that a plain object contains a required own enumerable key.
|
|
19
|
+
*
|
|
20
|
+
* This rule accepts only JSON-compatible plain objects (prototype === Object.prototype).
|
|
21
|
+
* Arrays, null, class instances, Maps, Sets, Dates, and other non-plain objects are rejected.
|
|
22
|
+
*
|
|
23
|
+
* If the value is not a plain object, an object.not.plain-object event is emitted.
|
|
24
|
+
* If the required key is missing, an object.missing-key event is emitted.
|
|
25
|
+
*
|
|
26
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
27
|
+
*/
|
|
28
|
+
export const hasKey = (requiredKey) => async (value, path) => {
|
|
29
|
+
const structuralType = detectStructuralType(value);
|
|
30
|
+
if (!isPlainObject(value)) {
|
|
31
|
+
return [
|
|
32
|
+
validationEvent('error', 'object.not.plain-object', path, `Expected a plain object but received '${structuralType}'.`, `Please provide a JSON-compatible object.`, { expected: 'plain object', actual: structuralType }),
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
const hasOwnKey = Object.prototype.hasOwnProperty.call(value, requiredKey);
|
|
36
|
+
if (!hasOwnKey) {
|
|
37
|
+
return [
|
|
38
|
+
validationEvent('error', 'object.has.missing-key', path, `Object must contain key '${requiredKey}'.`, `Please include the required key.`, { expected: requiredKey, actual: Object.keys(value) }),
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
return [];
|
|
42
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Has Value
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that an array or plain object contains a value
|
|
7
|
+
* deeply equal to the target.
|
|
8
|
+
* @see https://jane-io.com
|
|
9
|
+
* ----------------------------------------------------------------------------
|
|
10
|
+
*/
|
|
11
|
+
import { validationEvent } from '../../pipeline';
|
|
12
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
13
|
+
import { deepEqual, isPlainObject, safeStringify } from '../../common';
|
|
14
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
15
|
+
|* Implementation *|
|
|
16
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
17
|
+
/**
|
|
18
|
+
* Validate that an array or plain object contains a value deeply equal to the target.
|
|
19
|
+
*
|
|
20
|
+
* This rule accepts only JSON-compatible arrays and plain objects.
|
|
21
|
+
* Primitives, null, class instances, Maps, Sets, Dates, and other non-collection
|
|
22
|
+
* values are rejected.
|
|
23
|
+
*
|
|
24
|
+
* Arrays are searched using deep equality on each element.
|
|
25
|
+
* Objects are searched using deep equality on each own enumerable value.
|
|
26
|
+
*
|
|
27
|
+
* If the value is not a collection, an object.not.collection event is emitted.
|
|
28
|
+
* If the target value is not found, a value.not.found event is emitted.
|
|
29
|
+
*
|
|
30
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
31
|
+
*/
|
|
32
|
+
export const hasValue = (target) => async (value, path) => {
|
|
33
|
+
const structuralType = detectStructuralType(value);
|
|
34
|
+
const isArray = structuralType === 'array';
|
|
35
|
+
const isPlainObjectValue = structuralType === 'object' && isPlainObject(value);
|
|
36
|
+
if (!isArray && !isPlainObjectValue) {
|
|
37
|
+
return [
|
|
38
|
+
validationEvent('error', 'object.has.missing-value', path, `Expected array or plain object but received '${structuralType}'.`, `Please provide a collection value.`, { expected: 'array | plain object', actual: structuralType }),
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
const found = deepContainsValue(value, target);
|
|
42
|
+
if (!found) {
|
|
43
|
+
return [
|
|
44
|
+
validationEvent('error', 'value.not.found', path, `Value does not contain ${safeStringify(target)}.`, `Please include the required value.`, { expected: target, actual: value }),
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
return [];
|
|
48
|
+
};
|
|
49
|
+
function deepContainsValue(container, target) {
|
|
50
|
+
if (deepEqual(container, target))
|
|
51
|
+
return true;
|
|
52
|
+
if (Array.isArray(container)) {
|
|
53
|
+
return container.some((item) => deepContainsValue(item, target));
|
|
54
|
+
}
|
|
55
|
+
if (isPlainObject(container)) {
|
|
56
|
+
return Object.values(container).some((item) => deepContainsValue(item, target));
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Keys Equal
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that a plain object contains exactly the expected
|
|
7
|
+
* set of keys.
|
|
8
|
+
* @see https://jane-io.com
|
|
9
|
+
* ----------------------------------------------------------------------------
|
|
10
|
+
*/
|
|
11
|
+
import { validationEvent } from '../../pipeline';
|
|
12
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
13
|
+
import { isPlainObject } from '../../common';
|
|
14
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
15
|
+
|* Implementation *|
|
|
16
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
17
|
+
/**
|
|
18
|
+
* Validate that a plain object contains exactly the expected set of keys.
|
|
19
|
+
*
|
|
20
|
+
* This rule accepts only JSON-compatible plain objects (prototype === Object.prototype).
|
|
21
|
+
* Arrays, null, class instances, Maps, Sets, Dates, and other non-plain objects are rejected.
|
|
22
|
+
*
|
|
23
|
+
* The object must contain the same number of keys and the same key names as expectedKeys.
|
|
24
|
+
* Order does not matter. Keys must match exactly.
|
|
25
|
+
*
|
|
26
|
+
* If the value is not a plain object, an object.not.plain-object event is emitted.
|
|
27
|
+
* If the key set does not match, an object.invalid-keys event is emitted.
|
|
28
|
+
*
|
|
29
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
30
|
+
*/
|
|
31
|
+
export const keysEqual = (expectedKeys) => async (value, path) => {
|
|
32
|
+
const structuralType = detectStructuralType(value);
|
|
33
|
+
if (!isPlainObject(value)) {
|
|
34
|
+
return [
|
|
35
|
+
validationEvent('error', 'object.not.plain-object', path, `Expected a plain object but received '${structuralType}'.`, `Please provide a JSON-compatible object.`, { expected: 'plain object', actual: structuralType }),
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
const actualKeys = Object.keys(value);
|
|
39
|
+
const sameLength = actualKeys.length === expectedKeys.length;
|
|
40
|
+
const sameMembers = sameLength && expectedKeys.every((key) => actualKeys.includes(key));
|
|
41
|
+
if (!sameMembers) {
|
|
42
|
+
return [
|
|
43
|
+
validationEvent('error', 'object.has.invalid-key', path, `Object keys must match exactly: ${expectedKeys.join(', ')}.`, `Please provide the correct set of keys.`, { expected: expectedKeys, actual: actualKeys }),
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
return [];
|
|
47
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------
|
|
3
|
+
* Validators | Maximum Keys (Max Keys)
|
|
4
|
+
* ----------------------------------------------------------------------------
|
|
5
|
+
* @package @clementine-solutions/jane
|
|
6
|
+
* @description Validate that a plain object contains no more than a
|
|
7
|
+
* specified number of keys.
|
|
8
|
+
* @see https://jane-io.com
|
|
9
|
+
* ----------------------------------------------------------------------------
|
|
10
|
+
*/
|
|
11
|
+
import { validationEvent } from '../../pipeline';
|
|
12
|
+
import { isPlainObject } from '../../common';
|
|
13
|
+
import { detectStructuralType } from '../../pipeline/scan';
|
|
14
|
+
/* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
|
|
15
|
+
|* Implementation *|
|
|
16
|
+
\* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
|
|
17
|
+
/**
|
|
18
|
+
* Validate that a plain object contains no more than a specified number of keys.
|
|
19
|
+
*
|
|
20
|
+
* This rule accepts only JSON-compatible plain objects (prototype === Object.prototype).
|
|
21
|
+
* Arrays, null, class instances, Maps, Sets, Dates, and other non-plain objects are rejected.
|
|
22
|
+
*
|
|
23
|
+
* If the value is not a plain object, an object.not.plain-object event is emitted.
|
|
24
|
+
* If the object contains more than the allowed number of keys, an object.too.many-keys
|
|
25
|
+
* event is emitted.
|
|
26
|
+
*
|
|
27
|
+
* The rule is async-compatible and returns a readonly array of JaneEvent objects.
|
|
28
|
+
*/
|
|
29
|
+
export const maxKeys = (maximum) => async (value, path) => {
|
|
30
|
+
const structuralType = detectStructuralType(value);
|
|
31
|
+
if (!isPlainObject(value)) {
|
|
32
|
+
return [
|
|
33
|
+
validationEvent('error', 'object.not.plain-object', path, `Expected a plain object but received '${structuralType}'.`, `Please provide a JSON-compatible object.`, { expected: 'plain object', actual: structuralType }),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
const keyCount = Object.keys(value).length;
|
|
37
|
+
if (keyCount > maximum) {
|
|
38
|
+
return [
|
|
39
|
+
validationEvent('error', 'object.too.many-keys', path, `Object has ${keyCount} keys but must have ≤ ${maximum}.`, `Please provide no more than ${maximum} keys.`, { expected: `≤ ${maximum}`, actual: keyCount }),
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
return [];
|
|
43
|
+
};
|