UNPKG

react-server-actions

Version:

A package for working with actions in React and Next.js

183 lines 7.48 kB
import { z } from 'zod'; /** * Get the html5 validation attributes from a Zod schema field * @param schema - The Zod schema * @param path - The path to the field in the schema * @returns The validation attributes for the field * @note It would be cool to infer also the "type" attribute of the field, but this would not be consistent because a zod rule is not a 1-1 relation with an html input. * For example, a zod.number() could be an <input type="number" /> but also a <select>. A z.date() could be represented by a <input type="date" /> but also a <input type="datetime-local" />. * We could make a "guess" based on the zod rule, and then could be overriden by the user, but this would lead to confusion. * So we leave it as a parameter for now. */ export function getZodValidationAttributes(schema, path, options) { let type = 'string'; const attrs = {}; // First handle object type to get to the actual field if (schema instanceof z.ZodObject && path.length > 0) { const shape = schema.shape; const field = shape[path[0]]; return field ? getZodValidationAttributes(field, path.slice(1), options) : { type, attrs }; } if (schema instanceof z.ZodTransform) { return getZodValidationAttributes(schema._def.in, path, options); } // Now we're at the actual field, check if it's optional/nullable const isOptionalType = schema instanceof z.ZodOptional; const isNullableType = schema instanceof z.ZodNullable; // If it's an optional/nullable type, get attributes from the inner type but don't set required if (isOptionalType || isNullableType) { const innerSchema = schema._def.innerType; const innerAttrs = getZodValidationAttributes(innerSchema, path, options); delete innerAttrs.attrs.required; return innerAttrs; } // Handle default values - if it's a ZodDefault, get the inner type and don't set required let actualSchema = schema; if (schema instanceof z.ZodDefault) { actualSchema = schema._def.innerType; // Don't set required for fields with default values } else { attrs.required = true; } // Check if it's a stringbool (ZodPipe with string in and boolean out) // This needs to be checked after unwrapping optional/default but before checking ZodString if (actualSchema instanceof z.ZodPipe) { const pipeDef = actualSchema._def; const inType = pipeDef.in?.type || pipeDef.in?._def?.type; const outType = pipeDef.out?.type || pipeDef.out?._def?.type; // If it's a string to boolean pipe, it's a stringbool if (inType === 'string' && outType === 'boolean') { type = 'boolean'; attrs.type = 'checkbox'; if (!options?.inferTypeAttr) { delete attrs.type; } return { type, attrs }; } // Otherwise, unwrap and continue with inner type return getZodValidationAttributes(actualSchema._def.in, path, options); } if (actualSchema instanceof z.ZodString) { type = 'string'; attrs.type = 'text'; // Access checks through the schema's internal structure const checks = actualSchema._def?.checks; if (checks) { for (const check of checks) { const checkDef = check._zod?.def; if (checkDef) { if (checkDef.check === 'min_length') { attrs.minLength = checkDef.minimum; } if (checkDef.check === 'max_length') { attrs.maxLength = checkDef.maximum; } if (checkDef.format === 'email') { attrs.type = 'email'; } if (checkDef.format === 'url') { attrs.type = 'url'; } } } } } else if (actualSchema instanceof z.ZodNumber) { type = 'number'; attrs.type = 'number'; const checks = actualSchema._def?.checks; if (checks) { for (const check of checks) { const checkDef = check._zod?.def; if (checkDef) { if (checkDef.check === 'greater_than' && checkDef.inclusive) { attrs.min = checkDef.value; } if (checkDef.check === 'less_than' && checkDef.inclusive) { attrs.max = checkDef.value; } if (checkDef.check === 'greater_than' && !checkDef.inclusive) { attrs.min = checkDef.value + 1; } if (checkDef.check === 'less_than' && !checkDef.inclusive) { attrs.max = checkDef.value - 1; } if (checkDef.format === 'safeint') { attrs.step = 1; } } } } } else if (actualSchema instanceof z.ZodDate) { type = 'date'; attrs.type = 'date'; const checks = actualSchema._def?.checks; if (checks) { for (const check of checks) { const checkDef = check._zod?.def; if (checkDef) { if (checkDef.check === 'greater_than' && checkDef.inclusive) { const minDate = new Date(checkDef.value) .toISOString() .split('T')[0]; if (minDate) { attrs.min = minDate; } } if (checkDef.check === 'less_than' && checkDef.inclusive) { const maxDate = new Date(checkDef.value) .toISOString() .split('T')[0]; if (maxDate) { attrs.max = maxDate; } } } } } } else if (actualSchema instanceof z.ZodBoolean) { type = 'boolean'; attrs.type = 'checkbox'; } else if (actualSchema instanceof z.ZodEnum) { type = 'enum'; attrs.type = 'radio'; } else if (actualSchema instanceof z.ZodFile) { type = 'file'; attrs.type = 'file'; } // If not specified, remove the type attribute if (!options?.inferTypeAttr) { delete attrs.type; } return { type, attrs }; } /** * Convert a date to an <input type="date"> default value * @param date - The date to convert * @returns The input default value */ export const dateToInputDefaultValue = (date) => { const newDate = date ? new Date(date) : new Date(); return new Date(newDate.getTime() - newDate.getTimezoneOffset() * 60000) .toISOString() .split('T')[0]; }; /** * Convert a date to an <input type="datetime-local"> default value * @param date - The date to convert * @returns The input default value */ export const datetimeToInputDefaultValue = (date) => { const newDate = date ? new Date(date) : new Date(); return new Date(newDate.getTime() - newDate.getTimezoneOffset() * 60000) .toISOString() .slice(0, -1); }; //# sourceMappingURL=helpers.js.map