-
-
Notifications
You must be signed in to change notification settings - Fork 330
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: spread valibot errors into fields, nested and arrays
- Loading branch information
1 parent
5fdfa85
commit 198c08a
Showing
2 changed files
with
193 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,63 +1,91 @@ | ||
import { getDotPath, safeParse, safeParseAsync } from 'valibot' | ||
import { setBy } from '@tanstack/form-core' | ||
import type { Validator, ValidatorAdapterParams } from '@tanstack/form-core' | ||
import type { | ||
ValidationError, | ||
Validator, | ||
ValidatorAdapterParams, | ||
} from '@tanstack/form-core' | ||
import type { | ||
BaseIssue, | ||
GenericIssue, | ||
GenericSchema, | ||
GenericSchemaAsync, | ||
ValiError, | ||
} from 'valibot' | ||
|
||
type Params = ValidatorAdapterParams<GenericIssue> | ||
type TransformFn = NonNullable<Params['transformErrors']> | ||
|
||
export function prefixSchemaToErrors(errors: Array<BaseIssue<unknown>>) { | ||
let schema = {} as object | ||
for (const valibotError of errors) { | ||
schema = setBy(schema, getDotPath(valibotError), () => valibotError.message) | ||
} | ||
return schema | ||
} | ||
export function prefixSchemaToErrors( | ||
valiErrors: GenericIssue[], | ||
transformErrors: TransformFn, | ||
) { | ||
const schema = new Map<string, GenericIssue[]>() | ||
|
||
for (const valiError of valiErrors) { | ||
if (!valiError.path) continue | ||
|
||
export function defaultFormTransformer(errors: Array<BaseIssue<unknown>>) { | ||
return { | ||
form: mapIssuesToSingleString(errors), | ||
fields: prefixSchemaToErrors(errors), | ||
const path = valiError.path | ||
.map(({ key: segment }) => | ||
typeof segment === 'number' ? `[${segment}]` : segment, | ||
) | ||
.join('.') | ||
.replace(/\.\[/g, '[') | ||
schema.set(path, (schema.get(path) ?? []).concat(valiError)) | ||
} | ||
|
||
const transformedSchema = {} as Record<string, ValidationError> | ||
|
||
schema.forEach((value, key) => { | ||
transformedSchema[key] = transformErrors(value) | ||
}) | ||
|
||
return transformedSchema | ||
} | ||
|
||
export const mapIssuesToSingleString = (errors: Array<BaseIssue<unknown>>) => | ||
errors.map((error) => error.message).join(', ') | ||
export function defaultFormTransformer(transformErrors: TransformFn) { | ||
return (zodErrors: GenericIssue[]) => ({ | ||
form: transformErrors(zodErrors), | ||
fields: prefixSchemaToErrors(zodErrors, transformErrors), | ||
}) | ||
} | ||
|
||
export const valibotValidator = | ||
( | ||
params: Params = {}, | ||
): Validator<unknown, GenericSchema | GenericSchemaAsync> => | ||
() => { | ||
const transformFieldErrors = | ||
params.transformErrors ?? | ||
((issues: GenericIssue[]) => | ||
issues.map((issue) => issue.message).join(', ')) | ||
|
||
const getTransformStrategy = (validationSource: 'form' | 'field') => | ||
validationSource === 'form' | ||
? defaultFormTransformer(transformFieldErrors) | ||
: transformFieldErrors | ||
|
||
return { | ||
validate({ value, validationSource }, fn) { | ||
if (fn.async) return | ||
const result = safeParse(fn, value, { | ||
abortPipeEarly: false, | ||
}) | ||
if (result.success) return | ||
const transformErrors = params.transformErrors | ||
? params.transformErrors | ||
: validationSource === 'form' | ||
? defaultFormTransformer | ||
: mapIssuesToSingleString | ||
return transformErrors(result.issues) | ||
|
||
const transformer = getTransformStrategy(validationSource) | ||
|
||
return transformer(result.issues) | ||
}, | ||
async validateAsync({ value, validationSource }, fn) { | ||
const result = await safeParseAsync(fn, value, { | ||
abortPipeEarly: false, | ||
}) | ||
if (result.success) return | ||
const transformErrors = params.transformErrors | ||
? params.transformErrors | ||
: validationSource === 'form' | ||
? defaultFormTransformer | ||
: mapIssuesToSingleString | ||
return transformErrors(result.issues) | ||
|
||
const transformer = getTransformStrategy(validationSource) | ||
|
||
return transformer(result.issues) | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters