@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.
Files changed (149) hide show
  1. package/package.json +5 -6
  2. package/src/lib/enums/rule-types.enum.ts +10 -0
  3. package/src/lib/errors/invalid-validation-arguments.error.ts +1 -0
  4. package/src/lib/errors/invalid-validation-rule-settings.error.ts +1 -0
  5. package/src/lib/errors/validation-error-details.ts +15 -0
  6. package/src/lib/errors/validation.error.ts +18 -0
  7. package/src/lib/helpers/deserialize-validation-rule.ts +94 -0
  8. package/src/lib/helpers/rule-to-schema.spec.ts +254 -0
  9. package/src/lib/helpers/rule-to-schema.ts +95 -0
  10. package/src/lib/helpers/schema-builder.spec.ts +52 -0
  11. package/src/lib/helpers/schema-builder.ts +28 -0
  12. package/src/lib/helpers/validate-with-function.spec.ts +264 -0
  13. package/src/lib/helpers/validate-with-function.ts +45 -0
  14. package/src/lib/schemas/array.schema.spec.ts +127 -0
  15. package/src/lib/schemas/array.schema.ts +104 -0
  16. package/src/lib/schemas/base.schema.spec.ts +168 -0
  17. package/src/lib/schemas/base.schema.ts +150 -0
  18. package/src/lib/schemas/content-value.schema.spec.ts +133 -0
  19. package/src/lib/schemas/content-value.schema.ts +61 -0
  20. package/src/lib/schemas/date.schema.spec.ts +144 -0
  21. package/src/lib/schemas/date.schema.ts +82 -0
  22. package/src/lib/schemas/number.schema.spec.ts +232 -0
  23. package/src/lib/schemas/number.schema.ts +123 -0
  24. package/src/lib/schemas/string.schema.spec.ts +135 -0
  25. package/src/lib/schemas/string.schema.ts +73 -0
  26. package/src/lib/test-utils/setup-files.ts +1 -0
  27. package/src/lib/test-utils/validation-result-matcher.ts +71 -0
  28. package/src/lib/types/{existing-content-validation-rules-settings.type.d.ts → existing-content-validation-rules-settings.type.ts} +10 -1
  29. package/src/lib/types/{existing-content-validation-rules.type.d.ts → existing-content-validation-rules.type.ts} +10 -1
  30. package/src/lib/types/{existing-validation-schemas.type.d.ts → existing-validation-schemas.type.ts} +7 -1
  31. package/src/lib/types/rule-instance.interface.ts +9 -0
  32. package/src/lib/types/{validation-context.interface.d.ts → validation-context.interface.ts} +3 -2
  33. package/src/lib/types/{validation-error-data.interface.d.ts → validation-error-data.interface.ts} +5 -4
  34. package/src/lib/types/{validation-result.interface.d.ts → validation-result.interface.ts} +3 -2
  35. package/src/lib/types/validation-schema.interface.ts +15 -0
  36. package/src/lib/validation-rules/allowed-values.validation-rule.spec.ts +57 -0
  37. package/src/lib/validation-rules/allowed-values.validation-rule.ts +53 -0
  38. package/src/lib/validation-rules/base.validation-rule.ts +35 -0
  39. package/src/lib/validation-rules/count.validation-rule.spec.ts +67 -0
  40. package/src/lib/validation-rules/count.validation-rule.ts +60 -0
  41. package/src/lib/validation-rules/date-between.validation-rule.spec.ts +77 -0
  42. package/src/lib/validation-rules/date-between.validation-rule.ts +54 -0
  43. package/src/lib/validation-rules/decimal-count.validation-rule.spec.ts +46 -0
  44. package/src/lib/validation-rules/decimal-count.validation-rule.ts +52 -0
  45. package/src/lib/validation-rules/number-between.validation-rule.spec.ts +77 -0
  46. package/src/lib/validation-rules/number-between.validation-rule.ts +58 -0
  47. package/src/lib/validation-rules/relatable-content-types.validation-rule.spec.ts +35 -0
  48. package/src/lib/validation-rules/relatable-content-types.validation-rule.ts +61 -0
  49. package/src/lib/validation-rules/string-format.validation-rule.spec.ts +43 -0
  50. package/src/lib/validation-rules/string-format.validation-rule.ts +50 -0
  51. package/src/lib/validation-rules/validation-rule-function.factory.ts +23 -0
  52. package/src/lib/validation-rules/value-type.validation-rule.spec.ts +108 -0
  53. package/src/lib/validation-rules/value-type.validation-rule.ts +76 -0
  54. package/src/index.js +0 -23
  55. package/src/index.js.map +0 -1
  56. package/src/lib/enums/rule-types.enum.d.ts +0 -10
  57. package/src/lib/enums/rule-types.enum.js +0 -15
  58. package/src/lib/enums/rule-types.enum.js.map +0 -1
  59. package/src/lib/errors/invalid-validation-arguments.error.d.ts +0 -2
  60. package/src/lib/errors/invalid-validation-arguments.error.js +0 -7
  61. package/src/lib/errors/invalid-validation-arguments.error.js.map +0 -1
  62. package/src/lib/errors/invalid-validation-rule-settings.error.d.ts +0 -2
  63. package/src/lib/errors/invalid-validation-rule-settings.error.js +0 -7
  64. package/src/lib/errors/invalid-validation-rule-settings.error.js.map +0 -1
  65. package/src/lib/errors/validation-error-details.d.ts +0 -6
  66. package/src/lib/errors/validation-error-details.js +0 -18
  67. package/src/lib/errors/validation-error-details.js.map +0 -1
  68. package/src/lib/errors/validation.error.d.ts +0 -8
  69. package/src/lib/errors/validation.error.js +0 -13
  70. package/src/lib/errors/validation.error.js.map +0 -1
  71. package/src/lib/helpers/deserialize-validation-rule.d.ts +0 -2
  72. package/src/lib/helpers/deserialize-validation-rule.js +0 -42
  73. package/src/lib/helpers/deserialize-validation-rule.js.map +0 -1
  74. package/src/lib/helpers/rule-to-schema.d.ts +0 -4
  75. package/src/lib/helpers/rule-to-schema.js +0 -65
  76. package/src/lib/helpers/rule-to-schema.js.map +0 -1
  77. package/src/lib/helpers/schema-builder.d.ts +0 -12
  78. package/src/lib/helpers/schema-builder.js +0 -27
  79. package/src/lib/helpers/schema-builder.js.map +0 -1
  80. package/src/lib/helpers/validate-with-function.d.ts +0 -4
  81. package/src/lib/helpers/validate-with-function.js +0 -31
  82. package/src/lib/helpers/validate-with-function.js.map +0 -1
  83. package/src/lib/schemas/array.schema.d.ts +0 -17
  84. package/src/lib/schemas/array.schema.js +0 -84
  85. package/src/lib/schemas/array.schema.js.map +0 -1
  86. package/src/lib/schemas/base.schema.d.ts +0 -20
  87. package/src/lib/schemas/base.schema.js +0 -95
  88. package/src/lib/schemas/base.schema.js.map +0 -1
  89. package/src/lib/schemas/content-value.schema.d.ts +0 -15
  90. package/src/lib/schemas/content-value.schema.js +0 -42
  91. package/src/lib/schemas/content-value.schema.js.map +0 -1
  92. package/src/lib/schemas/date.schema.d.ts +0 -13
  93. package/src/lib/schemas/date.schema.js +0 -62
  94. package/src/lib/schemas/date.schema.js.map +0 -1
  95. package/src/lib/schemas/number.schema.d.ts +0 -16
  96. package/src/lib/schemas/number.schema.js +0 -84
  97. package/src/lib/schemas/number.schema.js.map +0 -1
  98. package/src/lib/schemas/string.schema.d.ts +0 -11
  99. package/src/lib/schemas/string.schema.js +0 -56
  100. package/src/lib/schemas/string.schema.js.map +0 -1
  101. package/src/lib/types/existing-content-validation-rules-settings.type.js +0 -3
  102. package/src/lib/types/existing-content-validation-rules-settings.type.js.map +0 -1
  103. package/src/lib/types/existing-content-validation-rules.type.js +0 -3
  104. package/src/lib/types/existing-content-validation-rules.type.js.map +0 -1
  105. package/src/lib/types/existing-validation-schemas.type.js +0 -3
  106. package/src/lib/types/existing-validation-schemas.type.js.map +0 -1
  107. package/src/lib/types/rule-instance.interface.d.ts +0 -6
  108. package/src/lib/types/rule-instance.interface.js +0 -3
  109. package/src/lib/types/rule-instance.interface.js.map +0 -1
  110. package/src/lib/types/validation-context.interface.js +0 -3
  111. package/src/lib/types/validation-context.interface.js.map +0 -1
  112. package/src/lib/types/validation-error-data.interface.js +0 -3
  113. package/src/lib/types/validation-error-data.interface.js.map +0 -1
  114. package/src/lib/types/validation-result.interface.js +0 -3
  115. package/src/lib/types/validation-result.interface.js.map +0 -1
  116. package/src/lib/types/validation-schema.interface.d.ts +0 -7
  117. package/src/lib/types/validation-schema.interface.js +0 -3
  118. package/src/lib/types/validation-schema.interface.js.map +0 -1
  119. package/src/lib/validation-rules/allowed-values.validation-rule.d.ts +0 -13
  120. package/src/lib/validation-rules/allowed-values.validation-rule.js +0 -36
  121. package/src/lib/validation-rules/allowed-values.validation-rule.js.map +0 -1
  122. package/src/lib/validation-rules/base.validation-rule.d.ts +0 -13
  123. package/src/lib/validation-rules/base.validation-rule.js +0 -14
  124. package/src/lib/validation-rules/base.validation-rule.js.map +0 -1
  125. package/src/lib/validation-rules/count.validation-rule.d.ts +0 -16
  126. package/src/lib/validation-rules/count.validation-rule.js +0 -36
  127. package/src/lib/validation-rules/count.validation-rule.js.map +0 -1
  128. package/src/lib/validation-rules/date-between.validation-rule.d.ts +0 -16
  129. package/src/lib/validation-rules/date-between.validation-rule.js +0 -36
  130. package/src/lib/validation-rules/date-between.validation-rule.js.map +0 -1
  131. package/src/lib/validation-rules/decimal-count.validation-rule.d.ts +0 -13
  132. package/src/lib/validation-rules/decimal-count.validation-rule.js +0 -37
  133. package/src/lib/validation-rules/decimal-count.validation-rule.js.map +0 -1
  134. package/src/lib/validation-rules/number-between.validation-rule.d.ts +0 -16
  135. package/src/lib/validation-rules/number-between.validation-rule.js +0 -37
  136. package/src/lib/validation-rules/number-between.validation-rule.js.map +0 -1
  137. package/src/lib/validation-rules/relatable-content-types.validation-rule.d.ts +0 -13
  138. package/src/lib/validation-rules/relatable-content-types.validation-rule.js +0 -37
  139. package/src/lib/validation-rules/relatable-content-types.validation-rule.js.map +0 -1
  140. package/src/lib/validation-rules/string-format.validation-rule.d.ts +0 -13
  141. package/src/lib/validation-rules/string-format.validation-rule.js +0 -35
  142. package/src/lib/validation-rules/string-format.validation-rule.js.map +0 -1
  143. package/src/lib/validation-rules/validation-rule-function.factory.d.ts +0 -4
  144. package/src/lib/validation-rules/validation-rule-function.factory.js +0 -9
  145. package/src/lib/validation-rules/validation-rule-function.factory.js.map +0 -1
  146. package/src/lib/validation-rules/value-type.validation-rule.d.ts +0 -13
  147. package/src/lib/validation-rules/value-type.validation-rule.js +0 -53
  148. package/src/lib/validation-rules/value-type.validation-rule.js.map +0 -1
  149. /package/src/{index.d.ts → index.ts} +0 -0
@@ -0,0 +1,82 @@
1
+ import { ContentValueTypeNames } 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 {
8
+ DateBetweenValidationRuleSettings,
9
+ validateValueDateBetween,
10
+ } from "../validation-rules/date-between.validation-rule";
11
+ import { BaseValidationSchema } from "./base.schema";
12
+
13
+ export class DateValidationSchema extends BaseValidationSchema<Date, DateValidationSchema> {
14
+ public readonly type = "date";
15
+ private _start?: Date;
16
+ private _end?: Date;
17
+
18
+ public start(start: Date): this {
19
+ this._start = start;
20
+ return this;
21
+ }
22
+
23
+ public end(end: Date): this {
24
+ this._end = end;
25
+ return this;
26
+ }
27
+
28
+ protected _typeCheck(value: unknown, path: string[]): ValidationErrorData[] {
29
+ if (!(value instanceof Date)) {
30
+ return [
31
+ {
32
+ path,
33
+ message: this._message ?? "Value must be a valid date",
34
+ provided: value,
35
+ validationRule: {
36
+ ruleType: RuleType.VALUE_TYPE,
37
+ settings: { allowedTypes: [ContentValueTypeNames.DATE] },
38
+ },
39
+ },
40
+ ];
41
+ }
42
+
43
+ return [];
44
+ }
45
+
46
+ protected _validateAgainstSchema(value: Date, context: ValidationContext = {}): ValidationErrorData[] {
47
+ const path = context.path ?? [];
48
+ const errors: ValidationErrorData[] = [];
49
+
50
+ const dateBetweenError = this._validateDateBetween(value, path);
51
+ if (dateBetweenError) {
52
+ errors.push(dateBetweenError);
53
+ }
54
+
55
+ return errors;
56
+ }
57
+
58
+ private _validateDateBetween(
59
+ value: Date,
60
+ path: string[],
61
+ ): ValidationErrorData<DateBetweenValidationRuleSettings> | null {
62
+ if (this._start === undefined && this._end === undefined) {
63
+ return null;
64
+ }
65
+
66
+ const validationRule = this._validationRule ?? {
67
+ ruleType: RuleType.DATE_BETWEEN,
68
+ settings: {
69
+ start: this._start,
70
+ end: this._end,
71
+ },
72
+ };
73
+
74
+ return validateWithFunction<DateBetweenValidationRuleSettings, Date>(
75
+ value,
76
+ validationRule as RuleInstance<DateBetweenValidationRuleSettings>,
77
+ path,
78
+ this._message ?? "Date is outside allowed range",
79
+ validateValueDateBetween,
80
+ );
81
+ }
82
+ }
@@ -0,0 +1,232 @@
1
+ import { NumberValidationSchema } from "./number.schema";
2
+
3
+ describe("NumberValidationSchema", () => {
4
+ let schema: NumberValidationSchema;
5
+
6
+ beforeEach(() => {
7
+ schema = new NumberValidationSchema();
8
+ });
9
+
10
+ describe("type checking", () => {
11
+ it("passes validation when value is a number", () => {
12
+ const result = schema.validate(42, { path: ["test"] });
13
+ expect(result.isValid).toBe(true);
14
+ expect(result.errors).toHaveLength(0);
15
+ });
16
+
17
+ it("passes validation when value is zero", () => {
18
+ const result = schema.validate(0, { path: ["test"] });
19
+ expect(result.isValid).toBe(true);
20
+ expect(result.errors).toHaveLength(0);
21
+ });
22
+
23
+ it("passes validation when value is negative", () => {
24
+ const result = schema.validate(-42, { path: ["test"] });
25
+ expect(result.isValid).toBe(true);
26
+ expect(result.errors).toHaveLength(0);
27
+ });
28
+
29
+ it("passes validation when value is a decimal", () => {
30
+ const result = schema.validate(3.14, { path: ["test"] });
31
+ expect(result.isValid).toBe(true);
32
+ expect(result.errors).toHaveLength(0);
33
+ });
34
+
35
+ it("passes validation when value is a string that can be converted to number", () => {
36
+ const result = schema.validate("42" as unknown as number, { path: ["test"] });
37
+ expect(result.isValid).toBe(true);
38
+ expect(result.errors).toHaveLength(0);
39
+ });
40
+
41
+ it("fails validation when value cannot be converted to a number", () => {
42
+ const result = schema.validate("not-a-number" as unknown as number, { path: ["test"] });
43
+ expect(result.isValid).toBe(false);
44
+ expect(result.errors).toHaveLength(1);
45
+ expect(result.errors[0].message).toBe("Value must be a valid number");
46
+ expect(result.errors[0].path).toEqual(["test"]);
47
+ });
48
+
49
+ it("passes validation when value is null", () => {
50
+ const result = schema.validate(null as unknown as number, { path: ["test"] });
51
+ expect(result.isValid).toBe(true);
52
+ expect(result.errors).toHaveLength(0);
53
+ });
54
+
55
+ it("passes validation when value is undefined", () => {
56
+ const result = schema.validate(undefined as unknown as number, { path: ["test"] });
57
+ expect(result.isValid).toBe(true);
58
+ expect(result.errors).toHaveLength(0);
59
+ });
60
+
61
+ it("fails validation when value is an object that cannot be converted", () => {
62
+ const result = schema.validate({ value: 42 } as unknown as number, { path: ["test"] });
63
+ expect(result.isValid).toBe(false);
64
+ expect(result.errors).toHaveLength(1);
65
+ expect(result.errors[0].message).toBe("Value must be a valid number");
66
+ });
67
+
68
+ it("fails validation when value is NaN", () => {
69
+ const result = schema.validate(NaN, { path: ["test"] });
70
+ expect(result.isValid).toBe(false);
71
+ expect(result.errors).toHaveLength(1);
72
+ expect(result.errors[0].message).toBe("Value must be a valid number");
73
+ });
74
+ });
75
+
76
+ describe("range validation", () => {
77
+ it("passes validation when number is within min and max bounds", () => {
78
+ schema.min(0).max(100);
79
+ const result = schema.validate(50, { path: ["test"] });
80
+ expect(result.isValid).toBe(true);
81
+ expect(result.errors).toHaveLength(0);
82
+ });
83
+
84
+ it("fails validation when number is below minimum", () => {
85
+ schema.min(10);
86
+ const result = schema.validate(5, { path: ["test"] });
87
+ expect(result.isValid).toBe(false);
88
+ expect(result.errors).toHaveLength(1);
89
+ expect(result.errors[0].message).toContain("The value must be greater than or equal");
90
+ });
91
+
92
+ it("fails validation when number is above maximum", () => {
93
+ schema.max(100);
94
+ const result = schema.validate(150, { path: ["test"] });
95
+ expect(result.isValid).toBe(false);
96
+ expect(result.errors).toHaveLength(1);
97
+ expect(result.errors[0].message).toContain("The value must be less than or equal");
98
+ });
99
+
100
+ it("passes validation when number equals minimum", () => {
101
+ schema.min(10);
102
+ const result = schema.validate(10, { path: ["test"] });
103
+ expect(result.isValid).toBe(true);
104
+ expect(result.errors).toHaveLength(0);
105
+ });
106
+
107
+ it("passes validation when number equals maximum", () => {
108
+ schema.max(100);
109
+ const result = schema.validate(100, { path: ["test"] });
110
+ expect(result.isValid).toBe(true);
111
+ expect(result.errors).toHaveLength(0);
112
+ });
113
+
114
+ it("passes validation when no min/max constraints are set", () => {
115
+ const result = schema.validate(999999, { path: ["test"] });
116
+ expect(result.isValid).toBe(true);
117
+ expect(result.errors).toHaveLength(0);
118
+ });
119
+
120
+ it("handles decimal numbers with constraints", () => {
121
+ schema.min(0).max(10);
122
+ const result = schema.validate(3.14, { path: ["test"] });
123
+ expect(result.isValid).toBe(true);
124
+ expect(result.errors).toHaveLength(0);
125
+ });
126
+ });
127
+
128
+ describe("decimal count validation", () => {
129
+ it("passes validation when decimal places are within limit", () => {
130
+ schema.maxDecimals(2);
131
+ const result = schema.validate(3.14, { path: ["test"] });
132
+ expect(result.isValid).toBe(true);
133
+ expect(result.errors).toHaveLength(0);
134
+ });
135
+
136
+ it("fails validation when decimal places exceed limit", () => {
137
+ schema.maxDecimals(2);
138
+ const result = schema.validate(3.14159, { path: ["test"] });
139
+ expect(result.isValid).toBe(false);
140
+ expect(result.errors).toHaveLength(1);
141
+ expect(result.errors[0].message).toContain("The number must not have more than");
142
+ });
143
+
144
+ it("passes validation when number has no decimal places", () => {
145
+ schema.maxDecimals(2);
146
+ const result = schema.validate(42, { path: ["test"] });
147
+ expect(result.isValid).toBe(true);
148
+ expect(result.errors).toHaveLength(0);
149
+ });
150
+
151
+ it("passes validation when number has exactly the maximum decimal places", () => {
152
+ schema.maxDecimals(3);
153
+ const result = schema.validate(3.141, { path: ["test"] });
154
+ expect(result.isValid).toBe(true);
155
+ expect(result.errors).toHaveLength(0);
156
+ });
157
+
158
+ it("passes validation when no decimal constraint is set", () => {
159
+ const result = schema.validate(3.14159265359, { path: ["test"] });
160
+ expect(result.isValid).toBe(true);
161
+ expect(result.errors).toHaveLength(0);
162
+ });
163
+
164
+ it("handles zero decimal places", () => {
165
+ schema.maxDecimals(0);
166
+ const result = schema.validate(3.14, { path: ["test"] });
167
+ expect(result.isValid).toBe(false);
168
+ expect(result.errors).toHaveLength(1);
169
+ });
170
+ });
171
+
172
+ describe("allowed values validation", () => {
173
+ it("passes validation when number is in allowed values", () => {
174
+ schema.allowedValues([1, 2.5, -1, 42]);
175
+ const result = schema.validate(42, { path: ["test"] });
176
+ expect(result.isValid).toBe(true);
177
+ expect(result.errors).toHaveLength(0);
178
+ });
179
+
180
+ it("fails validation when number is not in allowed values", () => {
181
+ schema.allowedValues([1, 2, 3, 42]);
182
+ const result = schema.validate(99, { path: ["test"] });
183
+ expect(result.isValid).toBe(false);
184
+ expect(result.errors).toHaveLength(1);
185
+ expect(result.errors[0].message).toBe("The provided value must be one of 1, 2, 3, 42");
186
+ });
187
+
188
+ it("passes validation when no allowed values are set", () => {
189
+ const result = schema.validate(999, { path: ["test"] });
190
+ expect(result.isValid).toBe(true);
191
+ expect(result.errors).toHaveLength(0);
192
+ });
193
+ });
194
+
195
+ describe("method chaining", () => {
196
+ it("supports method chaining", () => {
197
+ const result = schema
198
+ .min(0)
199
+ .max(100)
200
+ .maxDecimals(2)
201
+ .allowedValues([3.14, 2.71])
202
+ .message("Custom number message")
203
+ .validate(3.14, { path: ["test"] });
204
+
205
+ expect(result.isValid).toBe(true);
206
+ });
207
+ });
208
+
209
+ describe("complex validation scenarios", () => {
210
+ it("combines multiple validations", () => {
211
+ schema.min(0).max(100).maxDecimals(2).allowedValues([3.14, 2.71]);
212
+
213
+ const result = schema.validate(3.14159, { path: ["test"] });
214
+ expect(result.isValid).toBe(false);
215
+ expect(result.errors).toHaveLength(1); // Only decimal count validation fails
216
+ });
217
+
218
+ it("handles multiple validation failures", () => {
219
+ schema.min(10).max(100).maxDecimals(0);
220
+
221
+ const result = schema.validate(5.5, { path: ["test"] });
222
+ expect(result.isValid).toBe(false);
223
+ expect(result.errors).toHaveLength(2); // Both range and decimal count validation fail
224
+ });
225
+
226
+ it("provides correct error paths", () => {
227
+ const result = schema.min(10).validate(5, { path: ["user", "age"] });
228
+ expect(result.isValid).toBe(false);
229
+ expect(result.errors[0].path).toEqual(["user", "age"]);
230
+ });
231
+ });
232
+ });
@@ -0,0 +1,123 @@
1
+ import { ContentValueTypeNames } 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 {
8
+ DecimalCountValidationRuleSettings,
9
+ validateValueDecimalCount,
10
+ } from "../validation-rules/decimal-count.validation-rule";
11
+ import {
12
+ NumberBetweenValidationRuleSettings,
13
+ validateValueNumberBetween,
14
+ } from "../validation-rules/number-between.validation-rule";
15
+ import { BaseValidationSchema } from "./base.schema";
16
+
17
+ export class NumberValidationSchema extends BaseValidationSchema<number, NumberValidationSchema> {
18
+ public readonly type = "number";
19
+ private _min?: number;
20
+ private _max?: number;
21
+ private _maxDecimals?: number;
22
+
23
+ public min(min: number): this {
24
+ this._min = min;
25
+ return this;
26
+ }
27
+
28
+ public max(max: number): this {
29
+ this._max = max;
30
+ return this;
31
+ }
32
+
33
+ public maxDecimals(maxDecimals: number): this {
34
+ this._maxDecimals = maxDecimals;
35
+ return this;
36
+ }
37
+
38
+ protected _typeCheck(value: unknown, path: string[]): ValidationErrorData[] {
39
+ const numberValue = Number(value);
40
+
41
+ if (Number.isNaN(numberValue)) {
42
+ return [
43
+ {
44
+ path,
45
+ message: this._message ?? "Value must be a valid number",
46
+ provided: value,
47
+ validationRule: {
48
+ ruleType: RuleType.VALUE_TYPE,
49
+ settings: { allowedTypes: [ContentValueTypeNames.NUMBER] },
50
+ },
51
+ },
52
+ ];
53
+ }
54
+
55
+ return [];
56
+ }
57
+
58
+ protected _validateAgainstSchema(value: number, context: ValidationContext = {}): ValidationErrorData[] {
59
+ const path = context.path ?? [];
60
+ const errors: ValidationErrorData[] = [];
61
+
62
+ const numberBetweenError = this._validateNumberBetween(value, path);
63
+ if (numberBetweenError) {
64
+ errors.push(numberBetweenError);
65
+ }
66
+
67
+ const decimalCountError = this._validateDecimalCount(value, path);
68
+ if (decimalCountError) {
69
+ errors.push(decimalCountError);
70
+ }
71
+
72
+ return errors;
73
+ }
74
+
75
+ private _validateNumberBetween(
76
+ value: number,
77
+ path: string[],
78
+ ): ValidationErrorData<NumberBetweenValidationRuleSettings> | null {
79
+ if (this._min === undefined && this._max === undefined) {
80
+ return null;
81
+ }
82
+
83
+ const validationRule = this._validationRule ?? {
84
+ ruleType: RuleType.NUMBER_BETWEEN,
85
+ settings: {
86
+ min: this._min,
87
+ max: this._max,
88
+ },
89
+ };
90
+
91
+ return validateWithFunction<NumberBetweenValidationRuleSettings, number>(
92
+ value,
93
+ validationRule as RuleInstance<NumberBetweenValidationRuleSettings>,
94
+ path,
95
+ this._message ?? "Number is outside allowed range",
96
+ validateValueNumberBetween,
97
+ );
98
+ }
99
+
100
+ private _validateDecimalCount(
101
+ value: number,
102
+ path: string[],
103
+ ): ValidationErrorData<DecimalCountValidationRuleSettings> | null {
104
+ if (this._maxDecimals === undefined) {
105
+ return null;
106
+ }
107
+
108
+ const validationRule = this._validationRule ?? {
109
+ ruleType: RuleType.DECIMAL_COUNT,
110
+ settings: {
111
+ max: this._maxDecimals,
112
+ },
113
+ };
114
+
115
+ return validateWithFunction<DecimalCountValidationRuleSettings, number>(
116
+ value,
117
+ validationRule as RuleInstance<DecimalCountValidationRuleSettings>,
118
+ path,
119
+ this._message ?? "Number has too many decimal places",
120
+ validateValueDecimalCount,
121
+ );
122
+ }
123
+ }
@@ -0,0 +1,135 @@
1
+ import { PRN } from "@platecms/delta-plate-resource-notation";
2
+ import { RuleType } from "../enums/rule-types.enum";
3
+ import { StringValidationSchema } from "./string.schema";
4
+
5
+ describe("StringValidationSchema", () => {
6
+ let schema: StringValidationSchema;
7
+
8
+ beforeEach(() => {
9
+ schema = new StringValidationSchema();
10
+ });
11
+
12
+ describe("type checking", () => {
13
+ it("passes when value is a string", () => {
14
+ const result = schema.validate("test", { path: ["test"] });
15
+ expect(result.isValid).toBe(true);
16
+ expect(result.errors).toHaveLength(0);
17
+ });
18
+
19
+ it("fails when value is not a string", () => {
20
+ const result = schema.validate(123 as unknown as string, { 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 string");
24
+ expect(result.errors[0].path).toEqual(["test"]);
25
+ });
26
+
27
+ it("passes validation when value is null (allowed by default)", () => {
28
+ const result = schema.validate(null as unknown as string, { path: ["test"] });
29
+ expect(result.isValid).toBe(true);
30
+ expect(result.errors).toHaveLength(0);
31
+ });
32
+
33
+ it("passes validation when value is undefined (allowed by default)", () => {
34
+ const result = schema.validate(undefined as unknown as string, { path: ["test"] });
35
+ expect(result.isValid).toBe(true);
36
+ expect(result.errors).toHaveLength(0);
37
+ });
38
+
39
+ it("fails validation when value is an object", () => {
40
+ const result = schema.validate({ key: "value" } as unknown as string, { path: ["test"] });
41
+ expect(result.isValid).toBe(false);
42
+ expect(result.errors).toHaveLength(1);
43
+ expect(result.errors[0].message).toBe("Value must be a string");
44
+ });
45
+
46
+ it("fails validation when value is an array", () => {
47
+ const result = schema.validate(["test"] as unknown as string, { path: ["test"] });
48
+ expect(result.isValid).toBe(false);
49
+ expect(result.errors).toHaveLength(1);
50
+ expect(result.errors[0].message).toBe("Value must be a string");
51
+ });
52
+ });
53
+
54
+ describe("format validation", () => {
55
+ it("passes validation when string matches email format", () => {
56
+ schema.format(".*@.*\\..*");
57
+ const result = schema.validate("test@example.com", { path: ["test"] });
58
+ expect(result.isValid).toBe(true);
59
+ expect(result.errors).toHaveLength(0);
60
+ });
61
+
62
+ it("fails validation when string does not match email format", () => {
63
+ schema.format(".*@.*\\..*");
64
+ const result = schema.validate("invalid-email", { path: ["test"] });
65
+ expect(result.isValid).toBe(false);
66
+ expect(result.errors).toHaveLength(1);
67
+ expect(result.errors[0].message).toContain("String does not match the required format");
68
+ });
69
+
70
+ it("passes validation when no format is specified", () => {
71
+ const result = schema.validate("any string", { path: ["test"] });
72
+ expect(result.isValid).toBe(true);
73
+ expect(result.errors).toHaveLength(0);
74
+ });
75
+
76
+ it("rejects invalid email formats", () => {
77
+ schema.format(".*@.*\\..*");
78
+
79
+ const invalidEmails = ["no-at-sign", "missing-dot@domain", "just@", "@just"];
80
+
81
+ invalidEmails.forEach((email) => {
82
+ const result = schema.validate(email, { path: ["test"] });
83
+ expect(result.isValid).toBe(false);
84
+ });
85
+ });
86
+
87
+ it("uses validation rule for format validation", () => {
88
+ const mockRule = {
89
+ ruleType: RuleType.STRING_FORMAT,
90
+ settings: { allowedFormat: ".*@.*\\..*" },
91
+ prn: PRN.fromString("prn:plate:-1:cxc:validation-rule:1"),
92
+ };
93
+
94
+ const schemaWithRule = new StringValidationSchema(mockRule);
95
+ const result = schemaWithRule.validate("invalid-email", { 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 when string is in allowed values", () => {
103
+ schema.allowedValues(["option1", "option2", "option3"]);
104
+ const result = schema.validate("option2", { path: ["test"] });
105
+ expect(result.isValid).toBe(true);
106
+ expect(result.errors).toHaveLength(0);
107
+ });
108
+
109
+ it("fails when string is not in allowed values", () => {
110
+ schema.allowedValues(["option1", "option2", "option3"]);
111
+ const result = schema.validate("invalid", { path: ["test"] });
112
+ expect(result.isValid).toBe(false);
113
+ expect(result.errors).toHaveLength(1);
114
+ expect(result.errors[0].message).toBe("The provided value must be one of option1, option2, option3");
115
+ });
116
+
117
+ it("passes when no allowed values are set", () => {
118
+ const result = schema.validate("any string", { path: ["test"] });
119
+ expect(result.isValid).toBe(true);
120
+ expect(result.errors).toHaveLength(0);
121
+ });
122
+ });
123
+
124
+ describe("method chaining", () => {
125
+ it("supports method chaining", () => {
126
+ const result = schema
127
+ .format(".*@.*\\..*")
128
+ .allowedValues(["test@example.com"])
129
+ .message("Custom string message")
130
+ .validate("test@example.com", { path: ["test"] });
131
+
132
+ expect(result.isValid).toBe(true);
133
+ });
134
+ });
135
+ });
@@ -0,0 +1,73 @@
1
+ import { ContentValueTypeNames } 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 {
8
+ StringFormatValidationRuleSettings,
9
+ validateValueStringFormat,
10
+ } from "../validation-rules/string-format.validation-rule";
11
+ import { BaseValidationSchema } from "./base.schema";
12
+
13
+ export class StringValidationSchema extends BaseValidationSchema<string, StringValidationSchema> {
14
+ public readonly type = "string";
15
+ private _format?: string;
16
+
17
+ public format(fmt: string): StringValidationSchema {
18
+ this._format = fmt;
19
+ return this;
20
+ }
21
+
22
+ protected _typeCheck(value: unknown, path: string[]): ValidationErrorData[] {
23
+ if (typeof value !== "string") {
24
+ return [
25
+ {
26
+ path,
27
+ message: this._message ?? "Value must be a string",
28
+ provided: value,
29
+ validationRule: {
30
+ ruleType: RuleType.VALUE_TYPE,
31
+ settings: { allowedTypes: [ContentValueTypeNames.STRING] },
32
+ },
33
+ },
34
+ ];
35
+ }
36
+ return [];
37
+ }
38
+
39
+ protected _validateAgainstSchema(value: string, context: ValidationContext = {}): ValidationErrorData[] {
40
+ const errors: ValidationErrorData[] = [];
41
+
42
+ const stringFormatError = this._validateStringFormat(value, context.path ?? []);
43
+ if (stringFormatError) {
44
+ errors.push(stringFormatError);
45
+ }
46
+
47
+ return errors;
48
+ }
49
+
50
+ private _validateStringFormat(
51
+ value: string,
52
+ path: string[],
53
+ ): ValidationErrorData<StringFormatValidationRuleSettings> | null {
54
+ if (this._format === undefined && this._validationRule === undefined) {
55
+ return null;
56
+ }
57
+
58
+ const validationRule = this._validationRule ?? {
59
+ ruleType: RuleType.STRING_FORMAT,
60
+ settings: {
61
+ allowedFormat: this._format!,
62
+ },
63
+ };
64
+
65
+ return validateWithFunction<StringFormatValidationRuleSettings, string>(
66
+ value,
67
+ validationRule as RuleInstance<StringFormatValidationRuleSettings>,
68
+ path,
69
+ this._message ?? "String format validation failed",
70
+ validateValueStringFormat,
71
+ );
72
+ }
73
+ }
@@ -0,0 +1 @@
1
+ import "./validation-result-matcher";