Skip to content

Commit 68fe123

Browse files
committed
feat: use formatted zod schema validation errors
1 parent c1ed226 commit 68fe123

File tree

23 files changed

+110
-74
lines changed

23 files changed

+110
-74
lines changed

packages/ci/src/lib/schemas.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,18 @@ export const interpolatedSlugSchema = slugSchema.catch(ctx => {
2626
throw new ZodError(ctx.error.issues);
2727
});
2828

29-
export const configPatternsSchema = z.object({
30-
persist: persistConfigSchema.transform(persist => ({
31-
...DEFAULT_PERSIST_CONFIG,
32-
...persist,
33-
})),
34-
upload: uploadConfigSchema
35-
.omit({ organization: true, project: true })
36-
.extend({
37-
organization: interpolatedSlugSchema,
38-
project: interpolatedSlugSchema,
39-
})
40-
.optional(),
41-
});
29+
export const configPatternsSchema = z
30+
.object({
31+
persist: persistConfigSchema.transform(persist => ({
32+
...DEFAULT_PERSIST_CONFIG,
33+
...persist,
34+
})),
35+
upload: uploadConfigSchema
36+
.omit({ organization: true, project: true })
37+
.extend({
38+
organization: interpolatedSlugSchema,
39+
project: interpolatedSlugSchema,
40+
})
41+
.optional(),
42+
})
43+
.meta({ title: 'ConfigPatterns' });

packages/ci/src/lib/settings.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ZodError, z } from 'zod';
1+
import { SchemaValidationError, validate } from '@code-pushup/models';
22
import type { ConfigPatterns, Settings } from './models.js';
33
import { configPatternsSchema } from './schemas.js';
44

@@ -32,17 +32,15 @@ export function parseConfigPatternsFromString(
3232

3333
try {
3434
const json = JSON.parse(value);
35-
return configPatternsSchema.parse(json);
35+
return validate(configPatternsSchema, json);
3636
} catch (error) {
3737
if (error instanceof SyntaxError) {
3838
throw new TypeError(
3939
`Invalid JSON value for configPatterns input - ${error.message}`,
4040
);
4141
}
42-
if (error instanceof ZodError) {
43-
throw new TypeError(
44-
`Invalid shape of configPatterns input:\n${z.prettifyError(error)}`,
45-
);
42+
if (error instanceof SchemaValidationError) {
43+
throw new TypeError(`Invalid shape of configPatterns input:\n${error}`);
4644
}
4745
throw error;
4846
}

packages/cli/src/lib/implementation/core-config.int.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ansis from 'ansis';
12
import { describe, expect, vi } from 'vitest';
23
import {
34
type CoreConfig,
@@ -203,6 +204,11 @@ describe('parsing values from CLI and middleware', () => {
203204
middlewares: [{ middlewareFunction: coreConfigMiddleware }],
204205
},
205206
).parseAsync(),
206-
).rejects.toThrow('invalid_type');
207+
).rejects.toThrow(`Invalid ${ansis.bold('UploadConfig')}
208+
✖ Invalid input: expected string, received undefined
209+
→ at server
210+
✖ Invalid input: expected string, received undefined
211+
→ at apiKey
212+
`);
207213
});
208214
});

packages/cli/src/lib/implementation/core-config.middleware.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
DEFAULT_PERSIST_OUTPUT_DIR,
99
type Format,
1010
uploadConfigSchema,
11+
validate,
1112
} from '@code-pushup/models';
1213
import type { CoreConfigCliOptions } from './core-config.model.js';
1314
import type { FilterOptions } from './filter.model.js';
@@ -58,10 +59,7 @@ export async function coreConfigMiddleware<
5859
const upload =
5960
rcUpload == null && cliUpload == null
6061
? undefined
61-
: uploadConfigSchema.parse({
62-
...rcUpload,
63-
...cliUpload,
64-
});
62+
: validate(uploadConfigSchema, { ...rcUpload, ...cliUpload });
6563

6664
return {
6765
...(config != null && { config }),

packages/cli/src/lib/yargs-cli.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import yargs, {
88
type Options,
99
type ParserConfigurationOptions,
1010
} from 'yargs';
11-
import { type PersistConfig, formatSchema } from '@code-pushup/models';
11+
import {
12+
type PersistConfig,
13+
formatSchema,
14+
validate,
15+
} from '@code-pushup/models';
1216
import { TERMINAL_WIDTH } from '@code-pushup/utils';
1317
import {
1418
descriptionStyle,
@@ -37,9 +41,8 @@ export const yargsDecorator = {
3741
* returns configurable yargs CLI for code-pushup
3842
*
3943
* @example
40-
* yargsCli(hideBin(process.argv))
41-
* // bootstrap CLI; format arguments
42-
* .argv;
44+
* // bootstrap CLI; format arguments
45+
* yargsCli(hideBin(process.argv)).argv;
4346
*/
4447
export function yargsCli<T = unknown>(
4548
argv: string[],
@@ -150,11 +153,13 @@ function validatePersistFormat(persist: PersistConfig) {
150153
if (persist.format != null) {
151154
persist.format
152155
.flatMap(format => format.split(','))
153-
.forEach(format => formatSchema.parse(format));
156+
.forEach(format => {
157+
validate(formatSchema, format);
158+
});
154159
}
155160
return true;
156161
} catch {
157-
throw new Error(
162+
throw new TypeError(
158163
`Invalid persist.format option. Valid options are: ${formatSchema.options.join(
159164
', ',
160165
)}`,

packages/core/src/lib/collect-and-persist.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
type CoreConfig,
44
type PersistConfig,
55
pluginReportSchema,
6+
validate,
67
} from '@code-pushup/models';
78
import {
89
isVerbose,
@@ -57,6 +58,6 @@ export async function collectAndPersistReports(
5758
// validate report and throw if invalid
5859
reportResult.plugins.forEach(plugin => {
5960
// Running checks after persisting helps while debugging as you can check the invalid output after the error is thrown
60-
pluginReportSchema.parse(plugin);
61+
validate(pluginReportSchema, plugin);
6162
});
6263
}

packages/core/src/lib/compare.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type ReportsDiff,
88
type UploadConfig,
99
reportSchema,
10+
validate,
1011
} from '@code-pushup/models';
1112
import {
1213
type Diff,
@@ -49,8 +50,8 @@ export async function compareReportFiles(
4950
readJsonFile(options?.after ?? defaultInputPath('after')),
5051
]);
5152
const reports: Diff<Report> = {
52-
before: reportSchema.parse(reportBefore),
53-
after: reportSchema.parse(reportAfter),
53+
before: validate(reportSchema, reportBefore),
54+
after: validate(reportSchema, reportAfter),
5455
};
5556

5657
const diff = compareReports(reports);

packages/core/src/lib/implementation/execute-plugin.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,16 @@ import {
3434
*
3535
* @example
3636
* // plugin execution
37-
* const pluginCfg = pluginConfigSchema.parse({...});
37+
* const pluginCfg = validate(pluginConfigSchema, {...});
3838
* const output = await executePlugin(pluginCfg);
3939
*
40-
* @example
41-
* // error handling
42-
* try {
43-
* await executePlugin(pluginCfg);
44-
* } catch (e) {
45-
* console.error(e.message);
46-
* }
40+
* @example
41+
* // error handling
42+
* try {
43+
* await executePlugin(pluginCfg);
44+
* } catch (e) {
45+
* console.error(e.message);
46+
* }
4747
*/
4848
export async function executePlugin(
4949
pluginConfig: PluginConfig,
@@ -144,14 +144,14 @@ const wrapProgress = async (
144144
*
145145
* @example
146146
* // plugin execution
147-
* const plugins = [pluginConfigSchema.parse({...})];
147+
* const plugins = [validate(pluginConfigSchema, {...})];
148148
*
149149
* @example
150150
* // error handling
151151
* try {
152-
* await executePlugins(plugins);
152+
* await executePlugins(plugins);
153153
* } catch (e) {
154-
* console.error(e.message); // Plugin output is invalid
154+
* console.error(e.message); // Plugin output is invalid
155155
* }
156156
*
157157
*/

packages/models/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ Import the type definitions if using TypeScript:
6565
If you need runtime validation, use the underlying Zod schemas:
6666

6767
```ts
68-
import { coreConfigSchema } from '@code-pushup/models';
68+
import { coreConfigSchema, validate } from '@code-pushup/models';
6969

7070
const json = JSON.parse(readFileSync('code-pushup.config.json'));
71-
const config = coreConfigSchema.parse(json); // throws ZodError if invalid
71+
const config = validate(coreConfigSchema, json); // throws SchemaValidationError if invalid
7272
```

packages/models/src/lib/implementation/validate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class SchemaValidationError extends Error {
2828

2929
export function validate<T extends ZodType>(
3030
schema: T,
31-
data: z.input<T>,
31+
data: z.input<T> | {} | null | undefined, // loose autocomplete
3232
context: SchemaValidationContext = {},
3333
): z.output<T> {
3434
const result = schema.safeParse(data);

0 commit comments

Comments
 (0)