@platecms/delta-validation 0.4.1 → 0.7.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/package.json +5 -6
- package/src/lib/enums/rule-types.enum.ts +10 -0
- package/src/lib/errors/invalid-validation-arguments.error.ts +1 -0
- package/src/lib/errors/invalid-validation-rule-settings.error.ts +1 -0
- package/src/lib/errors/validation-error-details.ts +15 -0
- package/src/lib/errors/validation.error.ts +18 -0
- package/src/lib/helpers/deserialize-validation-rule.ts +94 -0
- package/src/lib/helpers/rule-to-schema.spec.ts +254 -0
- package/src/lib/helpers/rule-to-schema.ts +95 -0
- package/src/lib/helpers/schema-builder.spec.ts +52 -0
- package/src/lib/helpers/schema-builder.ts +28 -0
- package/src/lib/helpers/validate-with-function.spec.ts +264 -0
- package/src/lib/helpers/validate-with-function.ts +45 -0
- package/src/lib/schemas/array.schema.spec.ts +127 -0
- package/src/lib/schemas/array.schema.ts +104 -0
- package/src/lib/schemas/base.schema.spec.ts +168 -0
- package/src/lib/schemas/base.schema.ts +150 -0
- package/src/lib/schemas/content-value.schema.spec.ts +133 -0
- package/src/lib/schemas/content-value.schema.ts +61 -0
- package/src/lib/schemas/date.schema.spec.ts +144 -0
- package/src/lib/schemas/date.schema.ts +82 -0
- package/src/lib/schemas/number.schema.spec.ts +232 -0
- package/src/lib/schemas/number.schema.ts +123 -0
- package/src/lib/schemas/string.schema.spec.ts +135 -0
- package/src/lib/schemas/string.schema.ts +73 -0
- package/src/lib/test-utils/setup-files.ts +1 -0
- package/src/lib/test-utils/validation-result-matcher.ts +71 -0
- package/src/lib/types/{existing-content-validation-rules-settings.type.d.ts → existing-content-validation-rules-settings.type.ts} +10 -1
- package/src/lib/types/{existing-content-validation-rules.type.d.ts → existing-content-validation-rules.type.ts} +10 -1
- package/src/lib/types/{existing-validation-schemas.type.d.ts → existing-validation-schemas.type.ts} +7 -1
- package/src/lib/types/rule-instance.interface.ts +9 -0
- package/src/lib/types/{validation-context.interface.d.ts → validation-context.interface.ts} +3 -2
- package/src/lib/types/{validation-error-data.interface.d.ts → validation-error-data.interface.ts} +5 -4
- package/src/lib/types/{validation-result.interface.d.ts → validation-result.interface.ts} +3 -2
- package/src/lib/types/validation-schema.interface.ts +15 -0
- package/src/lib/validation-rules/allowed-values.validation-rule.spec.ts +57 -0
- package/src/lib/validation-rules/allowed-values.validation-rule.ts +53 -0
- package/src/lib/validation-rules/base.validation-rule.ts +35 -0
- package/src/lib/validation-rules/count.validation-rule.spec.ts +67 -0
- package/src/lib/validation-rules/count.validation-rule.ts +60 -0
- package/src/lib/validation-rules/date-between.validation-rule.spec.ts +77 -0
- package/src/lib/validation-rules/date-between.validation-rule.ts +54 -0
- package/src/lib/validation-rules/decimal-count.validation-rule.spec.ts +46 -0
- package/src/lib/validation-rules/decimal-count.validation-rule.ts +52 -0
- package/src/lib/validation-rules/number-between.validation-rule.spec.ts +77 -0
- package/src/lib/validation-rules/number-between.validation-rule.ts +58 -0
- package/src/lib/validation-rules/relatable-content-types.validation-rule.spec.ts +35 -0
- package/src/lib/validation-rules/relatable-content-types.validation-rule.ts +61 -0
- package/src/lib/validation-rules/string-format.validation-rule.spec.ts +43 -0
- package/src/lib/validation-rules/string-format.validation-rule.ts +50 -0
- package/src/lib/validation-rules/validation-rule-function.factory.ts +23 -0
- package/src/lib/validation-rules/value-type.validation-rule.spec.ts +108 -0
- package/src/lib/validation-rules/value-type.validation-rule.ts +76 -0
- package/src/index.js +0 -23
- package/src/index.js.map +0 -1
- package/src/lib/enums/rule-types.enum.d.ts +0 -10
- package/src/lib/enums/rule-types.enum.js +0 -15
- package/src/lib/enums/rule-types.enum.js.map +0 -1
- package/src/lib/errors/invalid-validation-arguments.error.d.ts +0 -2
- package/src/lib/errors/invalid-validation-arguments.error.js +0 -7
- package/src/lib/errors/invalid-validation-arguments.error.js.map +0 -1
- package/src/lib/errors/invalid-validation-rule-settings.error.d.ts +0 -2
- package/src/lib/errors/invalid-validation-rule-settings.error.js +0 -7
- package/src/lib/errors/invalid-validation-rule-settings.error.js.map +0 -1
- package/src/lib/errors/validation-error-details.d.ts +0 -6
- package/src/lib/errors/validation-error-details.js +0 -18
- package/src/lib/errors/validation-error-details.js.map +0 -1
- package/src/lib/errors/validation.error.d.ts +0 -8
- package/src/lib/errors/validation.error.js +0 -13
- package/src/lib/errors/validation.error.js.map +0 -1
- package/src/lib/helpers/deserialize-validation-rule.d.ts +0 -2
- package/src/lib/helpers/deserialize-validation-rule.js +0 -42
- package/src/lib/helpers/deserialize-validation-rule.js.map +0 -1
- package/src/lib/helpers/rule-to-schema.d.ts +0 -4
- package/src/lib/helpers/rule-to-schema.js +0 -65
- package/src/lib/helpers/rule-to-schema.js.map +0 -1
- package/src/lib/helpers/schema-builder.d.ts +0 -12
- package/src/lib/helpers/schema-builder.js +0 -27
- package/src/lib/helpers/schema-builder.js.map +0 -1
- package/src/lib/helpers/validate-with-function.d.ts +0 -4
- package/src/lib/helpers/validate-with-function.js +0 -31
- package/src/lib/helpers/validate-with-function.js.map +0 -1
- package/src/lib/schemas/array.schema.d.ts +0 -17
- package/src/lib/schemas/array.schema.js +0 -84
- package/src/lib/schemas/array.schema.js.map +0 -1
- package/src/lib/schemas/base.schema.d.ts +0 -20
- package/src/lib/schemas/base.schema.js +0 -95
- package/src/lib/schemas/base.schema.js.map +0 -1
- package/src/lib/schemas/content-value.schema.d.ts +0 -15
- package/src/lib/schemas/content-value.schema.js +0 -42
- package/src/lib/schemas/content-value.schema.js.map +0 -1
- package/src/lib/schemas/date.schema.d.ts +0 -13
- package/src/lib/schemas/date.schema.js +0 -62
- package/src/lib/schemas/date.schema.js.map +0 -1
- package/src/lib/schemas/number.schema.d.ts +0 -16
- package/src/lib/schemas/number.schema.js +0 -84
- package/src/lib/schemas/number.schema.js.map +0 -1
- package/src/lib/schemas/string.schema.d.ts +0 -11
- package/src/lib/schemas/string.schema.js +0 -56
- package/src/lib/schemas/string.schema.js.map +0 -1
- package/src/lib/types/existing-content-validation-rules-settings.type.js +0 -3
- package/src/lib/types/existing-content-validation-rules-settings.type.js.map +0 -1
- package/src/lib/types/existing-content-validation-rules.type.js +0 -3
- package/src/lib/types/existing-content-validation-rules.type.js.map +0 -1
- package/src/lib/types/existing-validation-schemas.type.js +0 -3
- package/src/lib/types/existing-validation-schemas.type.js.map +0 -1
- package/src/lib/types/rule-instance.interface.d.ts +0 -6
- package/src/lib/types/rule-instance.interface.js +0 -3
- package/src/lib/types/rule-instance.interface.js.map +0 -1
- package/src/lib/types/validation-context.interface.js +0 -3
- package/src/lib/types/validation-context.interface.js.map +0 -1
- package/src/lib/types/validation-error-data.interface.js +0 -3
- package/src/lib/types/validation-error-data.interface.js.map +0 -1
- package/src/lib/types/validation-result.interface.js +0 -3
- package/src/lib/types/validation-result.interface.js.map +0 -1
- package/src/lib/types/validation-schema.interface.d.ts +0 -7
- package/src/lib/types/validation-schema.interface.js +0 -3
- package/src/lib/types/validation-schema.interface.js.map +0 -1
- package/src/lib/validation-rules/allowed-values.validation-rule.d.ts +0 -13
- package/src/lib/validation-rules/allowed-values.validation-rule.js +0 -36
- package/src/lib/validation-rules/allowed-values.validation-rule.js.map +0 -1
- package/src/lib/validation-rules/base.validation-rule.d.ts +0 -13
- package/src/lib/validation-rules/base.validation-rule.js +0 -14
- package/src/lib/validation-rules/base.validation-rule.js.map +0 -1
- package/src/lib/validation-rules/count.validation-rule.d.ts +0 -16
- package/src/lib/validation-rules/count.validation-rule.js +0 -36
- package/src/lib/validation-rules/count.validation-rule.js.map +0 -1
- package/src/lib/validation-rules/date-between.validation-rule.d.ts +0 -16
- package/src/lib/validation-rules/date-between.validation-rule.js +0 -36
- package/src/lib/validation-rules/date-between.validation-rule.js.map +0 -1
- package/src/lib/validation-rules/decimal-count.validation-rule.d.ts +0 -13
- package/src/lib/validation-rules/decimal-count.validation-rule.js +0 -37
- package/src/lib/validation-rules/decimal-count.validation-rule.js.map +0 -1
- package/src/lib/validation-rules/number-between.validation-rule.d.ts +0 -16
- package/src/lib/validation-rules/number-between.validation-rule.js +0 -37
- package/src/lib/validation-rules/number-between.validation-rule.js.map +0 -1
- package/src/lib/validation-rules/relatable-content-types.validation-rule.d.ts +0 -13
- package/src/lib/validation-rules/relatable-content-types.validation-rule.js +0 -37
- package/src/lib/validation-rules/relatable-content-types.validation-rule.js.map +0 -1
- package/src/lib/validation-rules/string-format.validation-rule.d.ts +0 -13
- package/src/lib/validation-rules/string-format.validation-rule.js +0 -35
- package/src/lib/validation-rules/string-format.validation-rule.js.map +0 -1
- package/src/lib/validation-rules/validation-rule-function.factory.d.ts +0 -4
- package/src/lib/validation-rules/validation-rule-function.factory.js +0 -9
- package/src/lib/validation-rules/validation-rule-function.factory.js.map +0 -1
- package/src/lib/validation-rules/value-type.validation-rule.d.ts +0 -13
- package/src/lib/validation-rules/value-type.validation-rule.js +0 -53
- package/src/lib/validation-rules/value-type.validation-rule.js.map +0 -1
- /package/src/{index.d.ts → index.ts} +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ValidationErrorDetails } from "../errors/validation-error-details";
|
|
2
|
+
import { ContentValidationResultType } from "../validation-rules/base.validation-rule";
|
|
3
|
+
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
5
|
+
interface CustomMatchers<R = unknown> {
|
|
6
|
+
toBeContentValidationResult: (expected: (boolean | null)[]) => R;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare module "vitest" {
|
|
10
|
+
/* eslint-disable @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type */
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
interface Assertion<T = any> extends CustomMatchers<T> {}
|
|
13
|
+
interface AsymmetricMatchersContaining extends CustomMatchers {}
|
|
14
|
+
/* eslint-enable @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type */
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
expect.extend({
|
|
18
|
+
toBeContentValidationResult(
|
|
19
|
+
received: ContentValidationResultType[],
|
|
20
|
+
expected: (boolean | null)[],
|
|
21
|
+
): {
|
|
22
|
+
pass: boolean;
|
|
23
|
+
message: () => string;
|
|
24
|
+
actual?: unknown;
|
|
25
|
+
expected?: unknown;
|
|
26
|
+
} {
|
|
27
|
+
const { isNot } = this;
|
|
28
|
+
|
|
29
|
+
// eslint-disable-next-line no-unreachable-loop
|
|
30
|
+
for (let index = 0; index < received.length; index++) {
|
|
31
|
+
const result = received[index];
|
|
32
|
+
const expectedValue = expected[index];
|
|
33
|
+
|
|
34
|
+
if (expectedValue === false) {
|
|
35
|
+
if (result instanceof ValidationErrorDetails) {
|
|
36
|
+
return {
|
|
37
|
+
pass: true,
|
|
38
|
+
message: (): string =>
|
|
39
|
+
`Expected ${JSON.stringify(
|
|
40
|
+
result,
|
|
41
|
+
)}${isNot ? " not" : ""} to be an instance of ContentValidationErrorDetail. Index ${index} failed.`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
pass: false,
|
|
47
|
+
message: (): string =>
|
|
48
|
+
`Expected ${JSON.stringify(
|
|
49
|
+
result,
|
|
50
|
+
)}${isNot ? " not" : ""} to be an instance of ContentValidationErrorDetail. Index ${index} failed.`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (expectedValue === result) {
|
|
55
|
+
return {
|
|
56
|
+
pass: true,
|
|
57
|
+
message: (): string =>
|
|
58
|
+
`Expected ${JSON.stringify(result)}${isNot ? " not" : ""} to be ${JSON.stringify(expectedValue)}. Index ${index} failed.`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
pass: false,
|
|
64
|
+
message: (): string =>
|
|
65
|
+
`Expected ${JSON.stringify(received)}${isNot ? " not" : ""} to be ${JSON.stringify(expected)}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
throw new Error("No results to compare.");
|
|
70
|
+
},
|
|
71
|
+
});
|
|
@@ -7,4 +7,13 @@ import { NumberBetweenValidationRuleSettings } from "../validation-rules/number-
|
|
|
7
7
|
import { RelatableContentTypesValidationRuleSettings } from "../validation-rules/relatable-content-types.validation-rule";
|
|
8
8
|
import { StringFormatValidationRuleSettings } from "../validation-rules/string-format.validation-rule";
|
|
9
9
|
import { ValueTypeValidationRuleSettings } from "../validation-rules/value-type.validation-rule";
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
export type ExistingContentValidationRulesSettings =
|
|
12
|
+
| AllowedValuesValidationRuleSettings
|
|
13
|
+
| CountValidationRuleSettings
|
|
14
|
+
| DateBetweenValidationRuleSettings
|
|
15
|
+
| DecimalCountValidationRuleSettings
|
|
16
|
+
| NumberBetweenValidationRuleSettings
|
|
17
|
+
| RelatableContentTypesValidationRuleSettings<PRN>
|
|
18
|
+
| StringFormatValidationRuleSettings
|
|
19
|
+
| ValueTypeValidationRuleSettings;
|
|
@@ -6,4 +6,13 @@ import { NumberBetweenValidationRule } from "../validation-rules/number-between.
|
|
|
6
6
|
import { RelatableContentTypesValidationRule } from "../validation-rules/relatable-content-types.validation-rule";
|
|
7
7
|
import { StringFormatValidationRule } from "../validation-rules/string-format.validation-rule";
|
|
8
8
|
import { ValueTypeValidationRule } from "../validation-rules/value-type.validation-rule";
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
export type ExistingContentValidationRules =
|
|
11
|
+
| AllowedValuesValidationRule
|
|
12
|
+
| CountValidationRule
|
|
13
|
+
| DateBetweenValidationRule
|
|
14
|
+
| DecimalCountValidationRule
|
|
15
|
+
| NumberBetweenValidationRule
|
|
16
|
+
| RelatableContentTypesValidationRule
|
|
17
|
+
| StringFormatValidationRule
|
|
18
|
+
| ValueTypeValidationRule;
|
package/src/lib/types/{existing-validation-schemas.type.d.ts → existing-validation-schemas.type.ts}
RENAMED
|
@@ -3,4 +3,10 @@ import { DateValidationSchema } from "../schemas/date.schema";
|
|
|
3
3
|
import { ContentValueValidationSchema } from "../schemas/content-value.schema";
|
|
4
4
|
import { NumberValidationSchema } from "../schemas/number.schema";
|
|
5
5
|
import { StringValidationSchema } from "../schemas/string.schema";
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
export type ExistingValidationSchemas =
|
|
8
|
+
| ArrayValidationSchema<unknown>
|
|
9
|
+
| ContentValueValidationSchema
|
|
10
|
+
| DateValidationSchema
|
|
11
|
+
| NumberValidationSchema
|
|
12
|
+
| StringValidationSchema;
|
package/src/lib/types/{validation-error-data.interface.d.ts → validation-error-data.interface.ts}
RENAMED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { RuleInstance } from "./rule-instance.interface";
|
|
2
|
+
|
|
2
3
|
export interface ValidationErrorData<TSettings extends object = object> {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
path: string[];
|
|
5
|
+
message: string;
|
|
6
|
+
validationRule: RuleInstance<TSettings>;
|
|
7
|
+
provided: unknown;
|
|
7
8
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ValidationContext } from "./validation-context.interface";
|
|
2
|
+
import { ValidationResult } from "./validation-result.interface";
|
|
3
|
+
|
|
4
|
+
export type SchemaType = "array" | "content-value" | "date" | "number" | "string" | "union";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* TType represents the value type this schema validates.
|
|
8
|
+
* Even if validate() takes unknown, the generic links
|
|
9
|
+
* concrete schemas (e.g. StringSchema -> string, ArraySchema<TType> -> TType[])
|
|
10
|
+
* and enables type-safe composition.
|
|
11
|
+
*/
|
|
12
|
+
export interface ValidationSchema<TType> {
|
|
13
|
+
type: SchemaType;
|
|
14
|
+
validate(value: unknown, context?: ValidationContext): ValidationResult;
|
|
15
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { PRN } from "@platecms/delta-plate-resource-notation";
|
|
2
|
+
import { ContentValueType } from "@platecms/delta-types";
|
|
3
|
+
import { AllowedValuesValidationRule } from "./allowed-values.validation-rule";
|
|
4
|
+
|
|
5
|
+
describe("AllowedValuesValidationRule", () => {
|
|
6
|
+
let rulePrn: PRN;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
rulePrn = PRN.fromString("prn:plate:-1:cxc:validation-rule:1");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("validate", () => {
|
|
13
|
+
describe("with mixed type allowed values", () => {
|
|
14
|
+
let rule: AllowedValuesValidationRule;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
const allowedValues = [1, "2", true, new Date("2020-01-01"), PRN.fromString("prn:plate:-1:cxc:content-item:1")];
|
|
18
|
+
rule = new AllowedValuesValidationRule(rulePrn, { allowedValues });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns an array of correct validation results", () => {
|
|
22
|
+
const result = rule.validate([
|
|
23
|
+
1,
|
|
24
|
+
"1",
|
|
25
|
+
2,
|
|
26
|
+
"2",
|
|
27
|
+
true,
|
|
28
|
+
false,
|
|
29
|
+
new Date("2020-01-01"),
|
|
30
|
+
new Date("2020-01-02"),
|
|
31
|
+
PRN.fromString("prn:plate:-1:cxc:content-item:1"),
|
|
32
|
+
PRN.fromString("prn:plate:-1:cxc:content-item:2"),
|
|
33
|
+
]);
|
|
34
|
+
expect(result).toBeContentValidationResult([true, false, true, false, true, false, true, false, true, false]);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("with no allowed values", () => {
|
|
39
|
+
let rule: AllowedValuesValidationRule;
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
const allowedValues: ContentValueType[] = [];
|
|
43
|
+
rule = new AllowedValuesValidationRule(rulePrn, { allowedValues });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it.each([
|
|
47
|
+
["1", false],
|
|
48
|
+
[1, false],
|
|
49
|
+
[true, false],
|
|
50
|
+
[new Date("2020-01-01"), false],
|
|
51
|
+
[PRN.fromString("prn:plate:-1:cxc:content-item:1"), false],
|
|
52
|
+
])("returns false if the value is %s", (value, expected) => {
|
|
53
|
+
expect(rule.validate([value])).toBeContentValidationResult([expected]);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { PRN } from "@platecms/delta-plate-resource-notation";
|
|
2
|
+
import { ContentValueType } from "@platecms/delta-types";
|
|
3
|
+
import { ValidationErrorDetails } from "../errors/validation-error-details";
|
|
4
|
+
import { RuleInstance } from "../types/rule-instance.interface";
|
|
5
|
+
import { RuleType } from "../enums/rule-types.enum";
|
|
6
|
+
import { BaseValidationRule, ContentValidationResultType } from "./base.validation-rule";
|
|
7
|
+
import { validationFunctionFromValidateValue } from "./validation-rule-function.factory";
|
|
8
|
+
|
|
9
|
+
export function validateValueAllowedValues(
|
|
10
|
+
value: ContentValueType,
|
|
11
|
+
ruleInstance: RuleInstance<AllowedValuesValidationRuleSettings>,
|
|
12
|
+
): ContentValidationResultType {
|
|
13
|
+
const valid = ruleInstance.settings.allowedValues.some((allowedValue) => {
|
|
14
|
+
if (value instanceof PRN) {
|
|
15
|
+
return allowedValue instanceof PRN && value.equals(allowedValue);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (value instanceof Date) {
|
|
19
|
+
return allowedValue instanceof Date && value.getTime() === allowedValue.getTime();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return value === allowedValue;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (!valid) {
|
|
26
|
+
return new ValidationErrorDetails(
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
28
|
+
`The provided value must be one of ${ruleInstance.settings.allowedValues.join(", ")}`,
|
|
29
|
+
ruleInstance,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
37
|
+
export const validateAllowedValues = validationFunctionFromValidateValue(validateValueAllowedValues);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A validation rule that checks whether a value is one of the allowed values.
|
|
41
|
+
* Explicitely checks for complex values such as Dates and ContentItems
|
|
42
|
+
*/
|
|
43
|
+
export class AllowedValuesValidationRule extends BaseValidationRule<AllowedValuesValidationRuleSettings> {
|
|
44
|
+
public readonly ruleType: RuleType = RuleType.ALLOWED_VALUES;
|
|
45
|
+
|
|
46
|
+
public override validate(values: ContentValueType[]): ContentValidationResultType[] {
|
|
47
|
+
return validateAllowedValues(values, this);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface AllowedValuesValidationRuleSettings {
|
|
52
|
+
allowedValues: ContentValueType[];
|
|
53
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { PRN } from "@platecms/delta-plate-resource-notation";
|
|
2
|
+
import { ContentValueType } from "@platecms/delta-types";
|
|
3
|
+
import { ValidationErrorDetails } from "../errors/validation-error-details";
|
|
4
|
+
import { RuleInstance } from "../types/rule-instance.interface";
|
|
5
|
+
import { RuleType } from "../enums/rule-types.enum";
|
|
6
|
+
|
|
7
|
+
export type ContentValidationResultType<TRuleInstance extends RuleInstance<unknown> = RuleInstance<unknown>> =
|
|
8
|
+
| ValidationErrorDetails<TRuleInstance>
|
|
9
|
+
| true
|
|
10
|
+
| null;
|
|
11
|
+
|
|
12
|
+
export abstract class BaseValidationRule<TSettings> implements RuleInstance<TSettings> {
|
|
13
|
+
public readonly prn: PRN;
|
|
14
|
+
|
|
15
|
+
public readonly settings: TSettings;
|
|
16
|
+
|
|
17
|
+
public abstract readonly ruleType: RuleType;
|
|
18
|
+
|
|
19
|
+
public constructor(prn: PRN, settings: TSettings) {
|
|
20
|
+
this.prn = prn;
|
|
21
|
+
this.settings = settings;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validates ContentValue[] against the rule.
|
|
26
|
+
* Returns array with for each value: true if the value is valid, ContentValidationErrorDetail if it is invalid,
|
|
27
|
+
* and null if the rule does not apply to the value.
|
|
28
|
+
* Some validation rules perform validation on the array as a whole, in which case the array will be validated as a whole,
|
|
29
|
+
* and the result will be returned in the first element of the array. E.g. the CountValidationRule.
|
|
30
|
+
* @param values ContentValueType[] to validate
|
|
31
|
+
*/
|
|
32
|
+
public validate(_: ContentValueType[]): ContentValidationResultType[] {
|
|
33
|
+
throw new Error("validate method must be overridden.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { PRN } from "@platecms/delta-plate-resource-notation";
|
|
2
|
+
import { InvalidValidationRuleSettingsError } from "../errors/invalid-validation-rule-settings.error";
|
|
3
|
+
import { CountValidationRule } from "./count.validation-rule";
|
|
4
|
+
|
|
5
|
+
describe("CountValidationRule", () => {
|
|
6
|
+
const rulePrn = PRN.fromString("prn:plate:-1:cxc:validation-rule:1");
|
|
7
|
+
|
|
8
|
+
describe("constructor", () => {
|
|
9
|
+
it("throws an error if neither min nor max are provided", () => {
|
|
10
|
+
expect(() => {
|
|
11
|
+
new CountValidationRule(rulePrn, {});
|
|
12
|
+
}).toThrowError(InvalidValidationRuleSettingsError);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("validate", () => {
|
|
17
|
+
describe("with min and max", () => {
|
|
18
|
+
let rule: CountValidationRule;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
rule = new CountValidationRule(rulePrn, { min: 1, max: 3 });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it.each([
|
|
25
|
+
[[1], true],
|
|
26
|
+
[[1, 2, 3], true],
|
|
27
|
+
[[1, 2], true],
|
|
28
|
+
[[], false],
|
|
29
|
+
[[1, 2, 3, 4], false],
|
|
30
|
+
])("returns %s if the value count is within the bounds", (value, expected) => {
|
|
31
|
+
expect(rule.validate(value)).toBeContentValidationResult([expected]);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("with only min", () => {
|
|
36
|
+
let rule: CountValidationRule;
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
rule = new CountValidationRule(rulePrn, { min: 1 });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it.each([
|
|
43
|
+
[[1], true],
|
|
44
|
+
[[1, 2], true],
|
|
45
|
+
[[], false],
|
|
46
|
+
])("returns %s if the value count is within the bounds", (value, expected) => {
|
|
47
|
+
expect(rule.validate(value)).toBeContentValidationResult([expected]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("with only max", () => {
|
|
52
|
+
let rule: CountValidationRule;
|
|
53
|
+
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
rule = new CountValidationRule(rulePrn, { max: 3 });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it.each([
|
|
59
|
+
[[1, 2, 3], true],
|
|
60
|
+
[[1, 2], true],
|
|
61
|
+
[[1, 2, 3, 4], false],
|
|
62
|
+
])("returns %s if the value count is within the bounds", (value, expected) => {
|
|
63
|
+
expect(rule.validate(value)).toBeContentValidationResult([expected]);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { PRN } from "@platecms/delta-plate-resource-notation";
|
|
2
|
+
import { ContentValueType } from "@platecms/delta-types";
|
|
3
|
+
import { InvalidValidationRuleSettingsError } from "../errors/invalid-validation-rule-settings.error";
|
|
4
|
+
import { ValidationErrorDetails } from "../errors/validation-error-details";
|
|
5
|
+
import { RuleInstance } from "../types/rule-instance.interface";
|
|
6
|
+
import { RuleType } from "../enums/rule-types.enum";
|
|
7
|
+
import { BaseValidationRule, ContentValidationResultType } from "./base.validation-rule";
|
|
8
|
+
|
|
9
|
+
export function validateValuesCount<T>(
|
|
10
|
+
values: T[],
|
|
11
|
+
ruleInstance: RuleInstance<CountValidationRuleSettings>,
|
|
12
|
+
): ContentValidationResultType {
|
|
13
|
+
if (ruleInstance.settings.min != null && values.length < ruleInstance.settings.min) {
|
|
14
|
+
return new ValidationErrorDetails(
|
|
15
|
+
`The amount of provided values must be greater than or equal ${ruleInstance.settings.min}.`,
|
|
16
|
+
ruleInstance,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
if (ruleInstance.settings.max != null && values.length > ruleInstance.settings.max) {
|
|
20
|
+
return new ValidationErrorDetails(
|
|
21
|
+
`The amount of provided values must be be less than or equal ${ruleInstance.settings.max}.`,
|
|
22
|
+
ruleInstance,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Wrapper for validateValuesCount that wraps the result in an array to keep the same interface as the other validation functions.
|
|
30
|
+
*/
|
|
31
|
+
export function validateCount(
|
|
32
|
+
value: ContentValueType[],
|
|
33
|
+
ruleInstance: RuleInstance<CountValidationRuleSettings>,
|
|
34
|
+
): ContentValidationResultType[] {
|
|
35
|
+
const result = validateValuesCount(value, ruleInstance);
|
|
36
|
+
return [result];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A validation rule that checks the number of values in a content field.
|
|
41
|
+
*/
|
|
42
|
+
export class CountValidationRule extends BaseValidationRule<CountValidationRuleSettings> {
|
|
43
|
+
public readonly ruleType: RuleType = RuleType.COUNT;
|
|
44
|
+
|
|
45
|
+
public constructor(prn: PRN, settings: CountValidationRuleSettings) {
|
|
46
|
+
if (settings.min == null && settings.max == null) {
|
|
47
|
+
throw new InvalidValidationRuleSettingsError("Either min or max must be defined.");
|
|
48
|
+
}
|
|
49
|
+
super(prn, settings);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public override validate(value: ContentValueType[]): ContentValidationResultType[] {
|
|
53
|
+
return validateCount(value, this);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface CountValidationRuleSettings {
|
|
58
|
+
min?: number;
|
|
59
|
+
max?: number;
|
|
60
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { PRN } from "@platecms/delta-plate-resource-notation";
|
|
2
|
+
import { InvalidValidationRuleSettingsError } from "../errors/invalid-validation-rule-settings.error";
|
|
3
|
+
import { DateBetweenValidationRule } from "./date-between.validation-rule";
|
|
4
|
+
|
|
5
|
+
describe("DateBetweenValidationRule", () => {
|
|
6
|
+
const rulePrn = PRN.fromString("prn:plate:-1:cxc:validation-rule:1");
|
|
7
|
+
|
|
8
|
+
describe("constructor", () => {
|
|
9
|
+
it("throws an error if min and max are not provided", () => {
|
|
10
|
+
expect(() => {
|
|
11
|
+
new DateBetweenValidationRule(rulePrn, {});
|
|
12
|
+
}).toThrowError(InvalidValidationRuleSettingsError);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("validate", () => {
|
|
17
|
+
describe("with min and max", () => {
|
|
18
|
+
let rule: DateBetweenValidationRule;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
rule = new DateBetweenValidationRule(rulePrn, {
|
|
22
|
+
start: new Date(2020, 1, 2, 3, 4, 5),
|
|
23
|
+
end: new Date(2020, 1, 3, 3, 4, 6),
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it.each([
|
|
28
|
+
[new Date(2020, 1, 2, 3, 4, 5), true],
|
|
29
|
+
[new Date(2020, 1, 3, 3, 4, 6), true],
|
|
30
|
+
[new Date(2020, 1, 2, 3, 4, 6), true],
|
|
31
|
+
[new Date(2020, 1, 2, 3, 4, 4), false],
|
|
32
|
+
[new Date(2020, 1, 3, 3, 4, 7), false],
|
|
33
|
+
["foo", null],
|
|
34
|
+
])("returns %s for the value within the bounds", (value, expected) => {
|
|
35
|
+
expect(rule.validate([value])).toBeContentValidationResult([expected]);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("with only min", () => {
|
|
40
|
+
let rule: DateBetweenValidationRule;
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
rule = new DateBetweenValidationRule(rulePrn, {
|
|
44
|
+
start: new Date(2020, 1, 2, 3, 4, 5),
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it.each([
|
|
49
|
+
[new Date(2020, 1, 2, 3, 4, 5), true],
|
|
50
|
+
[new Date(2020, 1, 2, 3, 4, 6), true],
|
|
51
|
+
[new Date(2020, 1, 2, 3, 4, 4), false],
|
|
52
|
+
["foo", null],
|
|
53
|
+
])("returns %s for the value within the bounds", (value, expected) => {
|
|
54
|
+
expect(rule.validate([value])).toBeContentValidationResult([expected]);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("with only max", () => {
|
|
59
|
+
let rule: DateBetweenValidationRule;
|
|
60
|
+
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
rule = new DateBetweenValidationRule(rulePrn, {
|
|
63
|
+
end: new Date(2020, 1, 3, 3, 4, 6),
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it.each([
|
|
68
|
+
[new Date(2020, 1, 3, 3, 4, 6), true],
|
|
69
|
+
[new Date(2020, 1, 3, 3, 4, 5), true],
|
|
70
|
+
[new Date(2020, 1, 3, 3, 4, 7), false],
|
|
71
|
+
["foo", null],
|
|
72
|
+
])("returns %s for the value within the bounds", (value, expected) => {
|
|
73
|
+
expect(rule.validate([value])).toBeContentValidationResult([expected]);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { PRN } from "@platecms/delta-plate-resource-notation";
|
|
2
|
+
import { ContentValueType } from "@platecms/delta-types";
|
|
3
|
+
import { InvalidValidationRuleSettingsError } from "../errors/invalid-validation-rule-settings.error";
|
|
4
|
+
import { ValidationErrorDetails } from "../errors/validation-error-details";
|
|
5
|
+
import { RuleInstance } from "../types/rule-instance.interface";
|
|
6
|
+
import { RuleType } from "../enums/rule-types.enum";
|
|
7
|
+
import { BaseValidationRule, ContentValidationResultType } from "./base.validation-rule";
|
|
8
|
+
import { validationFunctionFromValidateValue } from "./validation-rule-function.factory";
|
|
9
|
+
|
|
10
|
+
export function validateValueDateBetween(
|
|
11
|
+
value: ContentValueType,
|
|
12
|
+
ruleInstance: RuleInstance<DateBetweenValidationRuleSettings>,
|
|
13
|
+
): ContentValidationResultType {
|
|
14
|
+
if (!(value instanceof Date)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (ruleInstance.settings.start != null && value < ruleInstance.settings.start) {
|
|
19
|
+
return new ValidationErrorDetails(
|
|
20
|
+
`The date must be greater than or equal ${ruleInstance.settings.start.toDateString()}`,
|
|
21
|
+
ruleInstance,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
if (ruleInstance.settings.end != null && value > ruleInstance.settings.end) {
|
|
25
|
+
return new ValidationErrorDetails(
|
|
26
|
+
`The date must be lower than or equal ${ruleInstance.settings.end.toDateString()}`,
|
|
27
|
+
ruleInstance,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
34
|
+
export const validateDateBetween = validationFunctionFromValidateValue(validateValueDateBetween);
|
|
35
|
+
|
|
36
|
+
export class DateBetweenValidationRule extends BaseValidationRule<DateBetweenValidationRuleSettings> {
|
|
37
|
+
public readonly ruleType: RuleType = RuleType.DATE_BETWEEN;
|
|
38
|
+
|
|
39
|
+
public constructor(prn: PRN, settings: DateBetweenValidationRuleSettings) {
|
|
40
|
+
if (settings.start == null && settings.end == null) {
|
|
41
|
+
throw new InvalidValidationRuleSettingsError("Either start or end must be defined.");
|
|
42
|
+
}
|
|
43
|
+
super(prn, settings);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public override validate(values: ContentValueType[]): ContentValidationResultType[] {
|
|
47
|
+
return validateDateBetween(values, this);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface DateBetweenValidationRuleSettings {
|
|
52
|
+
start?: Date;
|
|
53
|
+
end?: Date;
|
|
54
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { PRN } from "@platecms/delta-plate-resource-notation";
|
|
2
|
+
import { DecimalCountValidationRule } from "./decimal-count.validation-rule";
|
|
3
|
+
|
|
4
|
+
describe("DecimalCountValidationRule", () => {
|
|
5
|
+
const rulePrn = PRN.fromString("prn:plate:-1:cxc:validation-rule:decimal-count");
|
|
6
|
+
|
|
7
|
+
describe("validate", () => {
|
|
8
|
+
describe("with max of 0", () => {
|
|
9
|
+
let rule: DecimalCountValidationRule;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
rule = new DecimalCountValidationRule(rulePrn, {
|
|
13
|
+
max: 0,
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it.each([
|
|
18
|
+
[1.0, true],
|
|
19
|
+
[1.1, false],
|
|
20
|
+
["foo", null],
|
|
21
|
+
])("returns %s for value with max 0", (value, expected) => {
|
|
22
|
+
expect(rule.validate([value])).toBeContentValidationResult([expected]);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("with max of 3", () => {
|
|
27
|
+
let rule: DecimalCountValidationRule;
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
rule = new DecimalCountValidationRule(rulePrn, {
|
|
31
|
+
max: 3,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it.each([
|
|
36
|
+
[1.0, true],
|
|
37
|
+
[1.23, true],
|
|
38
|
+
[1.034, true],
|
|
39
|
+
[1.0345, false],
|
|
40
|
+
["foo", null],
|
|
41
|
+
])("returns %s for value with max 3", (value, expected) => {
|
|
42
|
+
expect(rule.validate([value])).toBeContentValidationResult([expected]);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|