@platecms/delta-validation 0.6.0 → 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.d.ts → rule-instance.interface.ts} +5 -4
- 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 -96
- 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.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 -55
- 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,264 @@
|
|
|
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 { validateWithFunction } from "./validate-with-function";
|
|
7
|
+
|
|
8
|
+
describe("validate-with-function", () => {
|
|
9
|
+
const mockRulePrn = PRN.fromString("prn:plate:-1:cxc:validation-rule:1");
|
|
10
|
+
const mockPath = ["content-type-prn", "content-field-prn"];
|
|
11
|
+
const fallbackErrorMessage = "Validation failed";
|
|
12
|
+
|
|
13
|
+
function createMockRule(settings: object = {}): RuleInstance<object> {
|
|
14
|
+
return {
|
|
15
|
+
id: "test-rule",
|
|
16
|
+
prn: mockRulePrn,
|
|
17
|
+
settings,
|
|
18
|
+
ruleType: RuleType.COUNT,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("validateWithFunction", () => {
|
|
23
|
+
it("returns null when validation function returns true", () => {
|
|
24
|
+
const mockRule = createMockRule();
|
|
25
|
+
const mockValidationFunction = vi.fn().mockReturnValue(true);
|
|
26
|
+
|
|
27
|
+
const result = validateWithFunction(
|
|
28
|
+
"test-value",
|
|
29
|
+
mockRule,
|
|
30
|
+
mockPath,
|
|
31
|
+
fallbackErrorMessage,
|
|
32
|
+
mockValidationFunction,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
expect(result).toBeNull();
|
|
36
|
+
expect(mockValidationFunction).toHaveBeenCalledWith("test-value", mockRule);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("returns null when validation function returns null", () => {
|
|
40
|
+
const mockRule = createMockRule();
|
|
41
|
+
const mockValidationFunction = vi.fn().mockReturnValue(null);
|
|
42
|
+
|
|
43
|
+
const result = validateWithFunction(
|
|
44
|
+
"test-value",
|
|
45
|
+
mockRule,
|
|
46
|
+
mockPath,
|
|
47
|
+
fallbackErrorMessage,
|
|
48
|
+
mockValidationFunction,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(result).toBeNull();
|
|
52
|
+
expect(mockValidationFunction).toHaveBeenCalledWith("test-value", mockRule);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns ValidationErrorData when validation function returns ValidationErrorDetails", () => {
|
|
56
|
+
const mockRule = createMockRule();
|
|
57
|
+
const validationError = new ValidationErrorDetails("Custom error message", mockRule);
|
|
58
|
+
const mockValidationFunction = vi.fn().mockReturnValue(validationError);
|
|
59
|
+
|
|
60
|
+
const result = validateWithFunction(
|
|
61
|
+
"test-value",
|
|
62
|
+
mockRule,
|
|
63
|
+
mockPath,
|
|
64
|
+
fallbackErrorMessage,
|
|
65
|
+
mockValidationFunction,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
expect(result).toEqual({
|
|
69
|
+
path: mockPath,
|
|
70
|
+
message: "Custom error message",
|
|
71
|
+
provided: "test-value",
|
|
72
|
+
validationRule: mockRule,
|
|
73
|
+
});
|
|
74
|
+
expect(mockValidationFunction).toHaveBeenCalledWith("test-value", mockRule);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("returns ValidationErrorData with fallback message when validation function returns non-ValidationErrorDetails", () => {
|
|
78
|
+
const mockRule = createMockRule();
|
|
79
|
+
const mockValidationFunction = vi.fn().mockReturnValue("some-other-result");
|
|
80
|
+
|
|
81
|
+
const result = validateWithFunction(
|
|
82
|
+
"test-value",
|
|
83
|
+
mockRule,
|
|
84
|
+
mockPath,
|
|
85
|
+
fallbackErrorMessage,
|
|
86
|
+
mockValidationFunction,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
expect(result).toEqual({
|
|
90
|
+
path: mockPath,
|
|
91
|
+
message: fallbackErrorMessage,
|
|
92
|
+
provided: "test-value",
|
|
93
|
+
validationRule: mockRule,
|
|
94
|
+
});
|
|
95
|
+
expect(mockValidationFunction).toHaveBeenCalledWith("test-value", mockRule);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("returns ValidationErrorData when validation function throws an Error", () => {
|
|
99
|
+
const mockRule = createMockRule();
|
|
100
|
+
const errorMessage = "Validation function error";
|
|
101
|
+
const mockValidationFunction = vi.fn().mockImplementation(() => {
|
|
102
|
+
throw new Error(errorMessage);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const result = validateWithFunction(
|
|
106
|
+
"test-value",
|
|
107
|
+
mockRule,
|
|
108
|
+
mockPath,
|
|
109
|
+
fallbackErrorMessage,
|
|
110
|
+
mockValidationFunction,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(result).toEqual({
|
|
114
|
+
path: mockPath,
|
|
115
|
+
message: errorMessage,
|
|
116
|
+
provided: "test-value",
|
|
117
|
+
validationRule: mockRule,
|
|
118
|
+
});
|
|
119
|
+
expect(mockValidationFunction).toHaveBeenCalledWith("test-value", mockRule);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("returns ValidationErrorData with fallback message when validation function throws non-Error", () => {
|
|
123
|
+
const mockRule = createMockRule();
|
|
124
|
+
const mockValidationFunction = vi.fn().mockImplementation(() => {
|
|
125
|
+
throw "string error" as unknown as Error;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const result = validateWithFunction(
|
|
129
|
+
"test-value",
|
|
130
|
+
mockRule,
|
|
131
|
+
mockPath,
|
|
132
|
+
fallbackErrorMessage,
|
|
133
|
+
mockValidationFunction,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(result).toEqual({
|
|
137
|
+
path: mockPath,
|
|
138
|
+
message: fallbackErrorMessage,
|
|
139
|
+
provided: "test-value",
|
|
140
|
+
validationRule: mockRule,
|
|
141
|
+
});
|
|
142
|
+
expect(mockValidationFunction).toHaveBeenCalledWith("test-value", mockRule);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("handles different ContentValueType values", () => {
|
|
146
|
+
const mockRule = createMockRule();
|
|
147
|
+
const mockValidationFunction = vi.fn().mockReturnValue(true);
|
|
148
|
+
|
|
149
|
+
// Test with string
|
|
150
|
+
const stringResult = validateWithFunction(
|
|
151
|
+
"string-value",
|
|
152
|
+
mockRule,
|
|
153
|
+
mockPath,
|
|
154
|
+
fallbackErrorMessage,
|
|
155
|
+
mockValidationFunction,
|
|
156
|
+
);
|
|
157
|
+
expect(stringResult).toBeNull();
|
|
158
|
+
|
|
159
|
+
// Test with number
|
|
160
|
+
const numberResult = validateWithFunction(42, mockRule, mockPath, fallbackErrorMessage, mockValidationFunction);
|
|
161
|
+
expect(numberResult).toBeNull();
|
|
162
|
+
|
|
163
|
+
// Test with boolean
|
|
164
|
+
const booleanResult = validateWithFunction(
|
|
165
|
+
true,
|
|
166
|
+
mockRule,
|
|
167
|
+
mockPath,
|
|
168
|
+
fallbackErrorMessage,
|
|
169
|
+
mockValidationFunction,
|
|
170
|
+
);
|
|
171
|
+
expect(booleanResult).toBeNull();
|
|
172
|
+
|
|
173
|
+
// Test with Date
|
|
174
|
+
const dateResult = validateWithFunction(
|
|
175
|
+
new Date("2023-01-01"),
|
|
176
|
+
mockRule,
|
|
177
|
+
mockPath,
|
|
178
|
+
fallbackErrorMessage,
|
|
179
|
+
mockValidationFunction,
|
|
180
|
+
);
|
|
181
|
+
expect(dateResult).toBeNull();
|
|
182
|
+
|
|
183
|
+
// Test with PRN
|
|
184
|
+
const prnResult = validateWithFunction(
|
|
185
|
+
PRN.fromString("prn:plate:-1:cxc:content-item:1"),
|
|
186
|
+
mockRule,
|
|
187
|
+
mockPath,
|
|
188
|
+
fallbackErrorMessage,
|
|
189
|
+
mockValidationFunction,
|
|
190
|
+
);
|
|
191
|
+
expect(prnResult).toBeNull();
|
|
192
|
+
|
|
193
|
+
// Test with Root (smart text)
|
|
194
|
+
const rootResult = validateWithFunction(
|
|
195
|
+
{ type: "root", children: [] },
|
|
196
|
+
mockRule,
|
|
197
|
+
mockPath,
|
|
198
|
+
fallbackErrorMessage,
|
|
199
|
+
mockValidationFunction,
|
|
200
|
+
);
|
|
201
|
+
expect(rootResult).toBeNull();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe("validateWithFunction with arrays", () => {
|
|
206
|
+
it("handles empty values array", () => {
|
|
207
|
+
const mockRule = createMockRule();
|
|
208
|
+
const validationError = new ValidationErrorDetails("Empty array error", mockRule);
|
|
209
|
+
const mockValidationFunction = vi.fn().mockReturnValue(validationError);
|
|
210
|
+
const values: ContentValueType[] = [];
|
|
211
|
+
|
|
212
|
+
const result = validateWithFunction(values, mockRule, mockPath, fallbackErrorMessage, mockValidationFunction);
|
|
213
|
+
|
|
214
|
+
expect(result).toEqual({
|
|
215
|
+
path: mockPath,
|
|
216
|
+
message: "Empty array error",
|
|
217
|
+
provided: values,
|
|
218
|
+
validationRule: mockRule,
|
|
219
|
+
});
|
|
220
|
+
expect(mockValidationFunction).toHaveBeenCalledWith(values, mockRule);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("handles mixed content value types in array", () => {
|
|
224
|
+
const mockRule = createMockRule();
|
|
225
|
+
const validationError = new ValidationErrorDetails("Mixed types error", mockRule);
|
|
226
|
+
const mockValidationFunction = vi.fn().mockReturnValue(validationError);
|
|
227
|
+
const values: ContentValueType[] = [
|
|
228
|
+
"string",
|
|
229
|
+
42,
|
|
230
|
+
true,
|
|
231
|
+
new Date("2023-01-01"),
|
|
232
|
+
PRN.fromString("prn:plate:-1:cxc:content-item:1"),
|
|
233
|
+
{ type: "root", children: [] },
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
const result = validateWithFunction(values, mockRule, mockPath, fallbackErrorMessage, mockValidationFunction);
|
|
237
|
+
|
|
238
|
+
expect(result).toEqual({
|
|
239
|
+
path: mockPath,
|
|
240
|
+
message: "Mixed types error",
|
|
241
|
+
provided: values,
|
|
242
|
+
validationRule: mockRule,
|
|
243
|
+
});
|
|
244
|
+
expect(mockValidationFunction).toHaveBeenCalledWith(values, mockRule);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("handles single value in array", () => {
|
|
248
|
+
const mockRule = createMockRule();
|
|
249
|
+
const validationError = new ValidationErrorDetails("Single value error", mockRule);
|
|
250
|
+
const mockValidationFunction = vi.fn().mockReturnValue(validationError);
|
|
251
|
+
const values: ContentValueType[] = ["single-value"];
|
|
252
|
+
|
|
253
|
+
const result = validateWithFunction(values, mockRule, mockPath, fallbackErrorMessage, mockValidationFunction);
|
|
254
|
+
|
|
255
|
+
expect(result).toEqual({
|
|
256
|
+
path: mockPath,
|
|
257
|
+
message: "Single value error",
|
|
258
|
+
provided: values,
|
|
259
|
+
validationRule: mockRule,
|
|
260
|
+
});
|
|
261
|
+
expect(mockValidationFunction).toHaveBeenCalledWith(values, mockRule);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ValidationErrorDetails } from "../errors/validation-error-details";
|
|
2
|
+
import { RuleInstance } from "../types/rule-instance.interface";
|
|
3
|
+
import { ValidationErrorData } from "../types/validation-error-data.interface";
|
|
4
|
+
import { ContentValidationResultType } from "../validation-rules/base.validation-rule";
|
|
5
|
+
|
|
6
|
+
function validateWithFunctionInternal<TSettings extends object, TValue>(
|
|
7
|
+
value: TValue,
|
|
8
|
+
validationRule: RuleInstance<TSettings>,
|
|
9
|
+
path: string[],
|
|
10
|
+
fallbackErrorMessage: string,
|
|
11
|
+
validationFunction: (value: TValue, validationRule: RuleInstance<TSettings>) => ContentValidationResultType,
|
|
12
|
+
): ValidationErrorData<TSettings> | null {
|
|
13
|
+
let validationResult: ContentValidationResultType;
|
|
14
|
+
try {
|
|
15
|
+
validationResult = validationFunction(value, validationRule);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return {
|
|
18
|
+
path,
|
|
19
|
+
message: error instanceof Error ? error.message : fallbackErrorMessage,
|
|
20
|
+
provided: value,
|
|
21
|
+
validationRule,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (validationResult === true || validationResult === null) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
path,
|
|
31
|
+
message: validationResult instanceof ValidationErrorDetails ? validationResult.message : fallbackErrorMessage,
|
|
32
|
+
provided: value,
|
|
33
|
+
validationRule,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function validateWithFunction<TSettings extends object, TType>(
|
|
38
|
+
valueOrValues: TType,
|
|
39
|
+
validationRule: RuleInstance<TSettings>,
|
|
40
|
+
path: string[],
|
|
41
|
+
fallbackErrorMessage: string,
|
|
42
|
+
validationFunction: (input: TType, validationRule: RuleInstance<TSettings>) => ContentValidationResultType,
|
|
43
|
+
): ValidationErrorData<TSettings> | null {
|
|
44
|
+
return validateWithFunctionInternal(valueOrValues, validationRule, path, fallbackErrorMessage, validationFunction);
|
|
45
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { PRN } from "@platecms/delta-plate-resource-notation";
|
|
2
|
+
import { ContentValueType } from "@platecms/delta-types";
|
|
3
|
+
import { RuleType } from "../enums/rule-types.enum";
|
|
4
|
+
import { ArrayValidationSchema } from "./array.schema";
|
|
5
|
+
import { StringValidationSchema } from "./string.schema";
|
|
6
|
+
|
|
7
|
+
describe("ArrayValidationSchema", () => {
|
|
8
|
+
let schema: ArrayValidationSchema<ContentValueType>;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
schema = new ArrayValidationSchema<ContentValueType>();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe("type checking", () => {
|
|
15
|
+
it("passes validation when value is an array", () => {
|
|
16
|
+
const result = schema.validate([1, 2, 3] as ContentValueType[], { path: ["test"] });
|
|
17
|
+
expect(result.isValid).toBe(true);
|
|
18
|
+
expect(result.errors).toHaveLength(0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("fails validation when value is not an array", () => {
|
|
22
|
+
const result = schema.validate("not an array" as unknown as ContentValueType[], { path: ["test"] });
|
|
23
|
+
expect(result.isValid).toBe(false);
|
|
24
|
+
expect(result.errors).toHaveLength(1);
|
|
25
|
+
expect(result.errors[0].message).toBe("Value must be an array");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("passes validation when value is null (allowed by default)", () => {
|
|
29
|
+
const result = schema.validate(null as unknown as ContentValueType[], { path: ["test"] });
|
|
30
|
+
expect(result.isValid).toBe(true);
|
|
31
|
+
expect(result.errors).toHaveLength(0);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("passes validation when value is undefined (allowed by default)", () => {
|
|
35
|
+
const result = schema.validate(undefined as unknown as ContentValueType[], { path: ["test"] });
|
|
36
|
+
expect(result.isValid).toBe(true);
|
|
37
|
+
expect(result.errors).toHaveLength(0);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("count validation", () => {
|
|
42
|
+
it("passes validation when array length is within min and max bounds", () => {
|
|
43
|
+
schema.min(2).max(4);
|
|
44
|
+
const result = schema.validate([1, 2, 3] as ContentValueType[], { path: ["test"] });
|
|
45
|
+
expect(result.isValid).toBe(true);
|
|
46
|
+
expect(result.errors).toHaveLength(0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("fails validation when array length is below minimum", () => {
|
|
50
|
+
schema.min(3);
|
|
51
|
+
const result = schema.validate([1, 2] as ContentValueType[], { path: ["test"] });
|
|
52
|
+
expect(result.isValid).toBe(false);
|
|
53
|
+
expect(result.errors).toHaveLength(1);
|
|
54
|
+
expect(result.errors[0].message).toContain("The amount of provided values must be");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("fails validation when array length is above maximum", () => {
|
|
58
|
+
schema.max(2);
|
|
59
|
+
const result = schema.validate([1, 2, 3] as ContentValueType[], { path: ["test"] });
|
|
60
|
+
expect(result.isValid).toBe(false);
|
|
61
|
+
expect(result.errors).toHaveLength(1);
|
|
62
|
+
expect(result.errors[0].message).toContain("The amount of provided values must be");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("passes validation when no min/max constraints are set", () => {
|
|
66
|
+
const result = schema.validate([1, 2, 3, 4, 5] as ContentValueType[], { path: ["test"] });
|
|
67
|
+
expect(result.isValid).toBe(true);
|
|
68
|
+
expect(result.errors).toHaveLength(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("handles empty array with min constraint", () => {
|
|
72
|
+
schema.min(1);
|
|
73
|
+
const result = schema.validate([] as ContentValueType[], { path: ["test"] });
|
|
74
|
+
expect(result.isValid).toBe(false);
|
|
75
|
+
expect(result.errors).toHaveLength(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("handles empty array with max constraint", () => {
|
|
79
|
+
schema.max(2);
|
|
80
|
+
const result = schema.validate([] as ContentValueType[], { path: ["test"] });
|
|
81
|
+
expect(result.isValid).toBe(true);
|
|
82
|
+
expect(result.errors).toHaveLength(0);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("item validation", () => {
|
|
87
|
+
it("handles empty array with item schema", () => {
|
|
88
|
+
const stringSchema = new StringValidationSchema();
|
|
89
|
+
schema.of(stringSchema);
|
|
90
|
+
const result = schema.validate([] as ContentValueType[], { path: ["test"] });
|
|
91
|
+
expect(result.isValid).toBe(true);
|
|
92
|
+
expect(result.errors).toHaveLength(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("does not validate items when no item schema is set", () => {
|
|
96
|
+
const result = schema.validate(["string", 123, true] as ContentValueType[], { path: ["test"] });
|
|
97
|
+
expect(result.isValid).toBe(true);
|
|
98
|
+
expect(result.errors).toHaveLength(0);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("integration with validation rules", () => {
|
|
103
|
+
it("uses validation rule for count validation", () => {
|
|
104
|
+
const mockRule = {
|
|
105
|
+
ruleType: RuleType.COUNT,
|
|
106
|
+
settings: { min: 2, max: 4 },
|
|
107
|
+
id: "count-rule",
|
|
108
|
+
prn: PRN.fromString("prn:plate:-1:cxc:validation-rule:1"),
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const schemaWithRule = new ArrayValidationSchema<ContentValueType>(mockRule);
|
|
112
|
+
const result = schemaWithRule.validate([1, 2, 3] as ContentValueType[], { path: ["test"] });
|
|
113
|
+
expect(result.isValid).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("method chaining", () => {
|
|
118
|
+
it("supports method chaining", () => {
|
|
119
|
+
const result = schema
|
|
120
|
+
.min(1)
|
|
121
|
+
.max(5)
|
|
122
|
+
.validate([1, 2, 3] as ContentValueType[], { path: ["test"] });
|
|
123
|
+
|
|
124
|
+
expect(result.isValid).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { RuleType } from "../enums/rule-types.enum";
|
|
2
|
+
import { validateWithFunction } from "../helpers/validate-with-function";
|
|
3
|
+
import { RuleInstance } from "../types/rule-instance.interface";
|
|
4
|
+
import { ValidationContext } from "../types/validation-context.interface";
|
|
5
|
+
import { ValidationErrorData } from "../types/validation-error-data.interface";
|
|
6
|
+
import { ValidationSchema } from "../types/validation-schema.interface";
|
|
7
|
+
import { CountValidationRuleSettings, validateValuesCount } from "../validation-rules/count.validation-rule";
|
|
8
|
+
import { BaseValidationSchema } from "./base.schema";
|
|
9
|
+
|
|
10
|
+
export class ArrayValidationSchema<T> extends BaseValidationSchema<T[], ArrayValidationSchema<T>> {
|
|
11
|
+
public readonly type = "array";
|
|
12
|
+
private _itemSchema?: ValidationSchema<T>;
|
|
13
|
+
private _min?: number;
|
|
14
|
+
private _max?: number;
|
|
15
|
+
|
|
16
|
+
public of(itemSchema: ValidationSchema<T>): this {
|
|
17
|
+
this._itemSchema = itemSchema;
|
|
18
|
+
return this;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public min(min: number): this {
|
|
22
|
+
this._min = min;
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public max(max: number): this {
|
|
27
|
+
this._max = max;
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected _typeCheck(value: unknown, path: string[]): ValidationErrorData[] {
|
|
32
|
+
if (!Array.isArray(value)) {
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
path,
|
|
36
|
+
message: this._message ?? "Value must be an array",
|
|
37
|
+
provided: value,
|
|
38
|
+
validationRule: {
|
|
39
|
+
ruleType: RuleType.COUNT,
|
|
40
|
+
settings: {
|
|
41
|
+
min: this._min,
|
|
42
|
+
max: this._max,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected _validateAgainstSchema(value: T[], context: ValidationContext = {}): ValidationErrorData[] {
|
|
52
|
+
const errors: ValidationErrorData[] = [];
|
|
53
|
+
|
|
54
|
+
const itemsCountError = this._validateCount(value, context.path ?? []);
|
|
55
|
+
if (itemsCountError) {
|
|
56
|
+
errors.push(itemsCountError);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
errors.push(...this._validateItems(value, context));
|
|
60
|
+
|
|
61
|
+
return errors;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private _validateCount(value: T[], path: string[]): ValidationErrorData<CountValidationRuleSettings> | null {
|
|
65
|
+
if (this._min === undefined && this._max === undefined && this._validationRule === undefined) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const validationRule = this._validationRule ?? {
|
|
70
|
+
ruleType: RuleType.COUNT,
|
|
71
|
+
settings: {
|
|
72
|
+
min: this._min,
|
|
73
|
+
max: this._max,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return validateWithFunction<CountValidationRuleSettings, T[]>(
|
|
78
|
+
value,
|
|
79
|
+
validationRule as RuleInstance<CountValidationRuleSettings>,
|
|
80
|
+
path,
|
|
81
|
+
this._message ?? "Array count validation failed",
|
|
82
|
+
validateValuesCount,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private _validateItems(value: T[], context: ValidationContext): ValidationErrorData[] {
|
|
87
|
+
if (this._itemSchema === undefined) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const errors: ValidationErrorData[] = [];
|
|
92
|
+
for (let i = 0; i < value.length; i++) {
|
|
93
|
+
const itemValidationResult = this._itemSchema.validate(value[i], {
|
|
94
|
+
...context,
|
|
95
|
+
path: [...(context.path ?? []), i.toString()],
|
|
96
|
+
});
|
|
97
|
+
if (!itemValidationResult.isValid) {
|
|
98
|
+
errors.push(...itemValidationResult.errors);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return errors;
|
|
103
|
+
}
|
|
104
|
+
}
|