Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 ZenStack
Copyright (c) 2022-2025 ZenStack

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion packages/LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 ZenStack
Copyright (c) 2025 ZenStack

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
4 changes: 2 additions & 2 deletions packages/plugins/openapi/src/generator-base.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { getZodErrorMessage } from '@zenstackhq/runtime/local-helpers';
import { PluginError, getDataModels, hasAttribute, type PluginOptions, type PluginResult } from '@zenstackhq/sdk';
import { Model } from '@zenstackhq/sdk/ast';
import type { DMMF } from '@zenstackhq/sdk/prisma';
import type { OpenAPIV3_1 as OAPI } from 'openapi-types';
import semver from 'semver';
import { fromZodError } from 'zod-validation-error/v3';
import { name } from '.';
import { SecuritySchemesSchema } from './schema';

Expand Down Expand Up @@ -94,7 +94,7 @@ export abstract class OpenAPIGeneratorBase {
if (securitySchemes) {
const parsed = SecuritySchemesSchema.safeParse(securitySchemes);
if (!parsed.success) {
throw new PluginError(name, `"securitySchemes" option is invalid: ${fromZodError(parsed.error)}`);
throw new PluginError(name, `"securitySchemes" option is invalid: ${getZodErrorMessage(parsed.error)}`);
}
return parsed.data;
}
Expand Down
7 changes: 3 additions & 4 deletions packages/runtime/src/enhancements/node/policy/handler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import deepmerge from 'deepmerge';
import { fromZodError } from 'zod-validation-error/v3';
import { CrudFailureReason } from '../../../constants';
import {
ModelDataVisitor,
Expand All @@ -15,7 +14,7 @@ import {
type FieldInfo,
type ModelMeta,
} from '../../../cross';
import { invariant, lowerCaseFirst, upperCaseFirst } from '../../../local-helpers';
import { getZodErrorMessage, invariant, lowerCaseFirst, upperCaseFirst } from '../../../local-helpers';
import { EnhancementContext, PolicyOperationKind, type CrudContract, type DbClientContract } from '../../../types';
import type { InternalEnhancementOptions } from '../create-enhancement';
import { Logger } from '../logger';
Expand Down Expand Up @@ -420,7 +419,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
throw this.policyUtils.deniedByPolicy(
model,
'create',
`input failed validation: ${fromZodError(err)}`,
`input failed validation: ${getZodErrorMessage(err)}`,
CrudFailureReason.DATA_VALIDATION_VIOLATION,
err
);
Expand Down Expand Up @@ -1267,7 +1266,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
throw this.policyUtils.deniedByPolicy(
model,
'update',
`input failed validation: ${fromZodError(err)}`,
`input failed validation: ${getZodErrorMessage(err)}`,
CrudFailureReason.DATA_VALIDATION_VIOLATION,
err
);
Expand Down
13 changes: 9 additions & 4 deletions packages/runtime/src/enhancements/node/policy/policy-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import deepmerge from 'deepmerge';
import { z, type ZodError, type ZodObject, type ZodSchema } from 'zod';
import { fromZodError } from 'zod-validation-error/v3';
import { CrudFailureReason, PrismaErrorCode } from '../../../constants';
import {
clone,
Expand All @@ -14,7 +13,13 @@ import {
type FieldInfo,
type ModelMeta,
} from '../../../cross';
import { isPlainObject, lowerCaseFirst, simpleTraverse, upperCaseFirst } from '../../../local-helpers';
import {
getZodErrorMessage,
isPlainObject,
lowerCaseFirst,
simpleTraverse,
upperCaseFirst,
} from '../../../local-helpers';
import {
AuthUser,
CrudContract,
Expand Down Expand Up @@ -935,7 +940,7 @@ export class PolicyUtil extends QueryUtils {
throw this.deniedByPolicy(
model,
operation,
`entity ${formatObject(uniqueFilter, false)} failed validation: [${fromZodError(err)}]`,
`entity ${formatObject(uniqueFilter, false)} failed validation: [${getZodErrorMessage(err)}]`,
CrudFailureReason.DATA_VALIDATION_VIOLATION,
err
);
Expand Down Expand Up @@ -1440,7 +1445,7 @@ export class PolicyUtil extends QueryUtils {
if (!parseResult.success) {
if (this.logger.enabled('info')) {
this.logger.info(
`entity ${model} failed validation for operation ${kind}: ${fromZodError(parseResult.error)}`
`entity ${model} failed validation for operation ${kind}: ${getZodErrorMessage(parseResult.error)}`
);
}
onError(parseResult.error);
Expand Down
7 changes: 4 additions & 3 deletions packages/runtime/src/local-helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export * from './simple-traverse';
export * from './sleep';
export * from './is-plain-object';
export * from './lower-case-first';
export * from './upper-case-first';
export * from './param-case';
export * from './simple-traverse';
export * from './sleep';
export * from './tiny-invariant';
export * from './upper-case-first';
export * from './zod-utils';
23 changes: 23 additions & 0 deletions packages/runtime/src/local-helpers/zod-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { type ZodError } from 'zod';
import { fromZodError as fromZodErrorV3 } from 'zod-validation-error/v3';
import { fromZodError as fromZodErrorV4 } from 'zod-validation-error/v4';
import { type ZodError as Zod4Error } from 'zod/v4';

/**
* Formats a Zod error message for better readability. Compatible with both Zod v3 and v4.
*/
export function getZodErrorMessage(err: unknown): string {
if (!(err instanceof Error)) {
return 'Unknown error';
}

try {
if ('_zod' in err) {
return fromZodErrorV4(err as Zod4Error).message;
} else {
return fromZodErrorV3(err as ZodError).message;
}
} catch {
return err.message;
}
}
4 changes: 2 additions & 2 deletions packages/runtime/src/validation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { z } from 'zod';
import { fromZodError } from 'zod-validation-error/v3';
import { getZodErrorMessage } from './local-helpers';

/**
* Error indicating violations of field-level constraints
Expand All @@ -15,7 +15,7 @@ export function validate(validator: z.ZodType, data: unknown) {
try {
validator.parse(data);
} catch (err) {
throw new ValidationError(fromZodError(err as z.ZodError).message);
throw new ValidationError(getZodErrorMessage(err as z.ZodError));
}
}

Expand Down
3 changes: 1 addition & 2 deletions packages/schema/src/cli/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import fs from 'fs';
import z, { ZodError } from 'zod';
import { fromZodError } from 'zod-validation-error/v3';
import { CliError } from './cli-error';

// TODO: future use
Expand Down Expand Up @@ -28,7 +27,7 @@ export function loadConfig(filename: string) {
throw new CliError(`Config is not a valid JSON file: ${filename}`);
}
if (err instanceof ZodError) {
throw new CliError(`Config file ${filename} is not valid: ${fromZodError(err)}`);
throw new CliError(`Config file ${filename} is not valid: ${err}`);
}
throw new CliError(`Error loading config: ${filename}`);
}
Expand Down
11 changes: 5 additions & 6 deletions packages/server/src/api/rest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ import {
PrismaErrorCode,
clone,
enumerate,
requireField,
getIdFields,
isPrismaClientKnownRequestError,
requireField,
} from '@zenstackhq/runtime';
import { lowerCaseFirst, upperCaseFirst, paramCase } from '@zenstackhq/runtime/local-helpers';
import { getZodErrorMessage, lowerCaseFirst, paramCase, upperCaseFirst } from '@zenstackhq/runtime/local-helpers';
import SuperJSON from 'superjson';
import { Linker, Paginator, Relator, Serializer, SerializerOptions } from 'ts-japi';
import UrlPattern from 'url-pattern';
import z, { ZodError } from 'zod';
import { fromZodError } from 'zod-validation-error/v3';
import { LoggerConfig, Response } from '../../types';
import { APIHandlerBase, RequestContext } from '../base';
import { logWarning, registerCustomSerializers } from '../utils';
Expand Down Expand Up @@ -821,7 +820,7 @@ class RequestHandler extends APIHandlerBase {
return {
error: this.makeError(
'invalidPayload',
fromZodError(parsed.error).message,
getZodErrorMessage(parsed.error),
422,
CrudFailureReason.DATA_VALIDATION_VIOLATION,
parsed.error
Expand Down Expand Up @@ -1022,7 +1021,7 @@ class RequestHandler extends APIHandlerBase {
if (!parsed.success) {
return this.makeError(
'invalidPayload',
fromZodError(parsed.error).message,
getZodErrorMessage(parsed.error),
undefined,
CrudFailureReason.DATA_VALIDATION_VIOLATION,
parsed.error
Expand Down Expand Up @@ -1053,7 +1052,7 @@ class RequestHandler extends APIHandlerBase {
if (!parsed.success) {
return this.makeError(
'invalidPayload',
fromZodError(parsed.error).message,
getZodErrorMessage(parsed.error),
undefined,
CrudFailureReason.DATA_VALIDATION_VIOLATION,
parsed.error
Expand Down
Loading