@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platecms/delta-validation",
3
- "version": "0.13.0",
3
+ "version": "1.3.0",
4
4
  "description": "This package contains the validation system for Plate.",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {
@@ -17,9 +17,9 @@
17
17
  "src/**/*"
18
18
  ],
19
19
  "peerDependencies": {
20
- "@platecms/delta-cast": "0.13.0",
21
- "@platecms/delta-plate-resource-notation": "0.13.0",
22
- "@platecms/delta-types": "0.13.0",
20
+ "@platecms/delta-cast": "1.3.0",
21
+ "@platecms/delta-plate-resource-notation": "1.3.0",
22
+ "@platecms/delta-types": "1.3.0",
23
23
  "class-transformer": "0.5.1",
24
24
  "reflect-metadata": "0.2.2",
25
25
  "tslib": "2.8.1"
package/src/index.ts CHANGED
@@ -1,19 +1,20 @@
1
+ export * from "./lib/enums/rule-types.enum";
2
+ export * from "./lib/enums/validation-rule-update-impact.enum";
1
3
  export * from "./lib/errors/invalid-validation-arguments.error";
2
4
  export * from "./lib/errors/invalid-validation-rule-settings.error";
3
- export * from "./lib/errors/validation.error";
4
5
  export * from "./lib/errors/validation-error-details";
6
+ export * from "./lib/errors/validation.error";
5
7
  export * from "./lib/helpers/deserialize-validation-rule";
6
- export * from "./lib/validation-rules/number-between.validation-rule";
7
- export type * from "./lib/types/existing-content-validation-rules-settings.type";
8
+ export * from "./lib/helpers/fields-to-schema";
8
9
  export * from "./lib/helpers/rule-to-schema";
9
10
  export * from "./lib/helpers/schema-builder";
11
+ export type * from "./lib/types/existing-content-validation-rules-settings.type";
12
+ export type * from "./lib/types/existing-content-validation-rules.type";
10
13
  export type * from "./lib/types/existing-validation-schemas.type";
14
+ export type * from "./lib/types/rule-instance.interface";
15
+ export type * from "./lib/types/validation-context.interface";
11
16
  export type * from "./lib/types/validation-error-data.interface";
12
17
  export type * from "./lib/types/validation-result.interface";
13
- export type * from "./lib/types/validation-context.interface";
14
- export type * from "./lib/types/rule-instance.interface";
15
- export * from "./lib/enums/rule-types.enum";
16
- export type * from "./lib/types/existing-content-validation-rules.type";
17
18
  export * from "./lib/validation-rules/allowed-values.validation-rule";
18
19
  export * from "./lib/validation-rules/base.validation-rule";
19
20
  export * from "./lib/validation-rules/count.validation-rule";
@@ -0,0 +1,5 @@
1
+ export enum ValidationRuleUpdateImpact {
2
+ NONE = "NONE",
3
+ BREAKS = "BREAKS",
4
+ INVALIDATES = "INVALIDATES",
5
+ }
@@ -0,0 +1,184 @@
1
+ import { PRN } from "@platecms/delta-plate-resource-notation";
2
+ import { ContentValueTypeNames } from "@platecms/delta-types";
3
+ import { RuleType } from "../enums/rule-types.enum";
4
+ import { ObjectValidationSchema } from "../schemas/object.schema";
5
+ import { FieldToValidate, fieldsToSchema } from "./fields-to-schema";
6
+ import { RuleInstance } from "../types/rule-instance.interface";
7
+ import { ValidationResult } from "../types/validation-result.interface";
8
+
9
+ describe("fieldsToSchema", () => {
10
+ const mockPrn = PRN.fromString("prn:plate:-1:cms:validation-rule:1");
11
+
12
+ function mkField(slug: string, rules: RuleInstance<unknown>[] = []): FieldToValidate {
13
+ return {
14
+ slug,
15
+ contentValidationRules: rules.map((rule) => ({ ...rule, prn: rule.prn ?? mockPrn })),
16
+ };
17
+ }
18
+
19
+ function validateOk(schema: ObjectValidationSchema, value: unknown): ValidationResult {
20
+ const res = schema.validate(value, { path: [] });
21
+ expect(res.isValid).toBe(true);
22
+ expect(res.errors?.length ?? 0).toBe(0);
23
+ return res;
24
+ }
25
+
26
+ function validateFail(schema: ObjectValidationSchema, value: unknown): ValidationResult {
27
+ const res = schema.validate(value, { path: [] });
28
+ expect(res.isValid).toBe(false);
29
+ expect(res.errors?.length).toBeGreaterThan(0);
30
+ return res;
31
+ }
32
+
33
+ function expectError(res: ValidationResult, opts: { pathEndsWith?: (number | string)[]; ruleType?: RuleType }): void {
34
+ const err = res.errors[0];
35
+ if (opts.pathEndsWith) {
36
+ expect(err.path.slice(-opts.pathEndsWith.length)).toEqual(opts.pathEndsWith);
37
+ }
38
+ if (opts.ruleType !== undefined) {
39
+ expect(err.validationRule?.ruleType).toBe(opts.ruleType);
40
+ }
41
+ }
42
+
43
+ it("returns ObjectValidationSchema with type object", () => {
44
+ const schema = fieldsToSchema([]);
45
+ expect(schema).toBeInstanceOf(ObjectValidationSchema);
46
+ expect(schema.type).toBe("object");
47
+ });
48
+
49
+ it("accepts empty object when contentFields is empty", () => {
50
+ validateOk(fieldsToSchema([]), {});
51
+ });
52
+
53
+ it("treats missing field as empty array (if this is your intended contract)", () => {
54
+ // If your contract is different, flip this test accordingly.
55
+ const schema = fieldsToSchema([mkField("items", [])]);
56
+ validateOk(schema, {}); // missing key
57
+ validateOk(schema, { items: [] }); // explicit
58
+ });
59
+
60
+ it("rejects non-array field values", () => {
61
+ const schema = fieldsToSchema([mkField("items", [])]);
62
+ const res = validateFail(schema, { items: "nope" });
63
+ expectError(res, { pathEndsWith: ["items"] });
64
+ });
65
+
66
+ describe("COUNT", () => {
67
+ const schema = fieldsToSchema([mkField("items", [{ ruleType: RuleType.COUNT, settings: { min: 1, max: 2 } }])]);
68
+
69
+ test.each([
70
+ [{ items: [] }, false],
71
+ [{ items: [1] }, true],
72
+ [{ items: [1, 2] }, true],
73
+ [{ items: [1, 2, 3] }, false],
74
+ ])("COUNT %o => %s", (input, ok) => {
75
+ const res = schema.validate(input, { path: [] });
76
+ expect(res.isValid).toBe(ok);
77
+ if (!ok) {
78
+ expectError(res, { pathEndsWith: ["items"], ruleType: RuleType.COUNT });
79
+ }
80
+ });
81
+ });
82
+
83
+ describe("ALLOWED_VALUES", () => {
84
+ const schema = fieldsToSchema([
85
+ mkField("choice", [{ ruleType: RuleType.ALLOWED_VALUES, settings: { allowedValues: ["a", "b"] } }]),
86
+ ]);
87
+
88
+ it("accepts allowed and rejects not allowed", () => {
89
+ validateOk(schema, { choice: ["a"] });
90
+
91
+ const res = validateFail(schema, { choice: ["x"] });
92
+ expectError(res, { pathEndsWith: ["choice", "0"], ruleType: RuleType.ALLOWED_VALUES });
93
+ });
94
+ });
95
+
96
+ describe("VALUE_TYPE", () => {
97
+ const schema = fieldsToSchema([
98
+ mkField("vals", [
99
+ {
100
+ ruleType: RuleType.VALUE_TYPE,
101
+ settings: { allowedTypes: [ContentValueTypeNames.STRING, ContentValueTypeNames.NUMBER] },
102
+ },
103
+ ]),
104
+ ]);
105
+
106
+ it("accepts allowed and rejects disallowed", () => {
107
+ validateOk(schema, { vals: ["ok", 1] });
108
+
109
+ const res = validateFail(schema, { vals: [true] });
110
+ // Depending on your validator, the error could be on vals[0] or vals.
111
+ expect(res.errors[0].validationRule?.ruleType).toBe(RuleType.VALUE_TYPE);
112
+ });
113
+ });
114
+
115
+ describe("STRING_FORMAT", () => {
116
+ const schema = fieldsToSchema([
117
+ mkField("emails", [{ ruleType: RuleType.STRING_FORMAT, settings: { allowedFormat: ".*@.*\\..*" } }]),
118
+ ]);
119
+
120
+ it("accepts matching and rejects non-matching", () => {
121
+ validateOk(schema, { emails: ["a@b.co"] });
122
+
123
+ const res = validateFail(schema, { emails: ["invalid"] });
124
+ expectError(res, { pathEndsWith: ["emails", "0"], ruleType: RuleType.STRING_FORMAT });
125
+ });
126
+ });
127
+
128
+ describe("NUMBER_BETWEEN", () => {
129
+ const schema = fieldsToSchema([
130
+ mkField("scores", [{ ruleType: RuleType.NUMBER_BETWEEN, settings: { min: 0, max: 100 } }]),
131
+ ]);
132
+
133
+ test.each([
134
+ [{ scores: [50] }, true],
135
+ [{ scores: [-1] }, false],
136
+ [{ scores: [101] }, false],
137
+ ])("NUMBER_BETWEEN %o => %s", (input, ok) => {
138
+ const res = schema.validate(input, { path: [] });
139
+ expect(res.isValid).toBe(ok);
140
+ if (!ok) {
141
+ expectError(res, { ruleType: RuleType.NUMBER_BETWEEN });
142
+ }
143
+ });
144
+ });
145
+
146
+ describe("DECIMAL_COUNT", () => {
147
+ const schema = fieldsToSchema([mkField("amounts", [{ ruleType: RuleType.DECIMAL_COUNT, settings: { max: 2 } }])]);
148
+
149
+ it("accepts within max decimals and rejects above", () => {
150
+ validateOk(schema, { amounts: [3.14] });
151
+
152
+ const res = validateFail(schema, { amounts: [3.141] });
153
+ expectError(res, { ruleType: RuleType.DECIMAL_COUNT });
154
+ });
155
+ });
156
+
157
+ describe("DATE_BETWEEN", () => {
158
+ const start = new Date("2023-01-01");
159
+ const end = new Date("2023-12-31");
160
+
161
+ const schema = fieldsToSchema([mkField("dates", [{ ruleType: RuleType.DATE_BETWEEN, settings: { start, end } }])]);
162
+
163
+ it("accepts in range and rejects outside", () => {
164
+ validateOk(schema, { dates: [new Date("2023-06-01")] });
165
+
166
+ const res = validateFail(schema, { dates: [new Date("2024-01-01")] });
167
+ expectError(res, { ruleType: RuleType.DATE_BETWEEN });
168
+ });
169
+ });
170
+
171
+ describe("multiple fields", () => {
172
+ const schema = fieldsToSchema([
173
+ mkField("tags", []),
174
+ mkField("count", [{ ruleType: RuleType.COUNT, settings: { min: 0, max: 1 } }]),
175
+ ]);
176
+
177
+ it("accepts valid and rejects invalid count with correct path", () => {
178
+ validateOk(schema, { tags: [], count: [1] });
179
+
180
+ const res = validateFail(schema, { tags: [], count: [1, 2] });
181
+ expectError(res, { pathEndsWith: ["count"], ruleType: RuleType.COUNT });
182
+ });
183
+ });
184
+ });
@@ -0,0 +1,141 @@
1
+ import { PRN } from "@platecms/delta-plate-resource-notation";
2
+ import { ContentValueType, ContentValueTypeNames } from "@platecms/delta-types";
3
+ import { RuleType } from "../enums/rule-types.enum";
4
+ import { ArrayValidationSchema } from "../schemas/array.schema";
5
+ import { ContentValueValidationSchema } from "../schemas/content-value.schema";
6
+ import { DateValidationSchema } from "../schemas/date.schema";
7
+ import { NumberValidationSchema } from "../schemas/number.schema";
8
+ import { ObjectValidationSchema, Shape } from "../schemas/object.schema";
9
+ import { StringValidationSchema } from "../schemas/string.schema";
10
+ import { RuleInstance } from "../types/rule-instance.interface";
11
+ import { AllowedValuesValidationRuleSettings } from "../validation-rules/allowed-values.validation-rule";
12
+ import { CountValidationRuleSettings } from "../validation-rules/count.validation-rule";
13
+ import { DateBetweenValidationRuleSettings } from "../validation-rules/date-between.validation-rule";
14
+ import { DecimalCountValidationRuleSettings } from "../validation-rules/decimal-count.validation-rule";
15
+ import { NumberBetweenValidationRuleSettings } from "../validation-rules/number-between.validation-rule";
16
+ import { RelatableContentTypesValidationRuleSettings } from "../validation-rules/relatable-content-types.validation-rule";
17
+ import { StringFormatValidationRuleSettings } from "../validation-rules/string-format.validation-rule";
18
+ import { ValueTypeValidationRuleSettings } from "../validation-rules/value-type.validation-rule";
19
+
20
+ interface FieldToValidate {
21
+ slug: string;
22
+ contentValidationRules: RuleInstance<unknown>[];
23
+ }
24
+
25
+ interface FieldSchemaBuilder {
26
+ array: ArrayValidationSchema<ContentValueType>;
27
+ item: ContentValueValidationSchema;
28
+ }
29
+
30
+ function createFieldBuilder(): FieldSchemaBuilder {
31
+ const item = new ContentValueValidationSchema();
32
+ const array = new ArrayValidationSchema<ContentValueType>().of(item);
33
+ return { array, item };
34
+ }
35
+
36
+ function applyRule(builder: FieldSchemaBuilder, rule: RuleInstance<unknown>): void {
37
+ switch (rule.ruleType) {
38
+ case RuleType.COUNT: {
39
+ const settings = rule.settings as CountValidationRuleSettings;
40
+ builder.array.min(settings.min ?? 0).max(settings.max ?? 0);
41
+ return;
42
+ }
43
+
44
+ case RuleType.ALLOWED_VALUES: {
45
+ const settings = rule.settings as AllowedValuesValidationRuleSettings;
46
+ builder.item.allowedValues(settings.allowedValues);
47
+ return;
48
+ }
49
+
50
+ case RuleType.VALUE_TYPE: {
51
+ const settings = rule.settings as ValueTypeValidationRuleSettings;
52
+ builder.item.allowedValueTypes(settings.allowedTypes);
53
+ return;
54
+ }
55
+
56
+ case RuleType.RELATABLE_CONTENT_TYPES: {
57
+ const settings = rule.settings as RelatableContentTypesValidationRuleSettings<PRN>;
58
+ builder.item.allowedContentTypes(settings.allowedContentTypes);
59
+ return;
60
+ }
61
+
62
+ case RuleType.STRING_FORMAT: {
63
+ const settings = rule.settings as StringFormatValidationRuleSettings;
64
+ builder.item.subSchema(
65
+ ContentValueTypeNames.STRING,
66
+ new StringValidationSchema(rule).format(settings.allowedFormat),
67
+ );
68
+ return;
69
+ }
70
+
71
+ case RuleType.NUMBER_BETWEEN: {
72
+ const settings = rule.settings as NumberBetweenValidationRuleSettings;
73
+ const num =
74
+ (builder.item.subSchemas[ContentValueTypeNames.NUMBER] as NumberValidationSchema | undefined) ??
75
+ new NumberValidationSchema();
76
+
77
+ if (settings.min !== undefined) {
78
+ num.min(settings.min);
79
+ }
80
+ if (settings.max !== undefined) {
81
+ num.max(settings.max);
82
+ }
83
+
84
+ builder.item.subSchema(ContentValueTypeNames.NUMBER, num);
85
+ return;
86
+ }
87
+
88
+ case RuleType.DECIMAL_COUNT: {
89
+ const settings = rule.settings as DecimalCountValidationRuleSettings;
90
+ const num =
91
+ (builder.item.subSchemas[ContentValueTypeNames.NUMBER] as NumberValidationSchema | undefined) ??
92
+ new NumberValidationSchema();
93
+ num.maxDecimals(settings.max);
94
+
95
+ builder.item.subSchema(ContentValueTypeNames.NUMBER, num);
96
+ return;
97
+ }
98
+
99
+ case RuleType.DATE_BETWEEN: {
100
+ const settings = rule.settings as DateBetweenValidationRuleSettings;
101
+ const date = new DateValidationSchema(rule);
102
+
103
+ if (settings.start) {
104
+ date.start(settings.start);
105
+ }
106
+ if (settings.end) {
107
+ date.end(settings.end);
108
+ }
109
+
110
+ builder.item.subSchema(ContentValueTypeNames.DATE, date);
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Generates an object validation schema based on the provided content fields and their associated validation rules.
117
+ *
118
+ * Iterates over each field, applies its content validation rules to a builder, and constructs a validation schema shape
119
+ * with field slugs as keys. The resulting shape is set as the structure for a new ObjectValidationSchema.
120
+ *
121
+ * @param contentFields Array of validated field definitions, each containing validation rules to apply.
122
+ * @returns An ObjectValidationSchema instance representing the entire set of fields with their validation logic.
123
+ */
124
+ function fieldsToSchema(contentFields: FieldToValidate[]): ObjectValidationSchema {
125
+ const shape: Shape = {};
126
+
127
+ for (const field of contentFields) {
128
+ const builder = createFieldBuilder();
129
+
130
+ for (const rule of field.contentValidationRules) {
131
+ applyRule(builder, rule);
132
+ }
133
+
134
+ shape[field.slug] = builder.array;
135
+ }
136
+
137
+ return new ObjectValidationSchema().of(shape);
138
+ }
139
+
140
+ export { fieldsToSchema };
141
+ export type { FieldToValidate };
@@ -0,0 +1,26 @@
1
+ import {
2
+ ContentValueType,
3
+ ContentValueTypeName,
4
+ ContentValueTypeNames,
5
+ PRN_RESOURCE_TYPE_TO_CONTENT_VALUE_TYPE_NAME_MAP,
6
+ } from "@platecms/delta-types";
7
+ import { PRN } from "@platecms/delta-plate-resource-notation";
8
+
9
+ export function getContentValueTypeName(value: ContentValueType): ContentValueTypeName<ContentValueType> {
10
+ if (typeof value === "string") {
11
+ return ContentValueTypeNames.STRING;
12
+ }
13
+ if (typeof value === "number") {
14
+ return ContentValueTypeNames.NUMBER;
15
+ }
16
+ if (typeof value === "boolean") {
17
+ return ContentValueTypeNames.BOOLEAN;
18
+ }
19
+ if (value instanceof Date) {
20
+ return ContentValueTypeNames.DATE;
21
+ }
22
+ if (value instanceof PRN) {
23
+ return PRN_RESOURCE_TYPE_TO_CONTENT_VALUE_TYPE_NAME_MAP[value.resourceType];
24
+ }
25
+ return ContentValueTypeNames.SMART_TEXT;
26
+ }
@@ -0,0 +1,4 @@
1
+ export function isSuperset<T>(superset: T[], subset: T[]): boolean {
2
+ const set = new Set(superset);
3
+ return subset.every((item) => set.has(item));
4
+ }
@@ -5,14 +5,14 @@ import { ArrayValidationSchema } from "../schemas/array.schema";
5
5
  import { ruleToSchema } from "./rule-to-schema";
6
6
 
7
7
  describe("ruleToSchema", () => {
8
- const mockPrn = PRN.fromString("prn:plate:-1:cxc:validation-rule:1");
8
+ const mockPrn = PRN.fromString("prn:plate:-1:cms:validation-rule:1");
9
9
 
10
10
  describe("ALLOWED_VALUES rule", () => {
11
11
  it("converts ALLOWED_VALUES rule to array schema with inner content-value schema", () => {
12
12
  const rule = {
13
13
  ruleType: RuleType.ALLOWED_VALUES,
14
14
  settings: { allowedValues: ["option1", "option2", "option3"] },
15
- prn: PRN.fromString("prn:plate:-1:cxc:validation-rule:1"),
15
+ prn: PRN.fromString("prn:plate:-1:cms:validation-rule:1"),
16
16
  };
17
17
 
18
18
  const schema = ruleToSchema(rule);
@@ -68,7 +68,7 @@ describe("ruleToSchema", () => {
68
68
  const rule = {
69
69
  ruleType: RuleType.STRING_FORMAT,
70
70
  settings: { allowedFormat: ".*@.*\\..*" },
71
- prn: PRN.fromString("prn:plate:-1:cxc:validation-rule:1"),
71
+ prn: PRN.fromString("prn:plate:-1:cms:validation-rule:1"),
72
72
  };
73
73
 
74
74
  const schema = ruleToSchema(rule);
@@ -241,7 +241,7 @@ describe("ruleToSchema", () => {
241
241
  const rule = {
242
242
  ruleType: RuleType.RELATABLE_CONTENT_TYPES,
243
243
  settings: { allowedTypes: [ContentValueTypeNames.STRING, ContentValueTypeNames.NUMBER] },
244
- prn: PRN.fromString("prn:plate:-1:cxc:validation-rule:1"),
244
+ prn: PRN.fromString("prn:plate:-1:cms:validation-rule:1"),
245
245
  };
246
246
 
247
247
  const schema = ruleToSchema(rule);
@@ -2,6 +2,7 @@ import { ArrayValidationSchema } from "../schemas/array.schema";
2
2
  import { ContentValueValidationSchema } from "../schemas/content-value.schema";
3
3
  import { DateValidationSchema } from "../schemas/date.schema";
4
4
  import { NumberValidationSchema } from "../schemas/number.schema";
5
+ import { ObjectValidationSchema } from "../schemas/object.schema";
5
6
  import { StringValidationSchema } from "../schemas/string.schema";
6
7
 
7
8
  // eslint-disable-next-line @typescript-eslint/no-extraneous-class
@@ -22,6 +23,10 @@ export class SchemaBuilder {
22
23
  return new ArrayValidationSchema<T>();
23
24
  }
24
25
 
26
+ public static object<T>(): ObjectValidationSchema<T> {
27
+ return new ObjectValidationSchema<T>();
28
+ }
29
+
25
30
  public static contentValue(): ContentValueValidationSchema {
26
31
  return new ContentValueValidationSchema();
27
32
  }
@@ -6,7 +6,7 @@ import { RuleType } from "../enums/rule-types.enum";
6
6
  import { validateWithFunction } from "./validate-with-function";
7
7
 
8
8
  describe("validate-with-function", () => {
9
- const mockRulePrn = PRN.fromString("prn:plate:-1:cxc:validation-rule:1");
9
+ const mockRulePrn = PRN.fromString("prn:plate:-1:cms:validation-rule:1");
10
10
  const mockPath = ["content-type-prn", "content-field-prn"];
11
11
  const fallbackErrorMessage = "Validation failed";
12
12
 
@@ -182,7 +182,7 @@ describe("validate-with-function", () => {
182
182
 
183
183
  // Test with PRN
184
184
  const prnResult = validateWithFunction(
185
- PRN.fromString("prn:plate:-1:cxc:content-item:1"),
185
+ PRN.fromString("prn:plate:-1:cms:content-item:1"),
186
186
  mockRule,
187
187
  mockPath,
188
188
  fallbackErrorMessage,
@@ -229,7 +229,7 @@ describe("validate-with-function", () => {
229
229
  42,
230
230
  true,
231
231
  new Date("2023-01-01"),
232
- PRN.fromString("prn:plate:-1:cxc:content-item:1"),
232
+ PRN.fromString("prn:plate:-1:cms:content-item:1"),
233
233
  { type: "root", children: [] },
234
234
  ];
235
235
 
@@ -105,7 +105,7 @@ describe("ArrayValidationSchema", () => {
105
105
  ruleType: RuleType.COUNT,
106
106
  settings: { min: 2, max: 4 },
107
107
  id: "count-rule",
108
- prn: PRN.fromString("prn:plate:-1:cxc:validation-rule:1"),
108
+ prn: PRN.fromString("prn:plate:-1:cms:validation-rule:1"),
109
109
  };
110
110
 
111
111
  const schemaWithRule = new ArrayValidationSchema<ContentValueType>(mockRule);
@@ -37,7 +37,7 @@ describe("ContentValueValidationSchema", () => {
37
37
  });
38
38
 
39
39
  it("passes validation when value is a PRN", () => {
40
- const prn = PRN.fromString("prn:plate:-1:cxc:content-item:1");
40
+ const prn = PRN.fromString("prn:plate:-1:cms:content-item:1");
41
41
  const result = schema.validate(prn, { path: ["test"] });
42
42
  expect(result.isValid).toBe(true);
43
43
  expect(result.errors).toHaveLength(0);
@@ -92,10 +92,10 @@ describe("ContentValueValidationSchema", () => {
92
92
 
93
93
  describe("allowed content types validation", () => {
94
94
  it("passes validation when PRN content type is in allowed types", () => {
95
- const allowedTypes = [PRN.fromString("prn:plate:-1:cxc:content-item:1")];
95
+ const allowedTypes = [PRN.fromString("prn:plate:-1:cms:content-item:1")];
96
96
  schema.allowedContentTypes(allowedTypes);
97
97
 
98
- const prn = PRN.fromString("prn:plate:-1:cxc:content-item:1");
98
+ const prn = PRN.fromString("prn:plate:-1:cms:content-item:1");
99
99
  const result = schema.validate(prn, { path: ["test"] });
100
100
  expect(result.isValid).toBe(true);
101
101
  expect(result.errors).toHaveLength(0);
@@ -106,7 +106,7 @@ describe("ContentValueValidationSchema", () => {
106
106
  const mockRule = {
107
107
  ruleType: RuleType.VALUE_TYPE,
108
108
  settings: { allowedTypes: allowedValueTypes },
109
- prn: PRN.fromString("prn:plate:-1:cxc:validation-rule:1"),
109
+ prn: PRN.fromString("prn:plate:-1:cms:validation-rule:1"),
110
110
  };
111
111
 
112
112
  const schemaWithRule = new ContentValueValidationSchema(mockRule).allowedValueTypes(allowedValueTypes);
@@ -119,7 +119,7 @@ describe("ContentValueValidationSchema", () => {
119
119
  describe("method chaining", () => {
120
120
  it("supports method chaining", () => {
121
121
  const allowedValueTypes = [ContentValueTypeNames.STRING, ContentValueTypeNames.NUMBER];
122
- const allowedContentTypes = [PRN.fromString("prn:plate:-1:cxc:content-item:1")];
122
+ const allowedContentTypes = [PRN.fromString("prn:plate:-1:cms:content-item:1")];
123
123
 
124
124
  const result = schema
125
125
  .allowedValueTypes(allowedValueTypes)
@@ -10,11 +10,20 @@ import {
10
10
  validateValueValueType,
11
11
  } from "../validation-rules/value-type.validation-rule";
12
12
  import { BaseValidationSchema } from "./base.schema";
13
+ import { ValidationSchema } from "../types/validation-schema.interface";
14
+ import { getContentValueTypeName } from "../helpers/get-content-value-type-name";
15
+
16
+ type SubSchemas = Partial<Record<ContentValueTypeName<ContentValueType>, ValidationSchema<ContentValueType>>>;
13
17
 
14
18
  export class ContentValueValidationSchema extends BaseValidationSchema<ContentValueType, ContentValueValidationSchema> {
15
19
  public readonly type = "content-value";
16
20
  private _allowedValueTypes?: ContentValueTypeName<ContentValueType>[];
17
21
  private _allowedContentTypes?: PRN[];
22
+ private _subSchemas: SubSchemas = {};
23
+
24
+ public get subSchemas(): SubSchemas {
25
+ return this._subSchemas;
26
+ }
18
27
 
19
28
  public allowedContentTypes(types: PRN[]): this {
20
29
  this._allowedContentTypes = types;
@@ -26,13 +35,29 @@ export class ContentValueValidationSchema extends BaseValidationSchema<ContentVa
26
35
  return this;
27
36
  }
28
37
 
38
+ public subSchema(type: ContentValueTypeName<ContentValueType>, schema: ValidationSchema<ContentValueType>): this {
39
+ this._subSchemas[type] = schema;
40
+ return this;
41
+ }
42
+
29
43
  protected _typeCheck(value: unknown, path: string[]): ValidationErrorData[] {
30
44
  const allowedValueTypesError = this._validateAllowedValueTypes(value as ContentValueType, path);
31
45
  return allowedValueTypesError ? [allowedValueTypesError] : [];
32
46
  }
33
47
 
34
- protected _validateAgainstSchema(_value: ContentValueType, _context: ValidationContext = {}): ValidationErrorData[] {
35
- return [];
48
+ protected _validateAgainstSchema(value: ContentValueType, context: ValidationContext = {}): ValidationErrorData[] {
49
+ const errors: ValidationErrorData[] = [];
50
+ const valueType = getContentValueTypeName(value);
51
+
52
+ const subSchema = this._subSchemas[valueType];
53
+ if (subSchema !== undefined) {
54
+ const result = subSchema.validate(value, context);
55
+ if (!result.isValid) {
56
+ errors.push(...result.errors);
57
+ }
58
+ }
59
+
60
+ return errors;
36
61
  }
37
62
 
38
63
  private _validateAllowedValueTypes(
@@ -88,7 +88,7 @@ describe("DateValidationSchema", () => {
88
88
  const mockRule = {
89
89
  ruleType: RuleType.DATE_BETWEEN,
90
90
  settings: { start: startDate, end: endDate },
91
- prn: PRN.fromString("prn:plate:-1:cxc:validation-rule:1"),
91
+ prn: PRN.fromString("prn:plate:-1:cms:validation-rule:1"),
92
92
  };
93
93
 
94
94
  schema = new DateValidationSchema(mockRule).start(startDate).end(endDate);