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