@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,168 @@
|
|
|
1
|
+
import { RuleType } from "../enums/rule-types.enum";
|
|
2
|
+
import { ValidationContext } from "../types/validation-context.interface";
|
|
3
|
+
import { ValidationErrorData } from "../types/validation-error-data.interface";
|
|
4
|
+
import { SchemaType } from "../types/validation-schema.interface";
|
|
5
|
+
import { BaseValidationSchema } from "./base.schema";
|
|
6
|
+
|
|
7
|
+
// Concrete implementation for testing
|
|
8
|
+
class TestValidationSchema extends BaseValidationSchema<string, TestValidationSchema> {
|
|
9
|
+
public readonly type: SchemaType = "string";
|
|
10
|
+
|
|
11
|
+
protected _typeCheck(value: unknown, path: string[]): ValidationErrorData[] {
|
|
12
|
+
if (typeof value !== "string") {
|
|
13
|
+
return [
|
|
14
|
+
{
|
|
15
|
+
path,
|
|
16
|
+
message: this._message ?? "Value must be a string",
|
|
17
|
+
provided: value,
|
|
18
|
+
validationRule: {
|
|
19
|
+
ruleType: RuleType.VALUE_TYPE,
|
|
20
|
+
settings: { allowedTypes: ["string"] },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected _validateAgainstSchema(_value: string, _context: ValidationContext = {}): ValidationErrorData[] {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("BaseValidationSchema", () => {
|
|
34
|
+
let schema: TestValidationSchema;
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
schema = new TestValidationSchema();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("required validation", () => {
|
|
41
|
+
it("passes validation when value is provided and required is false", () => {
|
|
42
|
+
const result = schema.validate("test", { path: ["test"] });
|
|
43
|
+
expect(result.isValid).toBe(true);
|
|
44
|
+
expect(result.errors).toHaveLength(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("passes validation when value is provided and required is true", () => {
|
|
48
|
+
schema.required(true);
|
|
49
|
+
const result = schema.validate("test", { path: ["test"] });
|
|
50
|
+
expect(result.isValid).toBe(true);
|
|
51
|
+
expect(result.errors).toHaveLength(0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("fails validation when value is null and required is true", () => {
|
|
55
|
+
schema.required(true);
|
|
56
|
+
const result = schema.validate(null as unknown as string, { path: ["test"] });
|
|
57
|
+
expect(result.isValid).toBe(false);
|
|
58
|
+
expect(result.errors).toHaveLength(1);
|
|
59
|
+
expect(result.errors[0].message).toBe("Value is required");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("fails validation when value is undefined and required is true", () => {
|
|
63
|
+
schema.required(true);
|
|
64
|
+
const result = schema.validate(undefined as unknown as string, { path: ["test"] });
|
|
65
|
+
expect(result.isValid).toBe(false);
|
|
66
|
+
expect(result.errors).toHaveLength(1);
|
|
67
|
+
expect(result.errors[0].message).toBe("Value is required");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("passes validation when value is null and required is false", () => {
|
|
71
|
+
schema.required(false);
|
|
72
|
+
const result = schema.validate(null as unknown as string, { path: ["test"] });
|
|
73
|
+
expect(result.isValid).toBe(true);
|
|
74
|
+
expect(result.errors).toHaveLength(0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("passes validation when value is undefined and required is false", () => {
|
|
78
|
+
schema.required(false);
|
|
79
|
+
const result = schema.validate(undefined as unknown as string, { path: ["test"] });
|
|
80
|
+
expect(result.isValid).toBe(true);
|
|
81
|
+
expect(result.errors).toHaveLength(0);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("message handling", () => {
|
|
86
|
+
it("uses custom message when provided", () => {
|
|
87
|
+
schema.message("Custom error message");
|
|
88
|
+
const result = schema.validate(123 as unknown as string, { path: ["test"] });
|
|
89
|
+
expect(result.isValid).toBe(false);
|
|
90
|
+
expect(result.errors).toHaveLength(1);
|
|
91
|
+
expect(result.errors[0].message).toBe("Custom error message");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("uses default message when no custom message is provided", () => {
|
|
95
|
+
const result = schema.validate(123 as unknown as string, { path: ["test"] });
|
|
96
|
+
expect(result.isValid).toBe(false);
|
|
97
|
+
expect(result.errors).toHaveLength(1);
|
|
98
|
+
expect(result.errors[0].message).toBe("Value must be a string");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("allowed values validation", () => {
|
|
103
|
+
it("passes validation when value is in allowed values", () => {
|
|
104
|
+
schema.allowedValues(["test", "other"]);
|
|
105
|
+
const result = schema.validate("test", { path: ["test"] });
|
|
106
|
+
expect(result.isValid).toBe(true);
|
|
107
|
+
expect(result.errors).toHaveLength(0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("fails validation when value is not in allowed values", () => {
|
|
111
|
+
schema.allowedValues(["test", "other"]);
|
|
112
|
+
const result = schema.validate("invalid", { path: ["test"] });
|
|
113
|
+
expect(result.isValid).toBe(false);
|
|
114
|
+
expect(result.errors).toHaveLength(1);
|
|
115
|
+
expect(result.errors[0].message).toBe("The provided value must be one of test, other");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("passes validation when no allowed values are set", () => {
|
|
119
|
+
const result = schema.validate("any string", { path: ["test"] });
|
|
120
|
+
expect(result.isValid).toBe(true);
|
|
121
|
+
expect(result.errors).toHaveLength(0);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("type checking", () => {
|
|
126
|
+
it("fails validation when value is not a string", () => {
|
|
127
|
+
const result = schema.validate(123 as unknown as string, { path: ["test"] });
|
|
128
|
+
expect(result.isValid).toBe(false);
|
|
129
|
+
expect(result.errors).toHaveLength(1);
|
|
130
|
+
expect(result.errors[0].message).toBe("Value must be a string");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("passes validation when value is a string", () => {
|
|
134
|
+
const result = schema.validate("test", { path: ["test"] });
|
|
135
|
+
expect(result.isValid).toBe(true);
|
|
136
|
+
expect(result.errors).toHaveLength(0);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe("context handling", () => {
|
|
141
|
+
it("uses provided path from context", () => {
|
|
142
|
+
const result = schema.validate(123 as unknown as string, { path: ["user", "name"] });
|
|
143
|
+
expect(result.errors[0].path).toEqual(["user", "name"]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("uses empty array when no path is provided", () => {
|
|
147
|
+
const result = schema.validate(123 as unknown as string);
|
|
148
|
+
expect(result.errors[0].path).toEqual([]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("handles undefined context", () => {
|
|
152
|
+
const result = schema.validate(123 as unknown as string, undefined);
|
|
153
|
+
expect(result.errors[0].path).toEqual([]);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("chaining", () => {
|
|
158
|
+
it("supports method chaining", () => {
|
|
159
|
+
const result = schema
|
|
160
|
+
.required(true)
|
|
161
|
+
.message("Custom message")
|
|
162
|
+
.allowedValues(["test"])
|
|
163
|
+
.validate("test", { path: ["test"] });
|
|
164
|
+
|
|
165
|
+
expect(result.isValid).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { ContentValueType } from "@platecms/delta-types";
|
|
2
|
+
import { RuleType } from "../enums/rule-types.enum";
|
|
3
|
+
import { validateWithFunction } from "../helpers/validate-with-function";
|
|
4
|
+
import { RuleInstance } from "../types/rule-instance.interface";
|
|
5
|
+
import { ValidationContext } from "../types/validation-context.interface";
|
|
6
|
+
import { ValidationErrorData } from "../types/validation-error-data.interface";
|
|
7
|
+
import { ValidationResult } from "../types/validation-result.interface";
|
|
8
|
+
import { SchemaType, ValidationSchema } from "../types/validation-schema.interface";
|
|
9
|
+
import {
|
|
10
|
+
AllowedValuesValidationRuleSettings,
|
|
11
|
+
validateValueAllowedValues,
|
|
12
|
+
} from "../validation-rules/allowed-values.validation-rule";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Base abstract class for all validation schemas.
|
|
16
|
+
* @template TType - The type of the value to validate.
|
|
17
|
+
* @template TSelf - The type of the schema itself.
|
|
18
|
+
*/
|
|
19
|
+
export abstract class BaseValidationSchema<TType, TSelf extends BaseValidationSchema<TType, TSelf>>
|
|
20
|
+
implements ValidationSchema<TType>
|
|
21
|
+
{
|
|
22
|
+
protected _required?: boolean;
|
|
23
|
+
protected _message?: string;
|
|
24
|
+
protected _validationRule?: RuleInstance<unknown>;
|
|
25
|
+
protected _allowedValues?: unknown[];
|
|
26
|
+
public abstract readonly type: SchemaType;
|
|
27
|
+
|
|
28
|
+
public constructor(validationRule?: RuleInstance<unknown>) {
|
|
29
|
+
if (validationRule) {
|
|
30
|
+
this._validationRule = {
|
|
31
|
+
id: validationRule.id,
|
|
32
|
+
prn: validationRule.prn,
|
|
33
|
+
ruleType: validationRule.ruleType,
|
|
34
|
+
settings: validationRule.settings,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public required(required: boolean): TSelf {
|
|
40
|
+
this._required = required;
|
|
41
|
+
return this as unknown as TSelf;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public message(msg: string): TSelf {
|
|
45
|
+
this._message = msg;
|
|
46
|
+
return this as unknown as TSelf;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public allowedValues(values: unknown[]): TSelf {
|
|
50
|
+
this._allowedValues = values;
|
|
51
|
+
return this as unknown as TSelf;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validates a value against the schema.
|
|
56
|
+
* Calls the _typeCheck and _validateAgainstSchema methods, which are implemented by the concrete schemas.
|
|
57
|
+
* @param value - The value to validate.
|
|
58
|
+
* @param context - The context of the validation, including the path of the value.
|
|
59
|
+
* @returns The validation result.
|
|
60
|
+
*/
|
|
61
|
+
public validate(value: TType, context: ValidationContext = {}): ValidationResult {
|
|
62
|
+
const errors: ValidationErrorData[] = [];
|
|
63
|
+
const path = context?.path ?? [];
|
|
64
|
+
|
|
65
|
+
// 1) required
|
|
66
|
+
const isRequired = this._required ?? false;
|
|
67
|
+
if (isRequired && (value === undefined || value === null)) {
|
|
68
|
+
return {
|
|
69
|
+
isValid: false,
|
|
70
|
+
errors: [
|
|
71
|
+
{
|
|
72
|
+
path,
|
|
73
|
+
message: this._message ?? "Value is required",
|
|
74
|
+
provided: value,
|
|
75
|
+
validationRule: {
|
|
76
|
+
ruleType: RuleType.COUNT,
|
|
77
|
+
settings: {
|
|
78
|
+
min: 1,
|
|
79
|
+
max: 1,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (!isRequired && (value === undefined || value === null)) {
|
|
87
|
+
return {
|
|
88
|
+
isValid: true,
|
|
89
|
+
errors: [],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 2) type/shape
|
|
94
|
+
errors.push(...this._typeCheck(value, path));
|
|
95
|
+
if (errors.length > 0) {
|
|
96
|
+
return {
|
|
97
|
+
isValid: false,
|
|
98
|
+
errors,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 3) validate against schema
|
|
103
|
+
errors.push(...this._validateAgainstSchema(value, context));
|
|
104
|
+
if (errors.length > 0) {
|
|
105
|
+
return {
|
|
106
|
+
isValid: false,
|
|
107
|
+
errors,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 4) validate against allowed values
|
|
112
|
+
const allowedValuesError = this._validateAllowedValues(value, path);
|
|
113
|
+
if (allowedValuesError) {
|
|
114
|
+
errors.push(allowedValuesError);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
isValid: errors.length === 0,
|
|
119
|
+
errors,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private _validateAllowedValues(
|
|
124
|
+
value: unknown,
|
|
125
|
+
path: string[],
|
|
126
|
+
): ValidationErrorData<AllowedValuesValidationRuleSettings> | null {
|
|
127
|
+
if (this._allowedValues === undefined) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const validationRule = this._validationRule ?? {
|
|
132
|
+
ruleType: RuleType.ALLOWED_VALUES,
|
|
133
|
+
settings: {
|
|
134
|
+
allowedValues: this._allowedValues,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return validateWithFunction<AllowedValuesValidationRuleSettings, ContentValueType>(
|
|
139
|
+
value as ContentValueType,
|
|
140
|
+
validationRule as RuleInstance<AllowedValuesValidationRuleSettings>,
|
|
141
|
+
path,
|
|
142
|
+
this._message ?? "Value is not in allowed values list",
|
|
143
|
+
validateValueAllowedValues,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
protected abstract _typeCheck(value: TType, path: string[]): ValidationErrorData[];
|
|
148
|
+
|
|
149
|
+
protected abstract _validateAgainstSchema(value: TType, context: ValidationContext): ValidationErrorData[];
|
|
150
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { PRN } from "@platecms/delta-plate-resource-notation";
|
|
2
|
+
import { ContentValueTypeNames } from "@platecms/delta-types";
|
|
3
|
+
import { RuleType } from "../enums/rule-types.enum";
|
|
4
|
+
import { ContentValueValidationSchema } from "./content-value.schema";
|
|
5
|
+
import { Root } from "@platecms/delta-cast";
|
|
6
|
+
|
|
7
|
+
describe("ContentValueValidationSchema", () => {
|
|
8
|
+
let schema: ContentValueValidationSchema;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
schema = new ContentValueValidationSchema();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe("type checking", () => {
|
|
15
|
+
it("passes validation when value is a string", () => {
|
|
16
|
+
const result = schema.validate("test string", { path: ["test"] });
|
|
17
|
+
expect(result.isValid).toBe(true);
|
|
18
|
+
expect(result.errors).toHaveLength(0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("passes validation when value is a number", () => {
|
|
22
|
+
const result = schema.validate(42, { path: ["test"] });
|
|
23
|
+
expect(result.isValid).toBe(true);
|
|
24
|
+
expect(result.errors).toHaveLength(0);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("passes validation when value is a boolean", () => {
|
|
28
|
+
const result = schema.validate(true, { path: ["test"] });
|
|
29
|
+
expect(result.isValid).toBe(true);
|
|
30
|
+
expect(result.errors).toHaveLength(0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("passes validation when value is a Date", () => {
|
|
34
|
+
const result = schema.validate(new Date("2023-01-01"), { path: ["test"] });
|
|
35
|
+
expect(result.isValid).toBe(true);
|
|
36
|
+
expect(result.errors).toHaveLength(0);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("passes validation when value is a PRN", () => {
|
|
40
|
+
const prn = PRN.fromString("prn:plate:-1:cxc:content-item:1");
|
|
41
|
+
const result = schema.validate(prn, { path: ["test"] });
|
|
42
|
+
expect(result.isValid).toBe(true);
|
|
43
|
+
expect(result.errors).toHaveLength(0);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("passes validation when value is a Root (smart text)", () => {
|
|
47
|
+
const root: Root = { type: "root", children: [] };
|
|
48
|
+
const result = schema.validate(root, { path: ["test"] });
|
|
49
|
+
expect(result.isValid).toBe(true);
|
|
50
|
+
expect(result.errors).toHaveLength(0);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("allowed value types validation", () => {
|
|
55
|
+
it("passes validation when value type is in allowed types (number)", () => {
|
|
56
|
+
schema.allowedValueTypes([ContentValueTypeNames.STRING, ContentValueTypeNames.NUMBER]);
|
|
57
|
+
const result = schema.validate(42, { path: ["test"] });
|
|
58
|
+
expect(result.isValid).toBe(true);
|
|
59
|
+
expect(result.errors).toHaveLength(0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("fails validation when value type is not in allowed types", () => {
|
|
63
|
+
schema.allowedValueTypes([ContentValueTypeNames.STRING, ContentValueTypeNames.NUMBER]);
|
|
64
|
+
const result = schema.validate(true, { path: ["test"] });
|
|
65
|
+
expect(result.isValid).toBe(false);
|
|
66
|
+
expect(result.errors).toHaveLength(1);
|
|
67
|
+
expect(result.errors[0].message).toContain("The value must be of the correct type");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("passes validation when no allowed value types are set", () => {
|
|
71
|
+
const result = schema.validate("any content value", { path: ["test"] });
|
|
72
|
+
expect(result.isValid).toBe(true);
|
|
73
|
+
expect(result.errors).toHaveLength(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("handles multiple allowed types", () => {
|
|
77
|
+
schema.allowedValueTypes([
|
|
78
|
+
ContentValueTypeNames.STRING,
|
|
79
|
+
ContentValueTypeNames.NUMBER,
|
|
80
|
+
ContentValueTypeNames.BOOLEAN,
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
const stringResult = schema.validate("test", { path: ["test"] });
|
|
84
|
+
const numberResult = schema.validate(42, { path: ["test"] });
|
|
85
|
+
const booleanResult = schema.validate(true, { path: ["test"] });
|
|
86
|
+
|
|
87
|
+
expect(stringResult.isValid).toBe(true);
|
|
88
|
+
expect(numberResult.isValid).toBe(true);
|
|
89
|
+
expect(booleanResult.isValid).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("allowed content types validation", () => {
|
|
94
|
+
it("passes validation when PRN content type is in allowed types", () => {
|
|
95
|
+
const allowedTypes = [PRN.fromString("prn:plate:-1:cxc:content-item:1")];
|
|
96
|
+
schema.allowedContentTypes(allowedTypes);
|
|
97
|
+
|
|
98
|
+
const prn = PRN.fromString("prn:plate:-1:cxc:content-item:1");
|
|
99
|
+
const result = schema.validate(prn, { path: ["test"] });
|
|
100
|
+
expect(result.isValid).toBe(true);
|
|
101
|
+
expect(result.errors).toHaveLength(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("uses validation rule for value type validation", () => {
|
|
105
|
+
const allowedValueTypes = [ContentValueTypeNames.STRING, ContentValueTypeNames.NUMBER];
|
|
106
|
+
const mockRule = {
|
|
107
|
+
ruleType: RuleType.VALUE_TYPE,
|
|
108
|
+
settings: { allowedTypes: allowedValueTypes },
|
|
109
|
+
prn: PRN.fromString("prn:plate:-1:cxc:validation-rule:1"),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const schemaWithRule = new ContentValueValidationSchema(mockRule).allowedValueTypes(allowedValueTypes);
|
|
113
|
+
const result = schemaWithRule.validate(true, { path: ["test"] });
|
|
114
|
+
expect(result.isValid).toBe(false);
|
|
115
|
+
expect(result.errors).toHaveLength(1);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("method chaining", () => {
|
|
120
|
+
it("supports method chaining", () => {
|
|
121
|
+
const allowedValueTypes = [ContentValueTypeNames.STRING, ContentValueTypeNames.NUMBER];
|
|
122
|
+
const allowedContentTypes = [PRN.fromString("prn:plate:-1:cxc:content-item:1")];
|
|
123
|
+
|
|
124
|
+
const result = schema
|
|
125
|
+
.allowedValueTypes(allowedValueTypes)
|
|
126
|
+
.allowedContentTypes(allowedContentTypes)
|
|
127
|
+
.message("Custom content value message")
|
|
128
|
+
.validate("test string", { path: ["test"] });
|
|
129
|
+
|
|
130
|
+
expect(result.isValid).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { PRN } from "@platecms/delta-plate-resource-notation";
|
|
2
|
+
import { ContentValueType, ContentValueTypeName } from "@platecms/delta-types";
|
|
3
|
+
import { RuleType } from "../enums/rule-types.enum";
|
|
4
|
+
import { validateWithFunction } from "../helpers/validate-with-function";
|
|
5
|
+
import { RuleInstance } from "../types/rule-instance.interface";
|
|
6
|
+
import { ValidationContext } from "../types/validation-context.interface";
|
|
7
|
+
import { ValidationErrorData } from "../types/validation-error-data.interface";
|
|
8
|
+
import {
|
|
9
|
+
ValueTypeValidationRuleSettings,
|
|
10
|
+
validateValueValueType,
|
|
11
|
+
} from "../validation-rules/value-type.validation-rule";
|
|
12
|
+
import { BaseValidationSchema } from "./base.schema";
|
|
13
|
+
|
|
14
|
+
export class ContentValueValidationSchema extends BaseValidationSchema<ContentValueType, ContentValueValidationSchema> {
|
|
15
|
+
public readonly type = "content-value";
|
|
16
|
+
private _allowedValueTypes?: ContentValueTypeName<ContentValueType>[];
|
|
17
|
+
private _allowedContentTypes?: PRN[];
|
|
18
|
+
|
|
19
|
+
public allowedContentTypes(types: PRN[]): this {
|
|
20
|
+
this._allowedContentTypes = types;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public allowedValueTypes(types: ContentValueTypeName<ContentValueType>[]): this {
|
|
25
|
+
this._allowedValueTypes = types;
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected _typeCheck(value: unknown, path: string[]): ValidationErrorData[] {
|
|
30
|
+
const allowedValueTypesError = this._validateAllowedValueTypes(value as ContentValueType, path);
|
|
31
|
+
return allowedValueTypesError ? [allowedValueTypesError] : [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
protected _validateAgainstSchema(_value: ContentValueType, _context: ValidationContext = {}): ValidationErrorData[] {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private _validateAllowedValueTypes(
|
|
39
|
+
value: ContentValueType,
|
|
40
|
+
path: string[],
|
|
41
|
+
): ValidationErrorData<ValueTypeValidationRuleSettings> | null {
|
|
42
|
+
if (this._allowedValueTypes === undefined) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const validationRule = this._validationRule ?? {
|
|
47
|
+
ruleType: RuleType.VALUE_TYPE,
|
|
48
|
+
settings: {
|
|
49
|
+
allowedTypes: this._allowedValueTypes,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return validateWithFunction<ValueTypeValidationRuleSettings, ContentValueType>(
|
|
54
|
+
value,
|
|
55
|
+
validationRule as RuleInstance<ValueTypeValidationRuleSettings>,
|
|
56
|
+
path,
|
|
57
|
+
this._message ?? "Value type validation failed",
|
|
58
|
+
validateValueValueType,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { PRN } from "@platecms/delta-plate-resource-notation";
|
|
2
|
+
import { RuleType } from "../enums/rule-types.enum";
|
|
3
|
+
import { DateValidationSchema } from "./date.schema";
|
|
4
|
+
|
|
5
|
+
describe("DateValidationSchema", () => {
|
|
6
|
+
let schema: DateValidationSchema;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
schema = new DateValidationSchema();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("type checking", () => {
|
|
13
|
+
it("passes validation when value is a Date object", () => {
|
|
14
|
+
const result = schema.validate(new Date("2023-01-01"), { path: ["test"] });
|
|
15
|
+
expect(result.isValid).toBe(true);
|
|
16
|
+
expect(result.errors).toHaveLength(0);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("fails validation when value is a date string", () => {
|
|
20
|
+
const result = schema.validate("2023-01-01" as unknown as Date, { path: ["test"] });
|
|
21
|
+
expect(result.isValid).toBe(false);
|
|
22
|
+
expect(result.errors).toHaveLength(1);
|
|
23
|
+
expect(result.errors[0].message).toBe("Value must be a valid date");
|
|
24
|
+
expect(result.errors[0].path).toEqual(["test"]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("fails validation when value is not a valid date", () => {
|
|
28
|
+
const result = schema.validate("invalid-date" as unknown as Date, { path: ["test"] });
|
|
29
|
+
expect(result.isValid).toBe(false);
|
|
30
|
+
expect(result.errors).toHaveLength(1);
|
|
31
|
+
expect(result.errors[0].message).toBe("Value must be a valid date");
|
|
32
|
+
expect(result.errors[0].path).toEqual(["test"]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("passes validation when value is null (allowed by default)", () => {
|
|
36
|
+
const result = schema.validate(null as unknown as Date, { path: ["test"] });
|
|
37
|
+
expect(result.isValid).toBe(true);
|
|
38
|
+
expect(result.errors).toHaveLength(0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("passes validation when value is undefined (allowed by default)", () => {
|
|
42
|
+
const result = schema.validate(undefined as unknown as Date, { path: ["test"] });
|
|
43
|
+
expect(result.isValid).toBe(true);
|
|
44
|
+
expect(result.errors).toHaveLength(0);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("range validation", () => {
|
|
49
|
+
it("passes validation when date is within min and max bounds", () => {
|
|
50
|
+
const startDate = new Date("2023-01-01");
|
|
51
|
+
const endDate = new Date("2023-12-31");
|
|
52
|
+
schema.start(startDate).end(endDate);
|
|
53
|
+
|
|
54
|
+
const result = schema.validate(new Date("2023-06-15"), { path: ["test"] });
|
|
55
|
+
expect(result.isValid).toBe(true);
|
|
56
|
+
expect(result.errors).toHaveLength(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("fails validation when date is before minimum", () => {
|
|
60
|
+
const startDate = new Date("2023-01-01");
|
|
61
|
+
schema.start(startDate);
|
|
62
|
+
|
|
63
|
+
const result = schema.validate(new Date("2022-12-31"), { path: ["test"] });
|
|
64
|
+
expect(result.isValid).toBe(false);
|
|
65
|
+
expect(result.errors).toHaveLength(1);
|
|
66
|
+
expect(result.errors[0].message).toContain("The date must be greater than or equal");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("fails validation when date is after maximum", () => {
|
|
70
|
+
const endDate = new Date("2023-12-31");
|
|
71
|
+
schema.end(endDate);
|
|
72
|
+
|
|
73
|
+
const result = schema.validate(new Date("2024-01-01"), { path: ["test"] });
|
|
74
|
+
expect(result.isValid).toBe(false);
|
|
75
|
+
expect(result.errors).toHaveLength(1);
|
|
76
|
+
expect(result.errors[0].message).toContain("The date must be lower than or equal");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("passes validation when no min/max constraints are set", () => {
|
|
80
|
+
const result = schema.validate(new Date("2030-01-01"), { path: ["test"] });
|
|
81
|
+
expect(result.isValid).toBe(true);
|
|
82
|
+
expect(result.errors).toHaveLength(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("uses validation rule for range validation", () => {
|
|
86
|
+
const startDate = new Date("2023-01-01");
|
|
87
|
+
const endDate = new Date("2023-12-31");
|
|
88
|
+
const mockRule = {
|
|
89
|
+
ruleType: RuleType.DATE_BETWEEN,
|
|
90
|
+
settings: { start: startDate, end: endDate },
|
|
91
|
+
prn: PRN.fromString("prn:plate:-1:cxc:validation-rule:1"),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
schema = new DateValidationSchema(mockRule).start(startDate).end(endDate);
|
|
95
|
+
const result = schema.validate(new Date("2024-01-01"), { path: ["test"] });
|
|
96
|
+
expect(result.isValid).toBe(false);
|
|
97
|
+
expect(result.errors).toHaveLength(1);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("allowed values validation", () => {
|
|
102
|
+
it("passes validation when date is in allowed values", () => {
|
|
103
|
+
const allowedDates = [new Date("2023-01-01"), new Date("2023-06-15"), new Date("2023-12-31")];
|
|
104
|
+
schema.allowedValues(allowedDates);
|
|
105
|
+
|
|
106
|
+
const result = schema.validate(new Date("2023-06-15"), { path: ["test"] });
|
|
107
|
+
expect(result.isValid).toBe(true);
|
|
108
|
+
expect(result.errors).toHaveLength(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("fails validation when date is not in allowed values", () => {
|
|
112
|
+
const allowedDates = [new Date("2023-01-01"), new Date("2023-06-15"), new Date("2023-12-31")];
|
|
113
|
+
schema.allowedValues(allowedDates);
|
|
114
|
+
|
|
115
|
+
const result = schema.validate(new Date("2023-03-15"), { path: ["test"] });
|
|
116
|
+
expect(result.isValid).toBe(false);
|
|
117
|
+
expect(result.errors).toHaveLength(1);
|
|
118
|
+
expect(result.errors[0].message).toContain("The provided value must be one of");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("passes validation when no allowed values are set", () => {
|
|
122
|
+
const result = schema.validate(new Date("2030-01-01"), { path: ["test"] });
|
|
123
|
+
expect(result.isValid).toBe(true);
|
|
124
|
+
expect(result.errors).toHaveLength(0);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("method chaining", () => {
|
|
129
|
+
it("supports method chaining", () => {
|
|
130
|
+
const startDate = new Date("2023-01-01");
|
|
131
|
+
const endDate = new Date("2023-12-31");
|
|
132
|
+
const allowedDates = [new Date("2023-06-15")];
|
|
133
|
+
|
|
134
|
+
const result = schema
|
|
135
|
+
.start(startDate)
|
|
136
|
+
.end(endDate)
|
|
137
|
+
.allowedValues(allowedDates)
|
|
138
|
+
.message("Custom date message")
|
|
139
|
+
.validate(new Date("2023-06-15"), { path: ["test"] });
|
|
140
|
+
|
|
141
|
+
expect(result.isValid).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|