@platecms/delta-validation 1.2.0 → 1.4.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 (29) 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/schema-builder.ts +5 -0
  9. package/src/lib/schemas/content-value.schema.ts +27 -2
  10. package/src/lib/schemas/object.schema.spec.ts +223 -0
  11. package/src/lib/schemas/object.schema.ts +106 -0
  12. package/src/lib/types/validation-schema.interface.ts +1 -1
  13. package/src/lib/validation-rules/allowed-values.validation-rule.spec.ts +25 -0
  14. package/src/lib/validation-rules/allowed-values.validation-rule.ts +15 -0
  15. package/src/lib/validation-rules/base.validation-rule.ts +21 -4
  16. package/src/lib/validation-rules/count.validation-rule.spec.ts +38 -0
  17. package/src/lib/validation-rules/count.validation-rule.ts +35 -0
  18. package/src/lib/validation-rules/date-between.validation-rule.spec.ts +83 -0
  19. package/src/lib/validation-rules/date-between.validation-rule.ts +15 -0
  20. package/src/lib/validation-rules/decimal-count.validation-rule.spec.ts +26 -0
  21. package/src/lib/validation-rules/decimal-count.validation-rule.ts +12 -0
  22. package/src/lib/validation-rules/number-between.validation-rule.spec.ts +44 -0
  23. package/src/lib/validation-rules/number-between.validation-rule.ts +14 -0
  24. package/src/lib/validation-rules/relatable-content-types.validation-rule.spec.ts +33 -8
  25. package/src/lib/validation-rules/relatable-content-types.validation-rule.ts +17 -0
  26. package/src/lib/validation-rules/string-format.validation-rule.spec.ts +47 -21
  27. package/src/lib/validation-rules/string-format.validation-rule.ts +14 -0
  28. package/src/lib/validation-rules/value-type.validation-rule.spec.ts +104 -74
  29. package/src/lib/validation-rules/value-type.validation-rule.ts +17 -2
@@ -1,5 +1,6 @@
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
6
  const rulePrn = PRN.fromString("prn:plate:-1:cms:validation-rule:decimal-count");
@@ -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,6 +1,7 @@
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
7
  const rulePrn = PRN.fromString("prn:plate:-1:cms:validation-rule:1");
@@ -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,6 +1,7 @@
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
7
  const rulePrn = PRN.fromString("prn:plate:-1:cms:validation-rule:1");
@@ -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
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 {
@@ -1,108 +1,138 @@
1
1
  import { PRN } from "@platecms/delta-plate-resource-notation";
2
- import { ContentValueType, ContentValueTypeNames } from "@platecms/delta-types";
2
+ import { ContentValueType, ContentValueTypeName, ContentValueTypeNames } from "@platecms/delta-types";
3
+ import { ValidationRuleUpdateImpact } from "../enums/validation-rule-update-impact.enum";
3
4
  import { ValueTypeValidationRule, ValueTypeValidationRuleSettings } from "./value-type.validation-rule";
4
5
 
5
6
  describe("ValueTypeValidationRule", () => {
6
7
  const rulePrn = PRN.fromString("prn:plate:-1:cms:validation-rule:value-type");
7
8
 
8
- describe.each([
9
- [
10
- "with allowed string type",
11
- { allowedTypes: [ContentValueTypeNames.STRING] },
9
+ describe("validate", () => {
10
+ describe.each([
12
11
  [
13
- ["foo", true],
14
- [123, false],
12
+ "with allowed string type",
13
+ { allowedTypes: [ContentValueTypeNames.STRING] },
14
+ [
15
+ ["foo", true],
16
+ [123, false],
17
+ ],
15
18
  ],
16
- ],
17
- [
18
- "with allowed number type",
19
- { allowedTypes: [ContentValueTypeNames.NUMBER] },
20
19
  [
21
- [123, true],
22
- ["foo", false],
20
+ "with allowed number type",
21
+ { allowedTypes: [ContentValueTypeNames.NUMBER] },
22
+ [
23
+ [123, true],
24
+ ["foo", false],
25
+ ],
23
26
  ],
24
- ],
25
- [
26
- "with allowed boolean type",
27
- { allowedTypes: [ContentValueTypeNames.BOOLEAN] },
28
27
  [
29
- [true, true],
30
- ["foo", false],
28
+ "with allowed boolean type",
29
+ { allowedTypes: [ContentValueTypeNames.BOOLEAN] },
30
+ [
31
+ [true, true],
32
+ ["foo", false],
33
+ ],
31
34
  ],
32
- ],
33
- [
34
- "with allowed date type",
35
- { allowedTypes: [ContentValueTypeNames.DATE] },
36
35
  [
37
- [new Date(), true],
38
- ["foo", false],
36
+ "with allowed date type",
37
+ { allowedTypes: [ContentValueTypeNames.DATE] },
38
+ [
39
+ [new Date(), true],
40
+ ["foo", false],
41
+ ],
39
42
  ],
40
- ],
41
- [
42
- "with allowed content item type",
43
- { allowedTypes: [ContentValueTypeNames.CONTENT_ITEM] },
44
43
  [
45
- [PRN.fromString("prn:plate:-1:cms:content-item:1"), true],
46
- ["foo", false],
44
+ "with allowed content item type",
45
+ { allowedTypes: [ContentValueTypeNames.CONTENT_ITEM] },
46
+ [
47
+ [PRN.fromString("prn:plate:-1:cms:content-item:1"), true],
48
+ ["foo", false],
49
+ ],
47
50
  ],
48
- ],
49
- [
50
- "with allowed asset type",
51
- { allowedTypes: [ContentValueTypeNames.ASSET] },
52
51
  [
53
- [PRN.fromString("prn:plate:-1:ps:asset:1"), true],
54
- ["foo", false],
52
+ "with allowed asset type",
53
+ { allowedTypes: [ContentValueTypeNames.ASSET] },
54
+ [
55
+ [PRN.fromString("prn:plate:-1:ps:asset:1"), true],
56
+ ["foo", false],
57
+ ],
55
58
  ],
56
- ],
57
- [
58
- "with allowed path part type",
59
- { allowedTypes: [ContentValueTypeNames.PATH_PART] },
60
59
  [
61
- [PRN.fromString("prn:plate:-1:cms:path-part:1"), true],
62
- ["foo", false],
60
+ "with allowed path part type",
61
+ { allowedTypes: [ContentValueTypeNames.PATH_PART] },
62
+ [
63
+ [PRN.fromString("prn:plate:-1:cms:path-part:1"), true],
64
+ ["foo", false],
65
+ ],
63
66
  ],
64
- ],
65
- [
66
- "with allowed grid placement type",
67
- { allowedTypes: [ContentValueTypeNames.GRID_PLACEMENT] },
68
67
  [
69
- [PRN.fromString("prn:plate:-1:cms:grid-placement:1"), true],
70
- ["foo", false],
68
+ "with allowed grid placement type",
69
+ { allowedTypes: [ContentValueTypeNames.GRID_PLACEMENT] },
70
+ [
71
+ [PRN.fromString("prn:plate:-1:cms:grid-placement:1"), true],
72
+ ["foo", false],
73
+ ],
71
74
  ],
72
- ],
73
- [
74
- "with allowed smart text type",
75
- { allowedTypes: [ContentValueTypeNames.SMART_TEXT] },
76
75
  [
77
- [{ type: "root", children: [] }, true],
78
- [PRN.fromString("prn:plate:-1:cms:grid-placement:1"), false],
76
+ "with allowed smart text type",
77
+ { allowedTypes: [ContentValueTypeNames.SMART_TEXT] },
78
+ [
79
+ [{ type: "root", children: [] }, true],
80
+ [PRN.fromString("prn:plate:-1:cms:grid-placement:1"), false],
81
+ ],
79
82
  ],
80
- ],
81
- [
82
- "with incorrect prn",
83
- { allowedTypes: [ContentValueTypeNames.CONTENT_ITEM] },
84
- [[PRN.fromString("prn:plate:-1:ps:asset:1"), false]],
85
- ],
86
- [
87
- "with multiple types",
88
- {
89
- allowedTypes: [ContentValueTypeNames.STRING, ContentValueTypeNames.CONTENT_ITEM],
90
- },
91
83
  [
92
- ["foo", true],
93
- [PRN.fromString("prn:plate:-1:cms:content-item:1"), true],
94
- [new Date(), false],
84
+ "with incorrect prn",
85
+ { allowedTypes: [ContentValueTypeNames.CONTENT_ITEM] },
86
+ [[PRN.fromString("prn:plate:-1:ps:asset:1"), false]],
95
87
  ],
96
- ],
97
- ])("%s", (_, ruleConfig, testCases: unknown[]) => {
88
+ [
89
+ "with multiple types",
90
+ {
91
+ allowedTypes: [ContentValueTypeNames.STRING, ContentValueTypeNames.CONTENT_ITEM],
92
+ },
93
+ [
94
+ ["foo", true],
95
+ [PRN.fromString("prn:plate:-1:cms:content-item:1"), true],
96
+ [new Date(), false],
97
+ ],
98
+ ],
99
+ ])("%s", (_, ruleConfig, testCases: unknown[]) => {
100
+ let rule: ValueTypeValidationRule;
101
+
102
+ beforeEach(() => {
103
+ rule = new ValueTypeValidationRule(rulePrn, ruleConfig as ValueTypeValidationRuleSettings);
104
+ });
105
+
106
+ it.each(testCases)("returns %s for the provided value", (value, expected) => {
107
+ expect(rule.validate([value as ContentValueType])).toBeContentValidationResult([expected as boolean]);
108
+ });
109
+ });
110
+ });
111
+
112
+ describe("classifyUpdate", () => {
98
113
  let rule: ValueTypeValidationRule;
114
+ const allowedTypes: ContentValueTypeName<ContentValueType>[] = [
115
+ ContentValueTypeNames.STRING,
116
+ ContentValueTypeNames.NUMBER,
117
+ ];
99
118
 
100
119
  beforeEach(() => {
101
- rule = new ValueTypeValidationRule(rulePrn, ruleConfig as ValueTypeValidationRuleSettings);
120
+ rule = new ValueTypeValidationRule(rulePrn, { allowedTypes });
102
121
  });
103
122
 
104
- it.each(testCases)("returns %s for the provided value", (value, expected) => {
105
- expect(rule.validate([value as ContentValueType])).toBeContentValidationResult([expected]);
123
+ it.each([
124
+ [ValidationRuleUpdateImpact.NONE, { allowedTypes: [...allowedTypes, ContentValueTypeNames.BOOLEAN] }], // extending
125
+ [ValidationRuleUpdateImpact.NONE, { allowedTypes: [...allowedTypes] }], // unchanged
126
+ [ValidationRuleUpdateImpact.BREAKS, { allowedTypes: [ContentValueTypeNames.BOOLEAN] }], // tightening
127
+ ])("returns %s when the new allowed types are %s", (expected, newSettings) => {
128
+ expect(rule.classifyUpdate(newSettings as ValueTypeValidationRuleSettings)).toBe(expected);
129
+ });
130
+ });
131
+
132
+ describe("classifyCreate", () => {
133
+ it("returns BREAKS", () => {
134
+ const rule = new ValueTypeValidationRule(rulePrn, { allowedTypes: [ContentValueTypeNames.STRING] });
135
+ expect(rule.classifyCreate()).toBe(ValidationRuleUpdateImpact.BREAKS);
106
136
  });
107
137
  });
108
138
  });
@@ -1,16 +1,18 @@
1
1
  import { InvalidCastError, validateCast } from "@platecms/delta-cast";
2
2
  import { PRN } from "@platecms/delta-plate-resource-notation";
3
3
  import { ContentValueType, ContentValueTypeName, ContentValueTypeNames } from "@platecms/delta-types";
4
+ import { RuleType } from "../enums/rule-types.enum";
5
+ import { ValidationRuleUpdateImpact } from "../enums/validation-rule-update-impact.enum";
4
6
  import { ValidationErrorDetails } from "../errors/validation-error-details";
7
+ import { isSuperset } from "../helpers/is-superset.helper";
5
8
  import { RuleInstance } from "../types/rule-instance.interface";
6
- import { RuleType } from "../enums/rule-types.enum";
7
9
  import { BaseValidationRule, ContentValidationResultType } from "./base.validation-rule";
8
10
  import { validationFunctionFromValidateValue } from "./validation-rule-function.factory";
9
11
 
10
12
  /**
11
13
  * Mapping of PRN resource type to Content Value Type Name.
12
14
  */
13
- const prnResourceTypeToContentValueTypeNameMap: Record<string, ContentValueTypeNames> = {
15
+ const prnResourceTypeToContentValueTypeNameMap: Record<string, ContentValueTypeName<PRN>> = {
14
16
  /* eslint-disable @typescript-eslint/naming-convention */
15
17
  "content-item": ContentValueTypeNames.CONTENT_ITEM,
16
18
  asset: ContentValueTypeNames.ASSET,
@@ -68,6 +70,19 @@ export class ValueTypeValidationRule extends BaseValidationRule<ValueTypeValidat
68
70
  public override validate(values: ContentValueType[]): ContentValidationResultType[] {
69
71
  return validateValueType(values, this);
70
72
  }
73
+
74
+ public override classifyCreate(): ValidationRuleUpdateImpact {
75
+ return ValidationRuleUpdateImpact.BREAKS;
76
+ }
77
+
78
+ public override classifyUpdate(newSettings: ValueTypeValidationRuleSettings): ValidationRuleUpdateImpact {
79
+ const oldAllowedTypes = this.settings.allowedTypes;
80
+ const newAllowedTypes = newSettings.allowedTypes;
81
+
82
+ return isSuperset(newAllowedTypes, oldAllowedTypes)
83
+ ? ValidationRuleUpdateImpact.NONE
84
+ : ValidationRuleUpdateImpact.BREAKS;
85
+ }
71
86
  }
72
87
 
73
88
  // The value type can be one or more of the types specified in the ContentValueType type