From b50e30a8270ecb5ecb383af4d9e0545622673eab Mon Sep 17 00:00:00 2001 From: apple-yagi Date: Mon, 3 Feb 2025 20:59:26 +0900 Subject: [PATCH] feat(standard-schema): add standard-schema resolver --- README.md | 67 ++++++++++++++ bun.lockb | Bin 328709 -> 329083 bytes config/node-13-exports.js | 1 + effect-ts/src/effect-ts.ts | 2 +- package.json | 16 +++- standard-schema/package.json | 18 ++++ .../src/__tests__/Form-native-validation.tsx | 82 ++++++++++++++++++ standard-schema/src/__tests__/Form.tsx | 56 ++++++++++++ .../src/__tests__/__fixtures__/data.ts | 65 ++++++++++++++ .../__snapshots__/standard-schema.ts.snap | 63 ++++++++++++++ .../src/__tests__/standard-schema.ts | 28 ++++++ standard-schema/src/index.ts | 2 + standard-schema/src/standard-schema.ts | 45 ++++++++++ standard-schema/src/types.ts | 10 +++ 14 files changed, 452 insertions(+), 3 deletions(-) create mode 100644 standard-schema/package.json create mode 100644 standard-schema/src/__tests__/Form-native-validation.tsx create mode 100644 standard-schema/src/__tests__/Form.tsx create mode 100644 standard-schema/src/__tests__/__fixtures__/data.ts create mode 100644 standard-schema/src/__tests__/__snapshots__/standard-schema.ts.snap create mode 100644 standard-schema/src/__tests__/standard-schema.ts create mode 100644 standard-schema/src/index.ts create mode 100644 standard-schema/src/standard-schema.ts create mode 100644 standard-schema/src/types.ts diff --git a/README.md b/README.md index 16a22980..43b72426 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Install your preferred validation library alongside `@hookform/resolvers`. - [effect-ts](#effect-ts) - [VineJS](#vinejs) - [fluentvalidation-ts](#fluentvalidation-ts) + - [standard-schema](#standard-schema) - [Backers](#backers) - [Sponsors](#sponsors) - [Contributors](#contributors) @@ -779,6 +780,72 @@ const App = () => { }; ``` +### [standard-schema](https://github.com/standard-schema/standard-schema) + +A standard interface for TypeScript schema validation libraries + +[![npm](https://img.shields.io/bundlephobia/minzip/@standard-schema/spec?style=for-the-badge)](https://bundlephobia.com/result?p=@standard-schema/spec) + +Example zod + +```typescript jsx +import { useForm } from 'react-hook-form'; +import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'; +import { z } from 'zod'; + +const schema = z.object({ + name: z.string().min(1, { message: 'Required' }), + age: z.number().min(10), +}); + +const App = () => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: standardSchemaResolver(schema), + }); + + return ( +
console.log(d))}> + + {errors.name?.message &&

{errors.name?.message}

} + + {errors.age?.message &&

{errors.age?.message}

} + +
+ ); +}; +``` + +Example arkType + +```typescript jsx +import { useForm } from 'react-hook-form'; +import { standardSchemaResolver } from '@hookform/resolvers/standard-schema'; +import { type } from 'arktype'; + +const schema = type({ + username: 'string>1', + password: 'string>1', +}); + +const App = () => { + const { register, handleSubmit } = useForm({ + resolver: standardSchemaResolver(schema), + }); + + return ( +
console.log(d))}> + + + +
+ ); +}; +``` + ## Backers Thanks go to all our backers! [[Become a backer](https://opencollective.com/react-hook-form#backer)]. diff --git a/bun.lockb b/bun.lockb index 9d0994bb737002a6621ba60636cb5864ff78b8af..84b7b9bd65e70d3c80197f414543e3d7f2770697 100755 GIT binary patch delta 49856 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�Ka{MOoZO(nfy=-PjJYR?t2 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

sY#7qUqB}`xp%TVBl=BXLr}An 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 pzuCo43xM44zxEm7i9OSJo%(J`$ { diff --git a/effect-ts/src/effect-ts.ts b/effect-ts/src/effect-ts.ts index b99a7584..91130bed 100644 --- a/effect-ts/src/effect-ts.ts +++ b/effect-ts/src/effect-ts.ts @@ -2,7 +2,7 @@ import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers'; import { Effect } from 'effect'; import { ArrayFormatter, decodeUnknown } from 'effect/ParseResult'; -import { appendErrors, type FieldError } from 'react-hook-form'; +import { type FieldError, appendErrors } from 'react-hook-form'; import type { Resolver } from './types'; export const effectTsResolver: Resolver = diff --git a/package.json b/package.json index 4463852c..b7f8352c 100644 --- a/package.json +++ b/package.json @@ -123,6 +123,12 @@ "import": "./fluentvalidation-ts/dist/fluentvalidation-ts.mjs", "require": "./fluentvalidation-ts/dist/fluentvalidation-ts.js" }, + "./standard-schema": { + "types": "./standard-schema/dist/index.d.ts", + "umd": "./standard-schema/dist/standard-schema.umd.js", + "import": "./standard-schema/dist/standard-schema.mjs", + "require": "./standard-schema/dist/standard-schema.js" + }, "./package.json": "./package.json", "./*": "./*" }, @@ -184,7 +190,10 @@ "vine/dist", "fluentvalidation-ts/package.json", "fluentvalidation-ts/src", - "fluentvalidation-ts/dist" + "fluentvalidation-ts/dist", + "standard-schema/package.json", + "standard-schema/src", + "standard-schema/dist" ], "publishConfig": { "access": "public" @@ -211,6 +220,7 @@ "build:effect-ts": "microbundle --cwd effect-ts --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,effect=Effect,effect/SchemaAST=EffectSchemaAST,effect/ParseResult=EffectParseResult", "build:vine": "microbundle --cwd vine --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,@vinejs/vine=vine", "build:fluentvalidation-ts": "microbundle --cwd fluentvalidation-ts --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm", + "build:standard-schema": "microbundle --cwd standard-schema --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,@standard-schema/spec=standardSchema", "postbuild": "node ./config/node-13-exports.js && check-export-map", "lint": "bunx @biomejs/biome check --write --vcs-use-ignore-file=true .", "lint:types": "tsc", @@ -243,7 +253,8 @@ "arktype", "typeschema", "vine", - "fluentvalidation-ts" + "fluentvalidation-ts", + "standard-schema" ], "repository": { "type": "git", @@ -257,6 +268,7 @@ "homepage": "https://react-hook-form.com", "devDependencies": { "@sinclair/typebox": "^0.34.15", + "@standard-schema/spec": "^1.0.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", diff --git a/standard-schema/package.json b/standard-schema/package.json new file mode 100644 index 00000000..de255f3d --- /dev/null +++ b/standard-schema/package.json @@ -0,0 +1,18 @@ +{ + "name": "@hookform/resolvers/standard-schema", + "amdName": "hookformResolversStandardSchema", + "version": "1.0.0", + "private": true, + "description": "React Hook Form validation resolver: standard-schema", + "main": "dist/standard-schema.js", + "module": "dist/standard-schema.module.js", + "umd:main": "dist/standard-schema.umd.js", + "source": "src/index.ts", + "types": "dist/index.d.ts", + "license": "MIT", + "peerDependencies": { + "react-hook-form": "^7.0.0", + "@standard-schema/spec": "^1.0.0", + "@hookform/resolvers": "^2.0.0" + } +} diff --git a/standard-schema/src/__tests__/Form-native-validation.tsx b/standard-schema/src/__tests__/Form-native-validation.tsx new file mode 100644 index 00000000..2f965099 --- /dev/null +++ b/standard-schema/src/__tests__/Form-native-validation.tsx @@ -0,0 +1,82 @@ +import { render, screen } from '@testing-library/react'; +import user from '@testing-library/user-event'; +import { type } from 'arktype'; +import React from 'react'; +import { useForm } from 'react-hook-form'; +import { standardSchemaResolver } from '..'; + +const schema = type({ + username: 'string>1', + password: 'string>1', +}); + +type FormData = typeof schema.infer; + +interface Props { + onSubmit: (data: FormData) => void; +} + +function TestComponent({ onSubmit }: Props) { + const { register, handleSubmit } = useForm({ + resolver: standardSchemaResolver(schema), + shouldUseNativeValidation: true, + }); + + return ( +

+ + + + + +
+ ); +} + +test("form's native validation with arkType", async () => { + const handleSubmit = vi.fn(); + render(); + + // username + let usernameField = screen.getByPlaceholderText( + /username/i, + ) as HTMLInputElement; + expect(usernameField.validity.valid).toBe(true); + expect(usernameField.validationMessage).toBe(''); + + // password + let passwordField = screen.getByPlaceholderText( + /password/i, + ) as HTMLInputElement; + expect(passwordField.validity.valid).toBe(true); + expect(passwordField.validationMessage).toBe(''); + + await user.click(screen.getByText(/submit/i)); + + // username + usernameField = screen.getByPlaceholderText(/username/i) as HTMLInputElement; + expect(usernameField.validity.valid).toBe(false); + expect(usernameField.validationMessage).toBe( + 'username must be at least length 2', + ); + + // password + passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement; + expect(passwordField.validity.valid).toBe(false); + expect(passwordField.validationMessage).toBe( + 'password must be at least length 2', + ); + + await user.type(screen.getByPlaceholderText(/username/i), 'joe'); + await user.type(screen.getByPlaceholderText(/password/i), 'password'); + + // username + usernameField = screen.getByPlaceholderText(/username/i) as HTMLInputElement; + expect(usernameField.validity.valid).toBe(true); + expect(usernameField.validationMessage).toBe(''); + + // password + passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement; + expect(passwordField.validity.valid).toBe(true); + expect(passwordField.validationMessage).toBe(''); +}); diff --git a/standard-schema/src/__tests__/Form.tsx b/standard-schema/src/__tests__/Form.tsx new file mode 100644 index 00000000..500a46d1 --- /dev/null +++ b/standard-schema/src/__tests__/Form.tsx @@ -0,0 +1,56 @@ +import { render, screen } from '@testing-library/react'; +import user from '@testing-library/user-event'; +import { type } from 'arktype'; +import React from 'react'; +import { useForm } from 'react-hook-form'; +import { standardSchemaResolver } from '..'; + +const schema = type({ + username: 'string>1', + password: 'string>1', +}); + +type FormData = typeof schema.infer & { unusedProperty: string }; + +interface Props { + onSubmit: (data: FormData) => void; +} + +function TestComponent({ onSubmit }: Props) { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: standardSchemaResolver(schema), // Useful to check TypeScript regressions + }); + + return ( +
+ + {errors.username && {errors.username.message}} + + + {errors.password && {errors.password.message}} + + +
+ ); +} + +test("form's validation with arkType and TypeScript's integration", async () => { + const handleSubmit = vi.fn(); + render(); + + expect(screen.queryAllByRole('alert')).toHaveLength(0); + + await user.click(screen.getByText(/submit/i)); + + expect( + screen.getByText('username must be at least length 2'), + ).toBeInTheDocument(); + expect( + screen.getByText('password must be at least length 2'), + ).toBeInTheDocument(); + expect(handleSubmit).not.toHaveBeenCalled(); +}); diff --git a/standard-schema/src/__tests__/__fixtures__/data.ts b/standard-schema/src/__tests__/__fixtures__/data.ts new file mode 100644 index 00000000..5f0495e7 --- /dev/null +++ b/standard-schema/src/__tests__/__fixtures__/data.ts @@ -0,0 +1,65 @@ +import { type } from 'arktype'; +import { Field, InternalFieldName } from 'react-hook-form'; + +export const schema = type({ + username: 'string>2', + password: '/.*[A-Za-z].*/>8|/.*\\d.*/', + repeatPassword: 'string>1', + accessToken: 'string|number', + birthYear: '19001', + 'like?': type({ + id: 'number', + name: 'string>3', + }).array(), + dateStr: 'Date', +}); + +export const validData: typeof schema.infer = { + username: 'Doe', + password: 'Password123_', + repeatPassword: 'Password123_', + birthYear: 2000, + email: 'john@doe.com', + tags: ['tag1', 'tag2'], + enabled: true, + accessToken: 'accessToken', + url: 'https://react-hook-form.com/', + like: [ + { + id: 1, + name: 'name', + }, + ], + dateStr: new Date('2020-01-01'), +}; + +export const invalidData = { + password: '___', + email: '', + birthYear: 'birthYear', + like: [{ id: 'z' }], + url: 'abc', +}; + +export const fields: Record = { + username: { + ref: { name: 'username' }, + name: 'username', + }, + password: { + ref: { name: 'password' }, + name: 'password', + }, + email: { + ref: { name: 'email' }, + name: 'email', + }, + birthday: { + ref: { name: 'birthday' }, + name: 'birthday', + }, +}; diff --git a/standard-schema/src/__tests__/__snapshots__/standard-schema.ts.snap b/standard-schema/src/__tests__/__snapshots__/standard-schema.ts.snap new file mode 100644 index 00000000..5000df6c --- /dev/null +++ b/standard-schema/src/__tests__/__snapshots__/standard-schema.ts.snap @@ -0,0 +1,63 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`standardSchemaResolver > should return a single error from standardSchemaResolver when validation fails 1`] = ` +{ + "errors": { + "accessToken": { + "message": "accessToken must be a number or a string (was missing)", + "ref": undefined, + }, + "birthYear": { + "message": "birthYear must be a number (was a string)", + "ref": undefined, + }, + "dateStr": { + "message": "dateStr must be a Date (was missing)", + "ref": undefined, + }, + "email": { + "message": "email must be an email address (was "")", + "ref": { + "name": "email", + }, + }, + "enabled": { + "message": "enabled must be boolean (was missing)", + "ref": undefined, + }, + "like": [ + { + "id": { + "message": "like[0].id must be a number (was a string)", + "ref": undefined, + }, + "name": { + "message": "like[0].name must be a string (was missing)", + "ref": undefined, + }, + }, + ], + "password": { + "message": "password must be matched by .*[A-Za-z].* or matched by .*\\d.* (was "___")", + "ref": { + "name": "password", + }, + }, + "repeatPassword": { + "message": "repeatPassword must be a string (was missing)", + "ref": undefined, + }, + "tags": { + "message": "tags must be an array (was missing)", + "ref": undefined, + }, + "username": { + "message": "username must be a string (was missing)", + "ref": { + "name": "username", + }, + }, + }, + "values": {}, +} +`; diff --git a/standard-schema/src/__tests__/standard-schema.ts b/standard-schema/src/__tests__/standard-schema.ts new file mode 100644 index 00000000..638d5d0d --- /dev/null +++ b/standard-schema/src/__tests__/standard-schema.ts @@ -0,0 +1,28 @@ +import { standardSchemaResolver } from '..'; +import { fields, invalidData, schema, validData } from './__fixtures__/data'; + +const shouldUseNativeValidation = false; + +describe('standardSchemaResolver', () => { + it('should return values from standardSchemaResolver when validation pass & raw=true', async () => { + const result = await standardSchemaResolver(schema)(validData, undefined, { + fields, + shouldUseNativeValidation, + }); + + expect(result).toEqual({ errors: {}, values: validData }); + }); + + it('should return a single error from standardSchemaResolver when validation fails', async () => { + const result = await standardSchemaResolver(schema)( + invalidData, + undefined, + { + fields, + shouldUseNativeValidation, + }, + ); + + expect(result).toMatchSnapshot(); + }); +}); diff --git a/standard-schema/src/index.ts b/standard-schema/src/index.ts new file mode 100644 index 00000000..5ee0d3cf --- /dev/null +++ b/standard-schema/src/index.ts @@ -0,0 +1,2 @@ +export * from './standard-schema'; +export * from './types'; diff --git a/standard-schema/src/standard-schema.ts b/standard-schema/src/standard-schema.ts new file mode 100644 index 00000000..12d9a9a1 --- /dev/null +++ b/standard-schema/src/standard-schema.ts @@ -0,0 +1,45 @@ +import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers'; +import { StandardSchemaV1 } from '@standard-schema/spec'; +import { FieldError } from 'react-hook-form'; +import type { Resolver } from './types'; + +const parseIssues = (issues: readonly StandardSchemaV1.Issue[]) => { + const errors: Record = {}; + + for (let i = 0; i < issues.length; i++) { + const issue = issues[i]; + const path = issue.path?.join('.') ?? ''; + + if (path) { + if (!errors[path]) { + errors[path] = { message: issue.message } as FieldError; + } + } + } + + return errors; +}; + +export const standardSchemaResolver: Resolver = + (schema) => async (values, _, options) => { + let result = schema['~standard'].validate(values); + if (result instanceof Promise) { + result = await result; + } + + if (result.issues) { + const errors = parseIssues(result.issues); + + return { + values: {}, + errors: toNestErrors(errors, options), + }; + } + + options.shouldUseNativeValidation && validateFieldsNatively({}, options); + + return { + values: values, + errors: {}, + }; + }; diff --git a/standard-schema/src/types.ts b/standard-schema/src/types.ts new file mode 100644 index 00000000..c1b8d51e --- /dev/null +++ b/standard-schema/src/types.ts @@ -0,0 +1,10 @@ +import { StandardSchemaV1 } from '@standard-schema/spec'; +import { FieldValues, ResolverOptions, ResolverResult } from 'react-hook-form'; + +export type Resolver = >( + schema: T, +) => ( + values: TFieldValues, + context: TContext | undefined, + options: ResolverOptions, +) => Promise>;