From ceecb60c3bbd5507b1f54faed001818639d9269c Mon Sep 17 00:00:00 2001 From: kyyy <60952577+rdjanuar@users.noreply.github.com> Date: Tue, 5 Nov 2024 22:13:25 +0700 Subject: [PATCH] feat(Form): apply transformations (#2460) --- src/runtime/components/forms/Form.vue | 214 +++++++++++++++++--------- src/runtime/types/form.d.ts | 5 + 2 files changed, 142 insertions(+), 77 deletions(-) diff --git a/src/runtime/components/forms/Form.vue b/src/runtime/components/forms/Form.vue index 967e8bde3e..a3b451a8da 100644 --- a/src/runtime/components/forms/Form.vue +++ b/src/runtime/components/forms/Form.vue @@ -14,7 +14,7 @@ import type { BaseSchema as ValibotSchema30, BaseSchemaAsync as ValibotSchemaAsy import type { GenericSchema as ValibotSchema31, GenericSchemaAsync as ValibotSchemaAsync31, SafeParser as ValibotSafeParser31, SafeParserAsync as ValibotSafeParserAsync31 } from 'valibot31' import type { GenericSchema as ValibotSchema, GenericSchemaAsync as ValibotSchemaAsync, SafeParser as ValibotSafeParser, SafeParserAsync as ValibotSafeParserAsync } from 'valibot' import type { Struct } from 'superstruct' -import type { FormError, FormEvent, FormEventType, FormSubmitEvent, FormErrorEvent, Form } from '../../types/form' +import type { FormError, FormEvent, FormEventType, FormSubmitEvent, FormErrorEvent, Form, ValidateReturnSchema } from '../../types/form' import { useId } from '#imports' class FormException extends Error { @@ -25,18 +25,19 @@ class FormException extends Error { } } +type Schema = PropType + | PropType> + | PropType + | PropType + | PropType + | PropType | ValibotSafeParserAsync31> + | PropType + | PropType | ValibotSafeParserAsync> | PropType> + export default defineComponent({ props: { schema: { - type: [Object, Function] as - | PropType - | PropType> - | PropType - | PropType - | PropType - | PropType | ValibotSafeParserAsync31> - | PropType - | PropType | ValibotSafeParserAsync> | PropType>, + type: [Object, Function] as Schema, default: undefined }, state: { @@ -72,6 +73,7 @@ export default defineComponent({ }) const errors = ref([]) + provide('form-errors', errors) provide('form-events', bus) const inputs = ref({}) @@ -81,18 +83,11 @@ export default defineComponent({ let errs = await props.validate(props.state) if (props.schema) { - if (isZodSchema(props.schema)) { - errs = errs.concat(await getZodErrors(props.state, props.schema)) - } else if (isYupSchema(props.schema)) { - errs = errs.concat(await getYupErrors(props.state, props.schema)) - } else if (isJoiSchema(props.schema)) { - errs = errs.concat(await getJoiErrors(props.state, props.schema)) - } else if (isValibotSchema(props.schema)) { - errs = errs.concat(await getValibotError(props.state, props.schema)) - } else if (isSuperStructSchema(props.schema)) { - errs = errs.concat(await getSuperStructErrors(props.state, props.schema)) + const { errors, result } = await parseSchema(props.state, props.schema as unknown as Schema) + if (errors) { + errs = errs.concat(errors) } else { - throw new Error('Form validation failed: Unsupported form schema') + Object.assign(props.state, result) } } @@ -207,95 +202,160 @@ function isSuperStructSchema(schema: any): schema is Struct { ) } -async function getYupErrors( +function isJoiSchema(schema: any): schema is JoiSchema { + return schema.validateAsync !== undefined && schema.id !== undefined +} + +function isJoiError(error: any): error is JoiError { + return error.isJoi === true +} + +function isValibotSchema(schema: any): schema is ValibotSchema30 | ValibotSchemaAsync30 | ValibotSchema31 | ValibotSchemaAsync31 | ValibotSafeParser31 | ValibotSafeParserAsync31 | ValibotSchema | ValibotSchemaAsync | ValibotSafeParser | ValibotSafeParserAsync { + return '_parse' in schema || '_run' in schema || (typeof schema === 'function' && 'schema' in schema) +} + +function isZodSchema(schema: any): schema is ZodSchema { + return schema.parse !== undefined +} + +async function validateValibotSchema( state: any, - schema: YupObjectSchema -): Promise { + schema: ValibotSchema30 | ValibotSchemaAsync30 | ValibotSchema31 | ValibotSchemaAsync31 | ValibotSafeParser31 | ValibotSafeParserAsync31 | ValibotSchema | ValibotSchemaAsync | ValibotSafeParser | ValibotSafeParserAsync +): Promise> { + const result = await ('_parse' in schema ? schema._parse(state) : '_run' in schema ? schema._run({ typed: false, value: state }, {}) : schema(state)) + + if (!result.issues || result.issues.length === 0) { + const output = ('output' in result + ? result.output + : 'value' in result + ? result.value + : null) + return { + errors: null, + result: output + } + } + + const errors = result.issues.map(issue => ({ + path: issue.path?.map(item => item.key).join('.') || '', + message: issue.message + })) + + return { + errors, + result: null + } +} + +async function validateJoiSchema( + state: any, + schema: JoiSchema +): Promise> { try { - await schema.validate(state, { abortEarly: false }) - return [] + await schema.validateAsync(state, { abortEarly: false }) + return { + errors: null, + result: state + } } catch (error) { - if (isYupError(error)) { - return error.inner.map(issue => ({ - path: issue.path ?? '', + if (isJoiError(error)) { + const errors = error.details.map(issue => ({ + path: issue.path.join('.'), message: issue.message })) + + return { + errors, + result: null + } } else { throw error } } } -function isZodSchema(schema: any): schema is ZodSchema { - return schema.parse !== undefined -} - -async function getSuperStructErrors(state: any, schema: Struct): Promise { - const [err] = schema.validate(state) - if (err) { - const errors = err.failures() - return errors.map(error => ({ - message: error.message, - path: error.path.join('.') - })) - } - return [] -} - -async function getZodErrors( +async function validateZodSchema( state: any, schema: ZodSchema -): Promise { +): Promise> { const result = await schema.safeParseAsync(state) if (result.success === false) { - return result.error.issues.map(issue => ({ + const errors = result.error.issues.map(issue => ({ path: issue.path.join('.'), message: issue.message })) + + return { + errors, + result: null + } + } + return { + result: result.data, + errors: null } - return [] } -function isJoiSchema(schema: any): schema is JoiSchema { - return schema.validateAsync !== undefined && schema.id !== undefined -} +async function validateSuperstructSchema(state: any, schema: Struct): Promise> { + const [err, result] = schema.validate(state) + if (err) { + const errors = err.failures().map(error => ({ + message: error.message, + path: error.path.join('.') + })) -function isJoiError(error: any): error is JoiError { - return error.isJoi === true + return { + errors, + result: null + } + } + + return { + errors: null, + result + } } -async function getJoiErrors( +async function validateYupSchema( state: any, - schema: JoiSchema -): Promise { + schema: YupObjectSchema +): Promise> { try { - await schema.validateAsync(state, { abortEarly: false }) - return [] + const result = schema.validateSync(state, { abortEarly: false }) + return { + errors: null, + result + } } catch (error) { - if (isJoiError(error)) { - return error.details.map(detail => ({ - path: detail.path.join('.'), - message: detail.message + if (isYupError(error)) { + const errors = error.inner.map(issue => ({ + path: issue.path ?? '', + message: issue.message })) + + return { + errors, + result: null + } } else { throw error } } } -function isValibotSchema(schema: any): schema is ValibotSchema30 | ValibotSchemaAsync30 | ValibotSchema31 | ValibotSchemaAsync31 | ValibotSafeParser31 | ValibotSafeParserAsync31 | ValibotSchema | ValibotSchemaAsync | ValibotSafeParser | ValibotSafeParserAsync { - return '_parse' in schema || '_run' in schema || (typeof schema === 'function' && 'schema' in schema) -} - -async function getValibotError( - state: any, - schema: ValibotSchema30 | ValibotSchemaAsync30 | ValibotSchema31 | ValibotSchemaAsync31 | ValibotSafeParser31 | ValibotSafeParserAsync31 | ValibotSchema | ValibotSchemaAsync | ValibotSafeParser | ValibotSafeParserAsync -): Promise { - const result = await ('_parse' in schema ? schema._parse(state) : '_run' in schema ? schema._run({ typed: false, value: state }, {}) : schema(state)) - return result.issues?.map(issue => ({ - // We know that the key for a form schema is always a string or a number - path: issue.path?.map(item => item.key).join('.') || '', - message: issue.message - })) || [] +function parseSchema(state: any, schema: Schema): Promise> { + if (isZodSchema(schema)) { + return validateZodSchema(state, schema) + } else if (isJoiSchema(schema)) { + return validateJoiSchema(state, schema) + } else if (isValibotSchema(schema)) { + return validateValibotSchema(state, schema) + } else if (isYupSchema(schema)) { + return validateYupSchema(state, schema) + } else if (isSuperStructSchema(schema)) { + return validateSuperstructSchema(state, schema) + } else { + throw new Error('Form validation failed: Unsupported form schema') + } } diff --git a/src/runtime/types/form.d.ts b/src/runtime/types/form.d.ts index 05dbbc9a9b..14eeb79794 100644 --- a/src/runtime/types/form.d.ts +++ b/src/runtime/types/form.d.ts @@ -5,6 +5,11 @@ export interface FormError { message: string } +export interface ValidateReturnSchema { + result: T + errors: FormError[] +} + export interface FormErrorWithId extends FormError { id: string }