Skip to content

Commit 8bbe219

Browse files
authored
chore: extract a zod error format utility for v3/v4 uniform handling (#2232)
1 parent e798397 commit 8bbe219

File tree

10 files changed

+51
-25
lines changed

10 files changed

+51
-25
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2022 ZenStack
3+
Copyright (c) 2022-2025 ZenStack
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

packages/LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2022 ZenStack
3+
Copyright (c) 2022-2025 ZenStack
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

packages/plugins/openapi/src/generator-base.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import { getZodErrorMessage } from '@zenstackhq/runtime/local-helpers';
12
import { PluginError, getDataModels, hasAttribute, type PluginOptions, type PluginResult } from '@zenstackhq/sdk';
23
import { Model } from '@zenstackhq/sdk/ast';
34
import type { DMMF } from '@zenstackhq/sdk/prisma';
45
import type { OpenAPIV3_1 as OAPI } from 'openapi-types';
56
import semver from 'semver';
6-
import { fromZodError } from 'zod-validation-error/v3';
77
import { name } from '.';
88
import { SecuritySchemesSchema } from './schema';
99

@@ -94,7 +94,7 @@ export abstract class OpenAPIGeneratorBase {
9494
if (securitySchemes) {
9595
const parsed = SecuritySchemesSchema.safeParse(securitySchemes);
9696
if (!parsed.success) {
97-
throw new PluginError(name, `"securitySchemes" option is invalid: ${fromZodError(parsed.error)}`);
97+
throw new PluginError(name, `"securitySchemes" option is invalid: ${getZodErrorMessage(parsed.error)}`);
9898
}
9999
return parsed.data;
100100
}

packages/runtime/src/enhancements/node/policy/handler.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22

33
import deepmerge from 'deepmerge';
4-
import { fromZodError } from 'zod-validation-error/v3';
54
import { CrudFailureReason } from '../../../constants';
65
import {
76
ModelDataVisitor,
@@ -15,7 +14,7 @@ import {
1514
type FieldInfo,
1615
type ModelMeta,
1716
} from '../../../cross';
18-
import { invariant, lowerCaseFirst, upperCaseFirst } from '../../../local-helpers';
17+
import { getZodErrorMessage, invariant, lowerCaseFirst, upperCaseFirst } from '../../../local-helpers';
1918
import { EnhancementContext, PolicyOperationKind, type CrudContract, type DbClientContract } from '../../../types';
2019
import type { InternalEnhancementOptions } from '../create-enhancement';
2120
import { Logger } from '../logger';
@@ -420,7 +419,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
420419
throw this.policyUtils.deniedByPolicy(
421420
model,
422421
'create',
423-
`input failed validation: ${fromZodError(err)}`,
422+
`input failed validation: ${getZodErrorMessage(err)}`,
424423
CrudFailureReason.DATA_VALIDATION_VIOLATION,
425424
err
426425
);
@@ -1267,7 +1266,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
12671266
throw this.policyUtils.deniedByPolicy(
12681267
model,
12691268
'update',
1270-
`input failed validation: ${fromZodError(err)}`,
1269+
`input failed validation: ${getZodErrorMessage(err)}`,
12711270
CrudFailureReason.DATA_VALIDATION_VIOLATION,
12721271
err
12731272
);

packages/runtime/src/enhancements/node/policy/policy-utils.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import deepmerge from 'deepmerge';
44
import { z, type ZodError, type ZodObject, type ZodSchema } from 'zod';
5-
import { fromZodError } from 'zod-validation-error/v3';
65
import { CrudFailureReason, PrismaErrorCode } from '../../../constants';
76
import {
87
clone,
@@ -14,7 +13,13 @@ import {
1413
type FieldInfo,
1514
type ModelMeta,
1615
} from '../../../cross';
17-
import { isPlainObject, lowerCaseFirst, simpleTraverse, upperCaseFirst } from '../../../local-helpers';
16+
import {
17+
getZodErrorMessage,
18+
isPlainObject,
19+
lowerCaseFirst,
20+
simpleTraverse,
21+
upperCaseFirst,
22+
} from '../../../local-helpers';
1823
import {
1924
AuthUser,
2025
CrudContract,
@@ -935,7 +940,7 @@ export class PolicyUtil extends QueryUtils {
935940
throw this.deniedByPolicy(
936941
model,
937942
operation,
938-
`entity ${formatObject(uniqueFilter, false)} failed validation: [${fromZodError(err)}]`,
943+
`entity ${formatObject(uniqueFilter, false)} failed validation: [${getZodErrorMessage(err)}]`,
939944
CrudFailureReason.DATA_VALIDATION_VIOLATION,
940945
err
941946
);
@@ -1440,7 +1445,7 @@ export class PolicyUtil extends QueryUtils {
14401445
if (!parseResult.success) {
14411446
if (this.logger.enabled('info')) {
14421447
this.logger.info(
1443-
`entity ${model} failed validation for operation ${kind}: ${fromZodError(parseResult.error)}`
1448+
`entity ${model} failed validation for operation ${kind}: ${getZodErrorMessage(parseResult.error)}`
14441449
);
14451450
}
14461451
onError(parseResult.error);
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
export * from './simple-traverse';
2-
export * from './sleep';
31
export * from './is-plain-object';
42
export * from './lower-case-first';
5-
export * from './upper-case-first';
63
export * from './param-case';
4+
export * from './simple-traverse';
5+
export * from './sleep';
76
export * from './tiny-invariant';
7+
export * from './upper-case-first';
8+
export * from './zod-utils';
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { type ZodError } from 'zod';
2+
import { fromZodError as fromZodErrorV3 } from 'zod-validation-error/v3';
3+
import { fromZodError as fromZodErrorV4 } from 'zod-validation-error/v4';
4+
import { type ZodError as Zod4Error } from 'zod/v4';
5+
6+
/**
7+
* Formats a Zod error message for better readability. Compatible with both Zod v3 and v4.
8+
*/
9+
export function getZodErrorMessage(err: unknown): string {
10+
if (!(err instanceof Error)) {
11+
return 'Unknown error';
12+
}
13+
14+
try {
15+
if ('_zod' in err) {
16+
return fromZodErrorV4(err as Zod4Error).message;
17+
} else {
18+
return fromZodErrorV3(err as ZodError).message;
19+
}
20+
} catch {
21+
return err.message;
22+
}
23+
}

packages/runtime/src/validation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { z } from 'zod';
2-
import { fromZodError } from 'zod-validation-error/v3';
2+
import { getZodErrorMessage } from './local-helpers';
33

44
/**
55
* Error indicating violations of field-level constraints
@@ -15,7 +15,7 @@ export function validate(validator: z.ZodType, data: unknown) {
1515
try {
1616
validator.parse(data);
1717
} catch (err) {
18-
throw new ValidationError(fromZodError(err as z.ZodError).message);
18+
throw new ValidationError(getZodErrorMessage(err as z.ZodError));
1919
}
2020
}
2121

packages/schema/src/cli/config.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import fs from 'fs';
22
import z, { ZodError } from 'zod';
3-
import { fromZodError } from 'zod-validation-error/v3';
43
import { CliError } from './cli-error';
54

65
// TODO: future use
@@ -28,7 +27,7 @@ export function loadConfig(filename: string) {
2827
throw new CliError(`Config is not a valid JSON file: ${filename}`);
2928
}
3029
if (err instanceof ZodError) {
31-
throw new CliError(`Config file ${filename} is not valid: ${fromZodError(err)}`);
30+
throw new CliError(`Config file ${filename} is not valid: ${err}`);
3231
}
3332
throw new CliError(`Error loading config: ${filename}`);
3433
}

packages/server/src/api/rest/index.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@ import {
77
PrismaErrorCode,
88
clone,
99
enumerate,
10-
requireField,
1110
getIdFields,
1211
isPrismaClientKnownRequestError,
12+
requireField,
1313
} from '@zenstackhq/runtime';
14-
import { lowerCaseFirst, upperCaseFirst, paramCase } from '@zenstackhq/runtime/local-helpers';
14+
import { getZodErrorMessage, lowerCaseFirst, paramCase, upperCaseFirst } from '@zenstackhq/runtime/local-helpers';
1515
import SuperJSON from 'superjson';
1616
import { Linker, Paginator, Relator, Serializer, SerializerOptions } from 'ts-japi';
1717
import UrlPattern from 'url-pattern';
1818
import z, { ZodError } from 'zod';
19-
import { fromZodError } from 'zod-validation-error/v3';
2019
import { LoggerConfig, Response } from '../../types';
2120
import { APIHandlerBase, RequestContext } from '../base';
2221
import { logWarning, registerCustomSerializers } from '../utils';
@@ -821,7 +820,7 @@ class RequestHandler extends APIHandlerBase {
821820
return {
822821
error: this.makeError(
823822
'invalidPayload',
824-
fromZodError(parsed.error).message,
823+
getZodErrorMessage(parsed.error),
825824
422,
826825
CrudFailureReason.DATA_VALIDATION_VIOLATION,
827826
parsed.error
@@ -1022,7 +1021,7 @@ class RequestHandler extends APIHandlerBase {
10221021
if (!parsed.success) {
10231022
return this.makeError(
10241023
'invalidPayload',
1025-
fromZodError(parsed.error).message,
1024+
getZodErrorMessage(parsed.error),
10261025
undefined,
10271026
CrudFailureReason.DATA_VALIDATION_VIOLATION,
10281027
parsed.error
@@ -1053,7 +1052,7 @@ class RequestHandler extends APIHandlerBase {
10531052
if (!parsed.success) {
10541053
return this.makeError(
10551054
'invalidPayload',
1056-
fromZodError(parsed.error).message,
1055+
getZodErrorMessage(parsed.error),
10571056
undefined,
10581057
CrudFailureReason.DATA_VALIDATION_VIOLATION,
10591058
parsed.error

0 commit comments

Comments
 (0)