@featurevisor/core 2.24.0 → 2.26.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.
- package/CHANGELOG.md +22 -0
- package/coverage/clover.xml +1499 -1476
- package/coverage/coverage-final.json +18 -17
- package/coverage/lcov-report/builder/allocator.ts.html +1 -1
- package/coverage/lcov-report/builder/buildDatafile.ts.html +1 -1
- package/coverage/lcov-report/builder/buildScopedConditions.ts.html +1 -1
- package/coverage/lcov-report/builder/buildScopedDatafile.ts.html +1 -1
- package/coverage/lcov-report/builder/buildScopedSegments.ts.html +1 -1
- package/coverage/lcov-report/builder/convertToV1.ts.html +1 -1
- package/coverage/lcov-report/builder/getFeatureRanges.ts.html +1 -1
- package/coverage/lcov-report/builder/hashes.ts.html +1 -1
- package/coverage/lcov-report/builder/index.html +1 -1
- package/coverage/lcov-report/builder/mutateVariables.ts.html +1 -1
- package/coverage/lcov-report/builder/mutator.ts.html +52 -52
- package/coverage/lcov-report/builder/revision.ts.html +1 -1
- package/coverage/lcov-report/builder/traffic.ts.html +1 -1
- package/coverage/lcov-report/config/index.html +1 -1
- package/coverage/lcov-report/config/index.ts.html +1 -1
- package/coverage/lcov-report/config/projectConfig.ts.html +15 -15
- package/coverage/lcov-report/datasource/adapter.ts.html +1 -1
- package/coverage/lcov-report/datasource/datasource.ts.html +18 -18
- package/coverage/lcov-report/datasource/filesystemAdapter.ts.html +43 -43
- package/coverage/lcov-report/datasource/index.html +1 -1
- package/coverage/lcov-report/datasource/index.ts.html +1 -1
- package/coverage/lcov-report/generate-code/index.html +1 -1
- package/coverage/lcov-report/generate-code/typescript.ts.html +1 -1
- package/coverage/lcov-report/index.html +18 -18
- package/coverage/lcov-report/linter/attributeSchema.ts.html +130 -130
- package/coverage/lcov-report/linter/checkCircularDependency.ts.html +11 -11
- package/coverage/lcov-report/linter/checkPercentageExceedingSlot.ts.html +15 -15
- package/coverage/lcov-report/linter/conditionSchema.ts.html +150 -150
- package/coverage/lcov-report/linter/featureSchema.ts.html +458 -443
- package/coverage/lcov-report/linter/groupSchema.ts.html +24 -27
- package/coverage/lcov-report/linter/index.html +83 -68
- package/coverage/lcov-report/linter/lintProject.ts.html +158 -158
- package/coverage/lcov-report/linter/mutationNotation.ts.html +104 -104
- package/coverage/lcov-report/linter/printError.ts.html +79 -28
- package/coverage/lcov-report/linter/schema.ts.html +129 -135
- package/coverage/lcov-report/linter/segmentSchema.ts.html +15 -9
- package/coverage/lcov-report/linter/testSchema.ts.html +67 -94
- package/coverage/lcov-report/linter/zodHelpers.ts.html +133 -0
- package/coverage/lcov-report/list/index.html +1 -1
- package/coverage/lcov-report/list/matrix.ts.html +1 -1
- package/coverage/lcov-report/tester/cliFormat.ts.html +6 -6
- package/coverage/lcov-report/tester/helpers.ts.html +1 -1
- package/coverage/lcov-report/tester/index.html +1 -1
- package/coverage/lcov-report/utils/extractKeys.ts.html +1 -1
- package/coverage/lcov-report/utils/git.ts.html +1 -1
- package/coverage/lcov-report/utils/index.html +1 -1
- package/coverage/lcov-report/utils/index.ts.html +1 -1
- package/coverage/lcov-report/utils/pretty.ts.html +1 -1
- package/coverage/lcov.info +3031 -2984
- package/lib/linter/attributeSchema.d.ts +19 -352
- package/lib/linter/attributeSchema.spec.js.map +1 -1
- package/lib/linter/conditionSchema.d.ts +1 -1
- package/lib/linter/conditionSchema.js +2 -3
- package/lib/linter/conditionSchema.js.map +1 -1
- package/lib/linter/conditionSchema.spec.js.map +1 -1
- package/lib/linter/featureSchema.d.ts +75 -964
- package/lib/linter/featureSchema.js +64 -53
- package/lib/linter/featureSchema.js.map +1 -1
- package/lib/linter/featureSchema.spec.js +28 -0
- package/lib/linter/featureSchema.spec.js.map +1 -1
- package/lib/linter/groupSchema.d.ts +4 -28
- package/lib/linter/groupSchema.js +3 -5
- package/lib/linter/groupSchema.js.map +1 -1
- package/lib/linter/lintProject.spec.js +101 -13
- package/lib/linter/lintProject.spec.js.map +1 -1
- package/lib/linter/printError.js +21 -7
- package/lib/linter/printError.js.map +1 -1
- package/lib/linter/schema.d.ts +9 -2
- package/lib/linter/schema.js +2 -6
- package/lib/linter/schema.js.map +1 -1
- package/lib/linter/schema.spec.js +3 -2
- package/lib/linter/schema.spec.js.map +1 -1
- package/lib/linter/segmentSchema.d.ts +1 -9
- package/lib/linter/segmentSchema.js +3 -1
- package/lib/linter/segmentSchema.js.map +1 -1
- package/lib/linter/segmentSchema.spec.js.map +1 -1
- package/lib/linter/testSchema.d.ts +16 -222
- package/lib/linter/testSchema.js +23 -38
- package/lib/linter/testSchema.js.map +1 -1
- package/lib/linter/testSchema.spec.d.ts +1 -0
- package/lib/linter/testSchema.spec.js +162 -0
- package/lib/linter/testSchema.spec.js.map +1 -0
- package/lib/linter/zodHelpers.d.ts +2 -0
- package/lib/linter/zodHelpers.js +15 -0
- package/lib/linter/zodHelpers.js.map +1 -0
- package/package.json +4 -3
- package/src/linter/attributeSchema.spec.ts +3 -3
- package/src/linter/conditionSchema.spec.ts +3 -3
- package/src/linter/conditionSchema.ts +4 -4
- package/src/linter/featureSchema.spec.ts +39 -4
- package/src/linter/featureSchema.ts +82 -77
- package/src/linter/groupSchema.ts +11 -12
- package/src/linter/lintProject.spec.ts +121 -13
- package/src/linter/printError.ts +27 -10
- package/src/linter/schema.spec.ts +12 -11
- package/src/linter/schema.ts +7 -9
- package/src/linter/segmentSchema.spec.ts +5 -5
- package/src/linter/segmentSchema.ts +3 -1
- package/src/linter/testSchema.spec.ts +193 -0
- package/src/linter/testSchema.ts +36 -45
- package/src/linter/zodHelpers.ts +16 -0
|
@@ -74,7 +74,7 @@ function getSegmentSchema() {
|
|
|
74
74
|
return getSegmentZodSchema(projectConfig, conditionsZodSchema);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function parseSegment(input: unknown): z.
|
|
77
|
+
function parseSegment(input: unknown): z.ZodSafeParseResult<unknown> {
|
|
78
78
|
return getSegmentSchema().safeParse(input);
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -82,7 +82,7 @@ function expectSegmentSuccess(input: unknown): void {
|
|
|
82
82
|
const result = parseSegment(input);
|
|
83
83
|
expect(result.success).toBe(true);
|
|
84
84
|
if (!result.success) {
|
|
85
|
-
const err = (result as z.
|
|
85
|
+
const err = (result as z.ZodSafeParseError<unknown>).error;
|
|
86
86
|
const msg = err.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
87
87
|
throw new Error(`Expected segment to pass: ${msg}`);
|
|
88
88
|
}
|
|
@@ -92,7 +92,7 @@ function expectSegmentFailure(input: unknown, messageSubstring?: string): z.ZodE
|
|
|
92
92
|
const result = parseSegment(input);
|
|
93
93
|
expect(result.success).toBe(false);
|
|
94
94
|
if (result.success) throw new Error("Expected segment to fail");
|
|
95
|
-
const err = (result as z.
|
|
95
|
+
const err = (result as z.ZodSafeParseError<unknown>).error;
|
|
96
96
|
if (messageSubstring) {
|
|
97
97
|
const messages = err.issues
|
|
98
98
|
.map((i) => (typeof i.message === "string" ? i.message : ""))
|
|
@@ -134,7 +134,7 @@ describe("segmentSchema.ts :: getSegmentZodSchema", () => {
|
|
|
134
134
|
});
|
|
135
135
|
expect(result.success).toBe(false);
|
|
136
136
|
if (!result.success) {
|
|
137
|
-
const err = (result as z.
|
|
137
|
+
const err = (result as z.ZodSafeParseError<unknown>).error;
|
|
138
138
|
const path = err.issues.map((i) => i.path.join(".")).join(" ");
|
|
139
139
|
expect(path).toContain("description");
|
|
140
140
|
}
|
|
@@ -346,7 +346,7 @@ describe("segmentSchema.ts :: getSegmentZodSchema", () => {
|
|
|
346
346
|
extraKey: true,
|
|
347
347
|
});
|
|
348
348
|
expect(result.success).toBe(false);
|
|
349
|
-
const err = (result as z.
|
|
349
|
+
const err = (result as z.ZodSafeParseError<unknown>).error;
|
|
350
350
|
const messages = err.issues
|
|
351
351
|
.map((i) => (typeof i.message === "string" ? i.message : ""))
|
|
352
352
|
.join(" ");
|
|
@@ -6,7 +6,9 @@ export function getSegmentZodSchema(projectConfig: ProjectConfig, conditionsZodS
|
|
|
6
6
|
const segmentZodSchema = z
|
|
7
7
|
.object({
|
|
8
8
|
archived: z.boolean().optional(),
|
|
9
|
-
description: z.string(
|
|
9
|
+
description: z.string({
|
|
10
|
+
error: (issue) => (issue.input === undefined ? "Required" : undefined),
|
|
11
|
+
}),
|
|
10
12
|
conditions: conditionsZodSchema,
|
|
11
13
|
})
|
|
12
14
|
.strict();
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import type { ProjectConfig } from "../config";
|
|
4
|
+
import { getLintIssuesFromZodError } from "./printError";
|
|
5
|
+
import { getTestsZodSchema } from "./testSchema";
|
|
6
|
+
|
|
7
|
+
function minimalProjectConfig(overrides: Partial<ProjectConfig> = {}): ProjectConfig {
|
|
8
|
+
return {
|
|
9
|
+
featuresDirectoryPath: "",
|
|
10
|
+
segmentsDirectoryPath: "",
|
|
11
|
+
attributesDirectoryPath: "",
|
|
12
|
+
groupsDirectoryPath: "",
|
|
13
|
+
schemasDirectoryPath: "",
|
|
14
|
+
testsDirectoryPath: "",
|
|
15
|
+
stateDirectoryPath: "",
|
|
16
|
+
datafilesDirectoryPath: "",
|
|
17
|
+
datafileNamePattern: "",
|
|
18
|
+
revisionFileName: "",
|
|
19
|
+
siteExportDirectoryPath: "",
|
|
20
|
+
environmentsDirectoryPath: "",
|
|
21
|
+
environments: ["staging", "production"],
|
|
22
|
+
splitByEnvironment: false,
|
|
23
|
+
tags: ["all", "beta"],
|
|
24
|
+
adapter: {},
|
|
25
|
+
plugins: [],
|
|
26
|
+
defaultBucketBy: "userId",
|
|
27
|
+
parser: "yml",
|
|
28
|
+
prettyState: true,
|
|
29
|
+
prettyDatafile: false,
|
|
30
|
+
stringify: true,
|
|
31
|
+
...overrides,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getSchema(projectConfig = minimalProjectConfig()) {
|
|
36
|
+
return getTestsZodSchema(projectConfig, ["checkout"], ["desktop"]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseTest(input: unknown): z.ZodSafeParseResult<unknown> {
|
|
40
|
+
return getSchema().safeParse(input);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function expectTestSuccess(input: unknown): void {
|
|
44
|
+
const result = parseTest(input);
|
|
45
|
+
expect(result.success).toBe(true);
|
|
46
|
+
if (!result.success) {
|
|
47
|
+
const error = (result as z.ZodSafeParseError<unknown>).error;
|
|
48
|
+
throw new Error(error.issues.map((issue) => issue.message).join("; "));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function expectTestFailure(input: unknown, messageSubstring: string): z.ZodError {
|
|
53
|
+
const result = parseTest(input);
|
|
54
|
+
expect(result.success).toBe(false);
|
|
55
|
+
if (result.success) {
|
|
56
|
+
throw new Error("Expected test schema to fail");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const error = (result as z.ZodSafeParseError<unknown>).error;
|
|
60
|
+
expect(error.issues.map((issue) => issue.message).join(" ")).toContain(messageSubstring);
|
|
61
|
+
|
|
62
|
+
return error;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe("testSchema.ts :: getTestsZodSchema", () => {
|
|
66
|
+
it("accepts a valid feature test with matrix, context, sticky, and expected evaluations", () => {
|
|
67
|
+
expectTestSuccess({
|
|
68
|
+
feature: "checkout",
|
|
69
|
+
assertions: [
|
|
70
|
+
{
|
|
71
|
+
matrix: {
|
|
72
|
+
country: ["nl", "de"],
|
|
73
|
+
loggedIn: [true, false],
|
|
74
|
+
score: [1, 2],
|
|
75
|
+
empty: [null],
|
|
76
|
+
},
|
|
77
|
+
description: "feature assertion",
|
|
78
|
+
at: "${{ score }}",
|
|
79
|
+
environment: "staging",
|
|
80
|
+
tag: "all",
|
|
81
|
+
sticky: {
|
|
82
|
+
flag: { checkout: "cached" },
|
|
83
|
+
},
|
|
84
|
+
context: {
|
|
85
|
+
userId: "user-1",
|
|
86
|
+
},
|
|
87
|
+
defaultVariableValues: {
|
|
88
|
+
title: "Fallback",
|
|
89
|
+
},
|
|
90
|
+
expectedToBeEnabled: true,
|
|
91
|
+
expectedVariation: "control",
|
|
92
|
+
expectedVariables: {
|
|
93
|
+
title: "Checkout",
|
|
94
|
+
},
|
|
95
|
+
expectedEvaluations: {
|
|
96
|
+
flag: { reason: "rule" },
|
|
97
|
+
variation: { reason: "bucketed" },
|
|
98
|
+
variables: { title: { reason: "default" } },
|
|
99
|
+
},
|
|
100
|
+
children: [
|
|
101
|
+
{
|
|
102
|
+
context: {
|
|
103
|
+
userId: "child-1",
|
|
104
|
+
},
|
|
105
|
+
expectedToBeEnabled: false,
|
|
106
|
+
expectedVariation: null,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("accepts matrix placeholders for environment values", () => {
|
|
115
|
+
expectTestSuccess({
|
|
116
|
+
feature: "checkout",
|
|
117
|
+
assertions: [
|
|
118
|
+
{
|
|
119
|
+
matrix: {
|
|
120
|
+
environment: ["staging", "production"],
|
|
121
|
+
},
|
|
122
|
+
at: 1,
|
|
123
|
+
environment: "${{ environment }}",
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("rejects unknown environments with a precise path", () => {
|
|
130
|
+
const error = expectTestFailure(
|
|
131
|
+
{
|
|
132
|
+
feature: "checkout",
|
|
133
|
+
assertions: [
|
|
134
|
+
{
|
|
135
|
+
at: 1,
|
|
136
|
+
environment: "qa",
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
'Unknown environment "qa"',
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(error.issues.some((issue) => issue.path.join(".") === "assertions.0.environment")).toBe(
|
|
144
|
+
true,
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("rejects unknown feature and segment keys", () => {
|
|
149
|
+
expectTestFailure(
|
|
150
|
+
{
|
|
151
|
+
feature: "not-real",
|
|
152
|
+
assertions: [{ at: 1, environment: "staging" }],
|
|
153
|
+
},
|
|
154
|
+
'Unknown feature "not-real"',
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expectTestFailure(
|
|
158
|
+
{
|
|
159
|
+
segment: "not-real",
|
|
160
|
+
assertions: [
|
|
161
|
+
{
|
|
162
|
+
context: {},
|
|
163
|
+
expectedToMatch: true,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
'Unknown segment "not-real"',
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("rejects invalid matrix values", () => {
|
|
172
|
+
const error = expectTestFailure(
|
|
173
|
+
{
|
|
174
|
+
feature: "checkout",
|
|
175
|
+
assertions: [
|
|
176
|
+
{
|
|
177
|
+
matrix: {
|
|
178
|
+
country: [{ code: "nl" }],
|
|
179
|
+
},
|
|
180
|
+
at: 1,
|
|
181
|
+
environment: "staging",
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
"Invalid input",
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const lintIssues = getLintIssuesFromZodError(error);
|
|
189
|
+
expect(lintIssues.some((issue) => issue.path.join(".").includes("matrix.country.0"))).toBe(
|
|
190
|
+
true,
|
|
191
|
+
);
|
|
192
|
+
});
|
|
193
|
+
});
|
package/src/linter/testSchema.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
import { ProjectConfig } from "../config";
|
|
4
|
+
import { refineWithMessage } from "./zodHelpers";
|
|
4
5
|
|
|
5
6
|
export function getTestsZodSchema(
|
|
6
7
|
projectConfig: ProjectConfig,
|
|
@@ -10,6 +11,7 @@ export function getTestsZodSchema(
|
|
|
10
11
|
const scopeNames = projectConfig.scopes ? projectConfig.scopes.map((scope) => scope.name) : [];
|
|
11
12
|
|
|
12
13
|
const matrixZodSchema = z.record(
|
|
14
|
+
z.string(),
|
|
13
15
|
z.array(
|
|
14
16
|
z.union([
|
|
15
17
|
// allowed values in arrays
|
|
@@ -23,18 +25,17 @@ export function getTestsZodSchema(
|
|
|
23
25
|
|
|
24
26
|
const segmentTestZodSchema = z
|
|
25
27
|
.object({
|
|
26
|
-
segment:
|
|
28
|
+
segment: refineWithMessage(
|
|
29
|
+
z.string(),
|
|
27
30
|
(value) => availableSegmentKeys.includes(value),
|
|
28
|
-
(value) =>
|
|
29
|
-
message: `Unknown segment "${value}"`,
|
|
30
|
-
}),
|
|
31
|
+
(value) => `Unknown segment "${value}"`,
|
|
31
32
|
),
|
|
32
33
|
assertions: z.array(
|
|
33
34
|
z
|
|
34
35
|
.object({
|
|
35
36
|
matrix: matrixZodSchema.optional(),
|
|
36
37
|
description: z.string().optional(),
|
|
37
|
-
context: z.record(z.unknown()),
|
|
38
|
+
context: z.record(z.string(), z.unknown()),
|
|
38
39
|
expectedToMatch: z.boolean(),
|
|
39
40
|
})
|
|
40
41
|
.strict(),
|
|
@@ -44,11 +45,10 @@ export function getTestsZodSchema(
|
|
|
44
45
|
|
|
45
46
|
const featureTestZodSchema = z
|
|
46
47
|
.object({
|
|
47
|
-
feature:
|
|
48
|
+
feature: refineWithMessage(
|
|
49
|
+
z.string(),
|
|
48
50
|
(value) => availableFeatureKeys.includes(value),
|
|
49
|
-
(value) =>
|
|
50
|
-
message: `Unknown feature "${value}"`,
|
|
51
|
-
}),
|
|
51
|
+
(value) => `Unknown feature "${value}"`,
|
|
52
52
|
),
|
|
53
53
|
assertions: z.array(
|
|
54
54
|
z
|
|
@@ -62,7 +62,8 @@ export function getTestsZodSchema(
|
|
|
62
62
|
z.string(),
|
|
63
63
|
]),
|
|
64
64
|
environment: Array.isArray(projectConfig.environments)
|
|
65
|
-
?
|
|
65
|
+
? refineWithMessage(
|
|
66
|
+
z.string(),
|
|
66
67
|
(value) => {
|
|
67
68
|
if (value.indexOf("${{") === 0) {
|
|
68
69
|
// allow unknown strings for matrix
|
|
@@ -79,45 +80,35 @@ export function getTestsZodSchema(
|
|
|
79
80
|
|
|
80
81
|
return false;
|
|
81
82
|
},
|
|
82
|
-
(value) =>
|
|
83
|
-
message: `Unknown environment "${value}"`,
|
|
84
|
-
}),
|
|
83
|
+
(value) => `Unknown environment "${value}"`,
|
|
85
84
|
)
|
|
86
85
|
: z.never().optional(),
|
|
87
|
-
tag:
|
|
88
|
-
.string()
|
|
89
|
-
.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
.string()
|
|
98
|
-
.refine(
|
|
99
|
-
(value) => scopeNames.includes(value),
|
|
100
|
-
(value) => ({
|
|
101
|
-
message: `Unknown scope "${value}"`,
|
|
102
|
-
}),
|
|
103
|
-
)
|
|
104
|
-
.optional(),
|
|
86
|
+
tag: refineWithMessage(
|
|
87
|
+
z.string(),
|
|
88
|
+
(value) => projectConfig.tags.includes(value),
|
|
89
|
+
(value) => `Unknown tag "${value}"`,
|
|
90
|
+
).optional(),
|
|
91
|
+
scope: refineWithMessage(
|
|
92
|
+
z.string(),
|
|
93
|
+
(value) => scopeNames.includes(value),
|
|
94
|
+
(value) => `Unknown scope "${value}"`,
|
|
95
|
+
).optional(),
|
|
105
96
|
|
|
106
97
|
// parent
|
|
107
|
-
sticky: z.record(z.record(z.any())).optional(),
|
|
108
|
-
context: z.record(z.unknown()).optional(),
|
|
98
|
+
sticky: z.record(z.string(), z.record(z.string(), z.any())).optional(),
|
|
99
|
+
context: z.record(z.string(), z.unknown()).optional(),
|
|
109
100
|
|
|
110
101
|
defaultVariationValue: z.string().optional(),
|
|
111
|
-
defaultVariableValues: z.record(z.unknown()).optional(),
|
|
102
|
+
defaultVariableValues: z.record(z.string(), z.unknown()).optional(),
|
|
112
103
|
|
|
113
104
|
expectedToBeEnabled: z.boolean().optional(),
|
|
114
105
|
expectedVariation: z.string().nullable().optional(),
|
|
115
|
-
expectedVariables: z.record(z.unknown()).optional(),
|
|
106
|
+
expectedVariables: z.record(z.string(), z.unknown()).optional(),
|
|
116
107
|
expectedEvaluations: z
|
|
117
108
|
.object({
|
|
118
|
-
flag: z.record(z.any()).optional(),
|
|
119
|
-
variation: z.record(z.any()).optional(),
|
|
120
|
-
variables: z.record(z.record(z.any())).optional(),
|
|
109
|
+
flag: z.record(z.string(), z.any()).optional(),
|
|
110
|
+
variation: z.record(z.string(), z.any()).optional(),
|
|
111
|
+
variables: z.record(z.string(), z.record(z.string(), z.any())).optional(),
|
|
121
112
|
})
|
|
122
113
|
.optional(),
|
|
123
114
|
|
|
@@ -125,21 +116,21 @@ export function getTestsZodSchema(
|
|
|
125
116
|
.array(
|
|
126
117
|
z.object({
|
|
127
118
|
// copied from parent
|
|
128
|
-
sticky: z.record(z.record(z.any())).optional(),
|
|
129
|
-
context: z.record(z.unknown()).optional(),
|
|
119
|
+
sticky: z.record(z.string(), z.record(z.string(), z.any())).optional(),
|
|
120
|
+
context: z.record(z.string(), z.unknown()).optional(),
|
|
130
121
|
|
|
131
122
|
defaultVariationValue: z.string().optional(),
|
|
132
|
-
defaultVariableValues: z.record(z.unknown()).optional(),
|
|
123
|
+
defaultVariableValues: z.record(z.string(), z.unknown()).optional(),
|
|
133
124
|
|
|
134
125
|
expectedToBeEnabled: z.boolean().optional(),
|
|
135
126
|
expectedVariation: z.string().nullable().optional(),
|
|
136
|
-
expectedVariables: z.record(z.unknown()).optional(),
|
|
127
|
+
expectedVariables: z.record(z.string(), z.unknown()).optional(),
|
|
137
128
|
|
|
138
129
|
expectedEvaluations: z
|
|
139
130
|
.object({
|
|
140
|
-
flag: z.record(z.any()).optional(),
|
|
141
|
-
variation: z.record(z.any()).optional(),
|
|
142
|
-
variables: z.record(z.record(z.any())).optional(),
|
|
131
|
+
flag: z.record(z.string(), z.any()).optional(),
|
|
132
|
+
variation: z.record(z.string(), z.any()).optional(),
|
|
133
|
+
variables: z.record(z.string(), z.record(z.string(), z.any())).optional(),
|
|
143
134
|
})
|
|
144
135
|
.optional(),
|
|
145
136
|
}),
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export function refineWithMessage<T>(
|
|
4
|
+
schema: z.ZodType<T>,
|
|
5
|
+
predicate: (value: T) => boolean,
|
|
6
|
+
getMessage: (value: T) => string,
|
|
7
|
+
) {
|
|
8
|
+
return schema.superRefine((value, ctx) => {
|
|
9
|
+
if (!predicate(value)) {
|
|
10
|
+
ctx.addIssue({
|
|
11
|
+
code: z.ZodIssueCode.custom,
|
|
12
|
+
message: getMessage(value),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|