@platecms/delta-validation 0.13.0 → 1.3.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 (35) hide show
  1. package/package.json +4 -4
  2. package/src/index.ts +8 -7
  3. package/src/lib/enums/validation-rule-update-impact.enum.ts +5 -0
  4. package/src/lib/helpers/fields-to-schema.spec.ts +184 -0
  5. package/src/lib/helpers/fields-to-schema.ts +141 -0
  6. package/src/lib/helpers/get-content-value-type-name.ts +26 -0
  7. package/src/lib/helpers/is-superset.helper.ts +4 -0
  8. package/src/lib/helpers/rule-to-schema.spec.ts +4 -4
  9. package/src/lib/helpers/schema-builder.ts +5 -0
  10. package/src/lib/helpers/validate-with-function.spec.ts +3 -3
  11. package/src/lib/schemas/array.schema.spec.ts +1 -1
  12. package/src/lib/schemas/content-value.schema.spec.ts +5 -5
  13. package/src/lib/schemas/content-value.schema.ts +27 -2
  14. package/src/lib/schemas/date.schema.spec.ts +1 -1
  15. package/src/lib/schemas/object.schema.spec.ts +223 -0
  16. package/src/lib/schemas/object.schema.ts +106 -0
  17. package/src/lib/schemas/string.schema.spec.ts +1 -1
  18. package/src/lib/types/validation-schema.interface.ts +1 -1
  19. package/src/lib/validation-rules/allowed-values.validation-rule.spec.ts +30 -5
  20. package/src/lib/validation-rules/allowed-values.validation-rule.ts +15 -0
  21. package/src/lib/validation-rules/base.validation-rule.ts +21 -4
  22. package/src/lib/validation-rules/count.validation-rule.spec.ts +39 -1
  23. package/src/lib/validation-rules/count.validation-rule.ts +35 -0
  24. package/src/lib/validation-rules/date-between.validation-rule.spec.ts +84 -1
  25. package/src/lib/validation-rules/date-between.validation-rule.ts +19 -4
  26. package/src/lib/validation-rules/decimal-count.validation-rule.spec.ts +27 -1
  27. package/src/lib/validation-rules/decimal-count.validation-rule.ts +12 -0
  28. package/src/lib/validation-rules/number-between.validation-rule.spec.ts +45 -1
  29. package/src/lib/validation-rules/number-between.validation-rule.ts +14 -0
  30. package/src/lib/validation-rules/relatable-content-types.validation-rule.spec.ts +38 -13
  31. package/src/lib/validation-rules/relatable-content-types.validation-rule.ts +17 -0
  32. package/src/lib/validation-rules/string-format.validation-rule.spec.ts +48 -22
  33. package/src/lib/validation-rules/string-format.validation-rule.ts +14 -0
  34. package/src/lib/validation-rules/value-type.validation-rule.spec.ts +105 -75
  35. package/src/lib/validation-rules/value-type.validation-rule.ts +17 -2
@@ -1,9 +1,10 @@
1
1
  import { PRN } from "@platecms/delta-plate-resource-notation";
2
+ import { ValidationRuleUpdateImpact } from "../enums/validation-rule-update-impact.enum";
2
3
  import { InvalidValidationRuleSettingsError } from "../errors/invalid-validation-rule-settings.error";
3
4
  import { DateBetweenValidationRule } from "./date-between.validation-rule";
4
5
 
5
6
  describe("DateBetweenValidationRule", () => {
6
- const rulePrn = PRN.fromString("prn:plate:-1:cxc:validation-rule:1");
7
+ const rulePrn = PRN.fromString("prn:plate:-1:cms:validation-rule:1");
7
8
 
8
9
  describe("constructor", () => {
9
10
  it("throws an error if min and max are not provided", () => {
@@ -74,4 +75,86 @@ describe("DateBetweenValidationRule", () => {
74
75
  });
75
76
  });
76
77
  });
78
+
79
+ describe("classifyUpdate", () => {
80
+ // eslint-disable-next-line func-style, id-length
81
+ const d = (iso: string): Date => new Date(iso);
82
+
83
+ it.each([
84
+ // OLD: bounded range
85
+ [
86
+ ValidationRuleUpdateImpact.NONE,
87
+ { start: d("2025-01-10"), end: d("2025-01-20") },
88
+ { start: d("2025-01-10"), end: d("2025-01-21") },
89
+ ], // widen end
90
+ [
91
+ ValidationRuleUpdateImpact.NONE,
92
+ { start: d("2025-01-10"), end: d("2025-01-20") },
93
+ { start: d("2025-01-09"), end: d("2025-01-20") },
94
+ ], // widen start (earlier)
95
+ [
96
+ ValidationRuleUpdateImpact.NONE,
97
+ { start: d("2025-01-10"), end: d("2025-01-20") },
98
+ { start: d("2025-01-09"), end: d("2025-01-21") },
99
+ ], // widen both
100
+
101
+ [ValidationRuleUpdateImpact.NONE, { start: d("2025-01-10"), end: d("2025-01-20") }, { start: d("2025-01-10") }], // remove end (→ Infinity)
102
+ [ValidationRuleUpdateImpact.NONE, { start: d("2025-01-10"), end: d("2025-01-20") }, { end: d("2025-01-20") }], // remove start (→ -Infinity)
103
+ [ValidationRuleUpdateImpact.NONE, { start: d("2025-01-10"), end: d("2025-01-20") }, {}], // remove both
104
+ [
105
+ ValidationRuleUpdateImpact.INVALIDATES,
106
+ { start: d("2025-01-10"), end: d("2025-01-20") },
107
+ { start: d("2025-01-10"), end: d("2025-01-19") },
108
+ ], // narrow end (earlier)
109
+ [
110
+ ValidationRuleUpdateImpact.INVALIDATES,
111
+ { start: d("2025-01-10"), end: d("2025-01-20") },
112
+ { start: d("2025-01-11"), end: d("2025-01-20") },
113
+ ], // narrow start (later)
114
+ [
115
+ ValidationRuleUpdateImpact.INVALIDATES,
116
+ { start: d("2025-01-10"), end: d("2025-01-20") },
117
+ { start: d("2025-01-11"), end: d("2025-01-19") },
118
+ ], // narrow both
119
+ // unchanged
120
+ [
121
+ ValidationRuleUpdateImpact.NONE,
122
+ { start: d("2025-01-10"), end: d("2025-01-20") },
123
+ { start: d("2025-01-10"), end: d("2025-01-20") },
124
+ ], // unchanged
125
+
126
+ // OLD: start-only (end = Infinity)
127
+ [ValidationRuleUpdateImpact.NONE, { start: d("2025-01-10") }, { start: d("2025-01-09") }], // widen start (earlier)
128
+ [ValidationRuleUpdateImpact.INVALIDATES, { start: d("2025-01-10") }, { start: d("2025-01-11") }], // narrow start (later)
129
+ [
130
+ ValidationRuleUpdateImpact.INVALIDATES,
131
+ { start: d("2025-01-10") },
132
+ { start: d("2025-01-10"), end: d("2025-01-20") },
133
+ ], // introduce end (tightening)
134
+ [ValidationRuleUpdateImpact.NONE, { start: d("2025-01-10") }, {}], // remove start (→ -Infinity)
135
+
136
+ // OLD: end-only (start = -Infinity)
137
+ [ValidationRuleUpdateImpact.NONE, { end: d("2025-01-20") }, { end: d("2025-01-21") }], // widen end (later)
138
+ [ValidationRuleUpdateImpact.INVALIDATES, { end: d("2025-01-20") }, { end: d("2025-01-19") }], // narrow end (earlier)
139
+ [
140
+ ValidationRuleUpdateImpact.INVALIDATES,
141
+ { end: d("2025-01-20") },
142
+ { start: d("2025-01-10"), end: d("2025-01-20") },
143
+ ], // introduce start (tightening)
144
+ [ValidationRuleUpdateImpact.NONE, { end: d("2025-01-20") }, {}], // remove end (→ Infinity)
145
+ ])("returns %s when old settings are %s and new settings are %s", (expected, oldSettings, newSettings) => {
146
+ const rule = new DateBetweenValidationRule(rulePrn, oldSettings);
147
+ expect(rule.classifyUpdate(newSettings)).toBe(expected);
148
+ });
149
+ });
150
+
151
+ describe("classifyCreate", () => {
152
+ it("returns INVALIDATES", () => {
153
+ const rule = new DateBetweenValidationRule(rulePrn, {
154
+ start: new Date(2020, 1, 2, 3, 4, 5),
155
+ end: new Date(2020, 1, 3, 3, 4, 6),
156
+ });
157
+ expect(rule.classifyCreate()).toBe(ValidationRuleUpdateImpact.INVALIDATES);
158
+ });
159
+ });
77
160
  });
@@ -6,6 +6,7 @@ import { RuleInstance } from "../types/rule-instance.interface";
6
6
  import { RuleType } from "../enums/rule-types.enum";
7
7
  import { BaseValidationRule, ContentValidationResultType } from "./base.validation-rule";
8
8
  import { validationFunctionFromValidateValue } from "./validation-rule-function.factory";
9
+ import { ValidationRuleUpdateImpact } from "../enums/validation-rule-update-impact.enum";
9
10
 
10
11
  export function validateValueDateBetween(
11
12
  value: ContentValueType,
@@ -15,15 +16,15 @@ export function validateValueDateBetween(
15
16
  return null;
16
17
  }
17
18
 
18
- if (ruleInstance.settings.start != null && value < ruleInstance.settings.start) {
19
+ if (ruleInstance.settings.start != null && value < new Date(ruleInstance.settings.start as unknown as Date)) {
19
20
  return new ValidationErrorDetails(
20
- `The date must be greater than or equal ${ruleInstance.settings.start.toDateString()}`,
21
+ `The date must be greater than or equal ${new Date(ruleInstance.settings.start).toDateString()}`,
21
22
  ruleInstance,
22
23
  );
23
24
  }
24
- if (ruleInstance.settings.end != null && value > ruleInstance.settings.end) {
25
+ if (ruleInstance.settings.end != null && value > new Date(ruleInstance.settings.end)) {
25
26
  return new ValidationErrorDetails(
26
- `The date must be lower than or equal ${ruleInstance.settings.end.toDateString()}`,
27
+ `The date must be lower than or equal ${new Date(ruleInstance.settings.end).toDateString()}`,
27
28
  ruleInstance,
28
29
  );
29
30
  }
@@ -46,6 +47,20 @@ export class DateBetweenValidationRule extends BaseValidationRule<DateBetweenVal
46
47
  public override validate(values: ContentValueType[]): ContentValidationResultType[] {
47
48
  return validateDateBetween(values, this);
48
49
  }
50
+
51
+ public override classifyCreate(): ValidationRuleUpdateImpact {
52
+ return ValidationRuleUpdateImpact.INVALIDATES;
53
+ }
54
+
55
+ public override classifyUpdate(newSettings: DateBetweenValidationRuleSettings): ValidationRuleUpdateImpact {
56
+ const oldStart = this.settings.start ? this.settings.start.getTime() : -Infinity;
57
+ const oldEnd = this.settings.end ? this.settings.end.getTime() : Infinity;
58
+ const newStart = newSettings.start ? newSettings.start.getTime() : -Infinity;
59
+ const newEnd = newSettings.end ? newSettings.end.getTime() : Infinity;
60
+ const extending = newStart <= oldStart && newEnd >= oldEnd;
61
+
62
+ return extending ? ValidationRuleUpdateImpact.NONE : ValidationRuleUpdateImpact.INVALIDATES;
63
+ }
49
64
  }
50
65
 
51
66
  export interface DateBetweenValidationRuleSettings {
@@ -1,8 +1,9 @@
1
1
  import { PRN } from "@platecms/delta-plate-resource-notation";
2
2
  import { DecimalCountValidationRule } from "./decimal-count.validation-rule";
3
+ import { ValidationRuleUpdateImpact } from "../enums/validation-rule-update-impact.enum";
3
4
 
4
5
  describe("DecimalCountValidationRule", () => {
5
- const rulePrn = PRN.fromString("prn:plate:-1:cxc:validation-rule:decimal-count");
6
+ const rulePrn = PRN.fromString("prn:plate:-1:cms:validation-rule:decimal-count");
6
7
 
7
8
  describe("validate", () => {
8
9
  describe("with max of 0", () => {
@@ -43,4 +44,29 @@ describe("DecimalCountValidationRule", () => {
43
44
  });
44
45
  });
45
46
  });
47
+
48
+ describe("classifyUpdate", () => {
49
+ let rule: DecimalCountValidationRule;
50
+
51
+ beforeEach(() => {
52
+ rule = new DecimalCountValidationRule(rulePrn, {
53
+ max: 4,
54
+ });
55
+ });
56
+
57
+ it.each([
58
+ [ValidationRuleUpdateImpact.NONE, { max: 5 }], // increase max
59
+ [ValidationRuleUpdateImpact.NONE, { max: 4 }], // unchanged
60
+ [ValidationRuleUpdateImpact.INVALIDATES, { max: 3 }], // tighten
61
+ ])("returns %s when the new max is %s", (expected, newSettings) => {
62
+ expect(rule.classifyUpdate(newSettings)).toBe(expected);
63
+ });
64
+ });
65
+
66
+ describe("classifyCreate", () => {
67
+ it("returns INVALIDATES", () => {
68
+ const rule = new DecimalCountValidationRule(rulePrn, { max: 0 });
69
+ expect(rule.classifyCreate()).toBe(ValidationRuleUpdateImpact.INVALIDATES);
70
+ });
71
+ });
46
72
  });
@@ -4,6 +4,7 @@ import { RuleInstance } from "../types/rule-instance.interface";
4
4
  import { RuleType } from "../enums/rule-types.enum";
5
5
  import { BaseValidationRule, ContentValidationResultType } from "./base.validation-rule";
6
6
  import { validationFunctionFromValidateValue } from "./validation-rule-function.factory";
7
+ import { ValidationRuleUpdateImpact } from "../enums/validation-rule-update-impact.enum";
7
8
 
8
9
  function getDecimalCount(value: number): number {
9
10
  const [_, decimalDigits] = value.toString().split(".");
@@ -45,6 +46,17 @@ export class DecimalCountValidationRule extends BaseValidationRule<DecimalCountV
45
46
  public override validate(values: ContentValueType[]): ContentValidationResultType[] {
46
47
  return validateDecimalCount(values, this);
47
48
  }
49
+
50
+ public override classifyCreate(): ValidationRuleUpdateImpact {
51
+ return ValidationRuleUpdateImpact.INVALIDATES;
52
+ }
53
+
54
+ public override classifyUpdate(newSettings: DecimalCountValidationRuleSettings): ValidationRuleUpdateImpact {
55
+ const oldMax = this.settings.max;
56
+ const newMax = newSettings.max;
57
+
58
+ return newMax >= oldMax ? ValidationRuleUpdateImpact.NONE : ValidationRuleUpdateImpact.INVALIDATES;
59
+ }
48
60
  }
49
61
 
50
62
  export interface DecimalCountValidationRuleSettings {
@@ -1,9 +1,10 @@
1
1
  import { PRN } from "@platecms/delta-plate-resource-notation";
2
2
  import { InvalidValidationRuleSettingsError } from "../errors/invalid-validation-rule-settings.error";
3
3
  import { NumberBetweenValidationRule } from "./number-between.validation-rule";
4
+ import { ValidationRuleUpdateImpact } from "../enums/validation-rule-update-impact.enum";
4
5
 
5
6
  describe("NumberBetweenValidationRule", () => {
6
- const rulePrn = PRN.fromString("prn:plate:-1:cxc:validation-rule:1");
7
+ const rulePrn = PRN.fromString("prn:plate:-1:cms:validation-rule:1");
7
8
 
8
9
  describe("constructor", () => {
9
10
  it("throws an error if min and max are not provided", () => {
@@ -74,4 +75,47 @@ describe("NumberBetweenValidationRule", () => {
74
75
  });
75
76
  });
76
77
  });
78
+
79
+ describe("classifyUpdate", () => {
80
+ it.each([
81
+ // OLD: { min: 1, max: 5 }
82
+ [ValidationRuleUpdateImpact.NONE, { min: 1, max: 5 }, { min: 1, max: 6 }], // widen upper bound
83
+ [ValidationRuleUpdateImpact.NONE, { min: 1, max: 5 }, { min: 0, max: 5 }], // widen lower bound
84
+ [ValidationRuleUpdateImpact.NONE, { min: 1, max: 5 }, { min: 0, max: 6 }], // widen both bounds
85
+ [ValidationRuleUpdateImpact.NONE, { min: 1, max: 5 }, { min: 1 }], // remove upper bound (→ Infinity)
86
+ [ValidationRuleUpdateImpact.NONE, { min: 1, max: 5 }, { max: 5 }], // remove lower bound (→ -Infinity)
87
+ [ValidationRuleUpdateImpact.INVALIDATES, { min: 1, max: 5 }, { min: 0, max: 4 }], // narrow upper bound
88
+ [ValidationRuleUpdateImpact.INVALIDATES, { min: 1, max: 5 }, { min: 2, max: 5 }], // narrow lower bound
89
+ [ValidationRuleUpdateImpact.INVALIDATES, { min: 1, max: 5 }, { min: 2, max: 4 }], // narrow both bounds
90
+ [ValidationRuleUpdateImpact.NONE, { min: 1, max: 5 }, { min: 1, max: 5 }], // unchanged
91
+
92
+ // OLD: { min: 1 } (max = Infinity)
93
+ [ValidationRuleUpdateImpact.NONE, { min: 1 }, { min: 0 }], // widen lower bound
94
+ [ValidationRuleUpdateImpact.INVALIDATES, { min: 1 }, { min: 2 }], // narrow lower bound
95
+ [ValidationRuleUpdateImpact.INVALIDATES, { min: 1 }, { min: 1, max: 5 }], // introduce upper bound (tightening)
96
+ [ValidationRuleUpdateImpact.NONE, { min: 1 }, {}], // remove lower bound (→ -Infinity)
97
+
98
+ // OLD: { max: 5 } (min = -Infinity)
99
+ [ValidationRuleUpdateImpact.NONE, { max: 5 }, { max: 6 }], // widen upper bound
100
+ [ValidationRuleUpdateImpact.INVALIDATES, { max: 5 }, { max: 4 }], // narrow upper bound
101
+ [ValidationRuleUpdateImpact.INVALIDATES, { max: 5 }, { min: 1, max: 5 }], // introduce lower bound (tightening)
102
+ [ValidationRuleUpdateImpact.NONE, { max: 5 }, {}], // remove upper bound (→ Infinity)
103
+
104
+ // OLD: spans negatives
105
+ [ValidationRuleUpdateImpact.NONE, { min: -5, max: 5 }, { min: -6, max: 5 }], // widen lower bound into negatives
106
+ [ValidationRuleUpdateImpact.NONE, { min: -5, max: 5 }, { min: -5, max: 6 }], // widen upper bound
107
+ [ValidationRuleUpdateImpact.INVALIDATES, { min: -5, max: 5 }, { min: -4, max: 5 }], // tighten lower bound
108
+ [ValidationRuleUpdateImpact.INVALIDATES, { min: -5, max: 5 }, { min: -5, max: 4 }], // tighten upper bound
109
+ ])("returns %s when old settings are %s and new settings are %s", (expected, oldSettings, newSettings) => {
110
+ const rule = new NumberBetweenValidationRule(rulePrn, oldSettings);
111
+ expect(rule.classifyUpdate(newSettings)).toBe(expected);
112
+ });
113
+ });
114
+
115
+ describe("classifyCreate", () => {
116
+ it("returns INVALIDATES", () => {
117
+ const rule = new NumberBetweenValidationRule(rulePrn, { min: 1, max: 2 });
118
+ expect(rule.classifyCreate()).toBe(ValidationRuleUpdateImpact.INVALIDATES);
119
+ });
120
+ });
77
121
  });
@@ -6,6 +6,7 @@ import { RuleInstance } from "../types/rule-instance.interface";
6
6
  import { RuleType } from "../enums/rule-types.enum";
7
7
  import { BaseValidationRule, ContentValidationResultType } from "./base.validation-rule";
8
8
  import { validationFunctionFromValidateValue } from "./validation-rule-function.factory";
9
+ import { ValidationRuleUpdateImpact } from "../enums/validation-rule-update-impact.enum";
9
10
 
10
11
  export function validateValueNumberBetween(
11
12
  value: ContentValueType,
@@ -50,6 +51,19 @@ export class NumberBetweenValidationRule extends BaseValidationRule<NumberBetwee
50
51
  public override validate(values: ContentValueType[]): ContentValidationResultType[] {
51
52
  return validateNumberBetween(values, this);
52
53
  }
54
+
55
+ public override classifyCreate(): ValidationRuleUpdateImpact {
56
+ return ValidationRuleUpdateImpact.INVALIDATES;
57
+ }
58
+
59
+ public override classifyUpdate(newSettings: NumberBetweenValidationRuleSettings): ValidationRuleUpdateImpact {
60
+ const oldSettings = this.settings;
61
+ const [oldMin, oldMax] = [oldSettings.min ?? 0, oldSettings.max ?? Infinity];
62
+ const [newMin, newMax] = [newSettings.min ?? 0, newSettings.max ?? Infinity];
63
+ const isExtending = newMin <= oldMin && newMax >= oldMax;
64
+
65
+ return isExtending ? ValidationRuleUpdateImpact.NONE : ValidationRuleUpdateImpact.INVALIDATES;
66
+ }
53
67
  }
54
68
 
55
69
  export interface NumberBetweenValidationRuleSettings {
@@ -1,17 +1,18 @@
1
1
  import { PRN } from "@platecms/delta-plate-resource-notation";
2
2
  import { InvalidValidationArgumentsError } from "../errors/invalid-validation-arguments.error";
3
3
  import { RelatableContentTypesValidationRule } from "./relatable-content-types.validation-rule";
4
+ import { ValidationRuleUpdateImpact } from "../enums/validation-rule-update-impact.enum";
4
5
 
5
6
  describe("RelatableContentTypesValidationRule", () => {
6
- const rulePrn = PRN.fromString("prn:plate:-1:cxc:validation-rule:1");
7
- const contentItemPrn = PRN.fromString("prn:plate:-1:cxc:content-item:1");
7
+ const rulePrn = PRN.fromString("prn:plate:-1:cms:validation-rule:1");
8
+ const contentItemPrn = PRN.fromString("prn:plate:-1:cms:content-item:1");
8
9
 
9
10
  const allowedContentTypes = [
10
- PRN.fromString("prn:plate:-1:cxc:content-type:1"),
11
- PRN.fromString("prn:plate:-1:cxc:content-type:2"),
11
+ PRN.fromString("prn:plate:-1:cms:content-type:1"),
12
+ PRN.fromString("prn:plate:-1:cms:content-type:2"),
12
13
  ];
13
14
 
14
- const disallowedContentType = PRN.fromString("prn:plate:-1:cxc:content-type:3");
15
+ const disallowedContentType = PRN.fromString("prn:plate:-1:cms:content-type:3");
15
16
 
16
17
  let rule: RelatableContentTypesValidationRule;
17
18
 
@@ -21,15 +22,39 @@ describe("RelatableContentTypesValidationRule", () => {
21
22
  });
22
23
  });
23
24
 
24
- it.each([
25
- [contentItemPrn, allowedContentTypes[0], true],
26
- [contentItemPrn, disallowedContentType, false],
27
- ["not a ContentItem", disallowedContentType, false],
28
- ])("returns %s for the provided content item", (contentItem, contentType, expected) => {
29
- expect(rule.validate([contentItem], [contentType])).toBeContentValidationResult([expected]);
25
+ describe("validate", () => {
26
+ it.each([
27
+ [contentItemPrn, allowedContentTypes[0], true],
28
+ [contentItemPrn, disallowedContentType, false],
29
+ ["not a ContentItem", disallowedContentType, false],
30
+ ])("returns %s for the provided content item", (contentItem, contentType, expected) => {
31
+ expect(rule.validate([contentItem], [contentType])).toBeContentValidationResult([expected]);
32
+ });
33
+
34
+ it("throws an error if the value does not have a ContentType", () => {
35
+ expect(() => rule.validate([contentItemPrn])).toThrowError(InvalidValidationArgumentsError);
36
+ });
30
37
  });
31
38
 
32
- it("throws an error if the value does not have a ContentType", () => {
33
- expect(() => rule.validate([contentItemPrn])).toThrowError(InvalidValidationArgumentsError);
39
+ describe("classifyUpdate", () => {
40
+ it.each([
41
+ [
42
+ ValidationRuleUpdateImpact.NONE,
43
+ { allowedContentTypes: [...allowedContentTypes, PRN.fromString("prn:plate:-1:cms:content-type:3")] },
44
+ ], // extending
45
+ [ValidationRuleUpdateImpact.NONE, { allowedContentTypes: [...allowedContentTypes] }], // unchanged
46
+ [
47
+ ValidationRuleUpdateImpact.INVALIDATES,
48
+ { allowedContentTypes: [PRN.fromString("prn:plate:-1:cms:content-type:3")] },
49
+ ], // tightening
50
+ ])("returns %s when the new allowed content types are %s", (expected, newSettings) => {
51
+ expect(rule.classifyUpdate(newSettings)).toBe(expected);
52
+ });
53
+ });
54
+
55
+ describe("classifyCreate", () => {
56
+ it("returns INVALIDATES", () => {
57
+ expect(rule.classifyCreate()).toBe(ValidationRuleUpdateImpact.INVALIDATES);
58
+ });
34
59
  });
35
60
  });
@@ -5,6 +5,8 @@ import { ValidationErrorDetails } from "../errors/validation-error-details";
5
5
  import { RuleInstance } from "../types/rule-instance.interface";
6
6
  import { RuleType } from "../enums/rule-types.enum";
7
7
  import { BaseValidationRule, ContentValidationResultType } from "./base.validation-rule";
8
+ import { ValidationRuleUpdateImpact } from "../enums/validation-rule-update-impact.enum";
9
+ import { isSuperset } from "../helpers/is-superset.helper";
8
10
 
9
11
  function validateValueRelatableContentTypes<TContentType>(
10
12
  value: ContentValueType,
@@ -54,6 +56,21 @@ export class RelatableContentTypesValidationRule extends BaseValidationRule<
54
56
 
55
57
  return validateRelatableContentTypes(values, this, contentTypes);
56
58
  }
59
+
60
+ public override classifyCreate(): ValidationRuleUpdateImpact {
61
+ return ValidationRuleUpdateImpact.INVALIDATES;
62
+ }
63
+
64
+ public override classifyUpdate(
65
+ newSettings: RelatableContentTypesValidationRuleSettings<PRN>,
66
+ ): ValidationRuleUpdateImpact {
67
+ const oldAllowedContentTypes = this.settings.allowedContentTypes;
68
+ const newAllowedContentTypes = newSettings.allowedContentTypes;
69
+
70
+ return isSuperset(newAllowedContentTypes, oldAllowedContentTypes)
71
+ ? ValidationRuleUpdateImpact.NONE
72
+ : ValidationRuleUpdateImpact.INVALIDATES;
73
+ }
57
74
  }
58
75
 
59
76
  export interface RelatableContentTypesValidationRuleSettings<TContentType> {
@@ -1,43 +1,69 @@
1
1
  import { PRN } from "@platecms/delta-plate-resource-notation";
2
+ import { ValidationRuleUpdateImpact } from "../enums/validation-rule-update-impact.enum";
2
3
  import { StringFormatValidationRule } from "./string-format.validation-rule";
3
4
 
4
5
  describe("StringFormatValidationRule", () => {
5
- const rulePrn = PRN.fromString("prn:plate:-1:cxc:validation-rule:string-format");
6
+ const rulePrn = PRN.fromString("prn:plate:-1:cms:validation-rule:string-format");
6
7
 
7
- describe("with an email regex", () => {
8
- let rule: StringFormatValidationRule;
8
+ describe("validate", () => {
9
+ describe("with an email regex", () => {
10
+ let rule: StringFormatValidationRule;
9
11
 
10
- beforeEach(() => {
11
- rule = new StringFormatValidationRule(rulePrn, {
12
- allowedFormat: "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$",
12
+ beforeEach(() => {
13
+ rule = new StringFormatValidationRule(rulePrn, {
14
+ allowedFormat: "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$",
15
+ });
16
+ });
17
+
18
+ it.each([
19
+ ["david@getplate.com", true],
20
+ ["david@getplate", false],
21
+ [123, null],
22
+ ])("returns %s for the provided value", (value, expected) => {
23
+ expect(rule.validate([value])).toBeContentValidationResult([expected]);
13
24
  });
14
25
  });
15
26
 
16
- it.each([
17
- ["david@getplate.com", true],
18
- ["david@getplate", false],
19
- [123, null],
20
- ])("returns %s for the provided value", (value, expected) => {
21
- expect(rule.validate([value])).toBeContentValidationResult([expected]);
27
+ describe("with a just letters regex", () => {
28
+ let rule: StringFormatValidationRule;
29
+
30
+ beforeEach(() => {
31
+ rule = new StringFormatValidationRule(rulePrn, {
32
+ allowedFormat: "^[a-zA-Z]+$",
33
+ });
34
+ });
35
+
36
+ it.each([
37
+ ["abc", true],
38
+ ["abc123", false],
39
+ ["", false],
40
+ [123, null],
41
+ ])("returns %s for the provided value", (value, expected) => {
42
+ expect(rule.validate([value])).toBeContentValidationResult([expected]);
43
+ });
22
44
  });
23
45
  });
24
46
 
25
- describe("with a just letters regex", () => {
47
+ describe("classifyUpdate", () => {
26
48
  let rule: StringFormatValidationRule;
49
+ const allowedFormat = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$";
27
50
 
28
51
  beforeEach(() => {
29
- rule = new StringFormatValidationRule(rulePrn, {
30
- allowedFormat: "^[a-zA-Z]+$",
31
- });
52
+ rule = new StringFormatValidationRule(rulePrn, { allowedFormat });
32
53
  });
33
54
 
34
55
  it.each([
35
- ["abc", true],
36
- ["abc123", false],
37
- ["", false],
38
- [123, null],
39
- ])("returns %s for the provided value", (value, expected) => {
40
- expect(rule.validate([value])).toBeContentValidationResult([expected]);
56
+ [ValidationRuleUpdateImpact.NONE, { allowedFormat }], // unchanged
57
+ [ValidationRuleUpdateImpact.INVALIDATES, { allowedFormat: "^[a-zA-Z]+$" }], // changing format
58
+ ])("returns %s when the new allowed format is %s", (expected, newSettings) => {
59
+ expect(rule.classifyUpdate(newSettings)).toBe(expected);
60
+ });
61
+ });
62
+
63
+ describe("classifyCreate", () => {
64
+ it("returns INVALIDATES", () => {
65
+ const rule = new StringFormatValidationRule(rulePrn, { allowedFormat: "^[a-zA-Z]+$" });
66
+ expect(rule.classifyCreate()).toBe(ValidationRuleUpdateImpact.INVALIDATES);
41
67
  });
42
68
  });
43
69
  });
@@ -4,6 +4,7 @@ import { RuleInstance } from "../types/rule-instance.interface";
4
4
  import { RuleType } from "../enums/rule-types.enum";
5
5
  import { BaseValidationRule, ContentValidationResultType } from "./base.validation-rule";
6
6
  import { validationFunctionFromValidateValue } from "./validation-rule-function.factory";
7
+ import { ValidationRuleUpdateImpact } from "../enums/validation-rule-update-impact.enum";
7
8
 
8
9
  export function validateValueStringFormat(
9
10
  value: ContentValueType,
@@ -43,6 +44,19 @@ export class StringFormatValidationRule extends BaseValidationRule<StringFormatV
43
44
  public override validate(values: ContentValueType[]): ContentValidationResultType[] {
44
45
  return validateStringFormat(values, this);
45
46
  }
47
+
48
+ public override classifyCreate(): ValidationRuleUpdateImpact {
49
+ return ValidationRuleUpdateImpact.INVALIDATES;
50
+ }
51
+
52
+ public override classifyUpdate(newSettings: StringFormatValidationRuleSettings): ValidationRuleUpdateImpact {
53
+ const oldAllowedFormat = this.settings.allowedFormat;
54
+ const newAllowedFormat = newSettings.allowedFormat;
55
+
56
+ return oldAllowedFormat === newAllowedFormat
57
+ ? ValidationRuleUpdateImpact.NONE
58
+ : ValidationRuleUpdateImpact.INVALIDATES;
59
+ }
46
60
  }
47
61
 
48
62
  export interface StringFormatValidationRuleSettings {