From e3da300e8e87e115f6570b131009b1d8b648cb5d Mon Sep 17 00:00:00 2001 From: upstream Date: Sat, 17 Oct 2026 03:33:29 +0200 Subject: [PATCH] Upstream payloadcms/payload state of the touched files before e34ea0b1e0 --- .../payload/src/errors/ValidationError.ts | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 packages/payload/src/errors/ValidationError.ts diff --git a/packages/payload/src/errors/ValidationError.ts b/packages/payload/src/errors/ValidationError.ts new file mode 100644 index 0000000..e723028 --- /dev/null +++ b/packages/payload/src/errors/ValidationError.ts @@ -0,0 +1,93 @@ +import type { TFunction } from '@payloadcms/translations' + +import { en } from '@payloadcms/translations/languages/en' + +import type { LabelFunction, StaticLabel } from '../config/types.js' +import type { PayloadRequest } from '../types/index.js' + +import { httpStatus } from '../utilities/httpStatus.js' +import { APIError } from './APIError.js' + +/** @deprecated Use `instanceof ValidationError` instead of name comparison. */ +export const ValidationErrorName = 'ValidationError' + +export type ValidationFieldError = { + /** Configured field label, when available. */ + label?: LabelFunction | StaticLabel + /** Locale for a localized validation pass. Omitted for non-localized validation. */ + locale?: string + /** Error message to display for this field. */ + message: string + /** Dot-separated path to the invalid field. */ + path: string + /** Database table associated with the invalid field, when applicable. */ + tableName?: string +} + +export class ValidationError extends APIError<{ + collection?: string + errors: ValidationFieldError[] + global?: string +}> { + constructor( + results: { + collection?: string + errors: ValidationFieldError[] + global?: string + id?: number | string + /** + * req needs to be passed through (if you have one) in order to resolve label functions that may be part of the errors array + */ + req?: Partial + }, + t?: TFunction, + ) { + const message = t + ? t('error:followingFieldsInvalid', { count: results.errors.length }) + : results.errors.length === 1 + ? en.translations.error.followingFieldsInvalid_one + : en.translations.error.followingFieldsInvalid_other + + const req = results.req + // delete to avoid logging the whole req + delete results['req'] + + const locales = results.errors + .map((fieldError) => fieldError.locale) + .filter((locale): locale is string => Boolean(locale)) + const spansMultipleLocales = new Set(locales).size > 1 + + super( + `${message} ${results.errors + .map((f) => { + const localePrefix = spansMultipleLocales && f.locale ? `[${f.locale}] ` : '' + + if (f.label) { + if (typeof f.label === 'function') { + if (!req || !req.i18n || !req.t) { + return `${localePrefix}${f.path}` + } + + return `${localePrefix}${f.label({ i18n: req.i18n, t: req.t })}` + } + + if (typeof f.label === 'object') { + if (req?.i18n?.language) { + return `${localePrefix}${f.label[req.i18n.language]}` + } + + return `${localePrefix}${f.label[Object.keys(f.label)[0]!]}` + } + + return `${localePrefix}${f.label}` + } + + return `${localePrefix}${f.path}` + }) + .join(', ')}`, + httpStatus.BAD_REQUEST, + results, + ) + this.name = 'ValidationError' + } +}