Skip to content

Commit

Permalink
feat(valibot): Add valibot resolver (#602)
Browse files Browse the repository at this point in the history
* Add valibot resolver

* fix docs

* fix node 13 export

* remove random lock file
  • Loading branch information
erikshestopal committed Aug 6, 2023
1 parent 1a64cd1 commit 2185330
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 14 deletions.
68 changes: 56 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,28 @@

### Supported resolvers

- [Yup](#yup)
- [Zod](#zod)
- [Superstruct](#superstruct)
- [Joi](#joi)
- [Class Validator](#class-validator)
- [io-ts](#io-ts)
- [Nope](#nope)
- [computed-types](#computed-types)
- [typanion](#typanion)
- [Ajv](#ajv)
- [TypeBox](#typebox)
- [ArkType](#arktype)
- [Install](#install)
- [Links](#links)
- [Supported resolvers](#supported-resolvers)
- [API](#api)
- [Quickstart](#quickstart)
- [Yup](#yup)
- [Zod](#zod)
- [Superstruct](#superstruct)
- [Joi](#joi)
- [Vest](#vest)
- [Class Validator](#class-validator)
- [io-ts](#io-ts)
- [Nope](#nope)
- [computed-types](#computed-types)
- [typanion](#typanion)
- [Ajv](#ajv)
- [TypeBox](#typebox)
- [ArkType](#arktype)
- [Valibot](#valibot)
- [Backers](#backers)
- [Sponsors](#sponsors)
- [Contributors](#contributors)

## API

Expand Down Expand Up @@ -532,6 +542,40 @@ const App = () => {
};
```

### [Valibot](https://github.com/fabian-hiller/valibot)

The modular and type safe schema library for validating structural data

[![npm](https://img.shields.io/bundlephobia/minzip/valibot?style=for-the-badge)](https://bundlephobia.com/result?p=valibot)

```typescript jsx
import { useForm } from 'react-hook-form';
import { valibotResolver } from '@hookform/resolvers/valibot';
import { object, string, minLength, endsWith } from 'valibot';

const schema = object({
username: string('username is required', [
minLength(3, 'Needs to be at least 3 characters'),
endsWith('cool', 'Needs to end with `cool`'),
]),
password: string('password is required'),
});

const App = () => {
const { register, handleSubmit } = useForm({
resolver: valibotResolver(schema),
});

return (
<form onSubmit={handleSubmit((d) => console.log(d))}>
<input {...register('username')} />
<input type="password" {...register('password')} />
<input type="submit" />
</form>
);
};
```

## Backers

Thanks goes to all our backers! [[Become a backer](https://opencollective.com/react-hook-form#backer)].
Expand Down
3 changes: 2 additions & 1 deletion config/node-13-exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const subRepositories = [
'typanion',
'ajv',
'typebox',
'arktype'
'arktype',
'valibot',
];

const copySrc = () => {
Expand Down
13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@
"import": "./arktype/dist/arktype.mjs",
"require": "./arktype/dist/arktype.js"
},
"./valibot": {
"types": "./valibot/dist/index.d.ts",
"umd": "./valibot/dist/valibot.umd.js",
"import": "./valibot/dist/valibot.mjs",
"require": "./valibot/dist/valibot.js"
},
"./package.json": "./package.json",
"./*": "./*"
},
Expand Down Expand Up @@ -136,7 +142,10 @@
"typebox/dist",
"arktype/package.json",
"arktype/src",
"arktype/dist"
"arktype/dist",
"valibot/package.json",
"valibot/src",
"valibot/dist"
],
"publishConfig": {
"access": "public"
Expand All @@ -158,6 +167,7 @@
"build:ajv": "microbundle --cwd ajv --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
"build:typebox": "microbundle --cwd typebox --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm,@sinclair/typebox/value=value",
"build:arktype": "microbundle --cwd arktype --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
"build:valibot": "microbundle --cwd valibot --globals @hookform/resolvers=hookformResolvers,react-hook-form=ReactHookForm",
"postbuild": "node ./config/node-13-exports.js",
"lint": "eslint . --ext .ts,.js --ignore-path .gitignore",
"lint:types": "tsc",
Expand Down Expand Up @@ -238,6 +248,7 @@
"superstruct": "^1.0.3",
"typanion": "^3.12.1",
"typescript": "^5.0.4",
"valibot": "^0.8.0",
"vest": "^4.6.9",
"vite": "^4.2.1",
"vite-tsconfig-paths": "^4.2.0",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions valibot/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@hookform/resolvers/valibot",
"amdName": "hookformResolversValibot",
"version": "1.0.0",
"private": true,
"description": "React Hook Form validation resolver: valibot",
"main": "dist/valibot.js",
"module": "dist/valibot.module.js",
"umd:main": "dist/valibot.umd.js",
"source": "src/index.ts",
"types": "dist/index.d.ts",
"license": "MIT",
"peerDependencies": {
"react-hook-form": "^7.0.0",
"@hookform/resolvers": "^2.0.0",
"valibot": ">=0.8"
}
}
88 changes: 88 additions & 0 deletions valibot/src/__tests__/__fixtures__/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Field, InternalFieldName } from 'react-hook-form';
import {
object,
string,
minLength,
maxLength,
regex,
number,
minValue,
maxValue,
email,
array,
boolean,
required,
} from 'valibot';

export const schema = required(
object({
username: string([minLength(2), maxLength(30), regex(/^\w+$/)]),
password: string('New Password is required', [
regex(new RegExp('.*[A-Z].*'), 'One uppercase character'),
regex(new RegExp('.*[a-z].*'), 'One lowercase character'),
regex(new RegExp('.*\\d.*'), 'One number'),
regex(
new RegExp('.*[`~<>?,./!@#$%^&*()\\-_+="\'|{}\\[\\];:\\\\].*'),
'One special character',
),
minLength(8, 'Must be at least 8 characters in length'),
]),
repeatPassword: string('Repeat Password is required'),
accessToken: string('Access token is required'),
birthYear: number('Please enter your birth year', [
minValue(1900),
maxValue(2013),
]),
email: string([email('Invalid email address')]),
tags: array(string('Tags should be strings')),
enabled: boolean(),
like: required(
object({
id: number('Like id is required'),
name: string('Like name is required', [minLength(4, 'Too short')]),
}),
),
}),
);

export const validData = {
username: 'Doe',
password: 'Password123_',
repeatPassword: 'Password123_',
birthYear: 2000,
email: 'john@doe.com',
tags: ['tag1', 'tag2'],
enabled: true,
accessToken: 'accessToken',
like: {
id: 1,
name: 'name',
},
};

export const invalidData = {
password: '___',
email: '',
birthYear: 'birthYear',
like: { id: 'z' },
tags: [1, 2, 3],
};

export const fields: Record<InternalFieldName, Field['_f']> = {
username: {
ref: { name: 'username' },
name: 'username',
},
password: {
ref: { name: 'password' },
name: 'password',
},
email: {
ref: { name: 'email' },
name: 'email',
},
birthday: {
ref: { name: 'birthday' },
name: 'birthday',
},
};
79 changes: 79 additions & 0 deletions valibot/src/__tests__/__snapshots__/valibot.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`valibotResolver > should return a single error from valibotResolver when validation fails 1`] = `
{
"errors": {
"accessToken": {
"message": "Invalid type",
"ref": undefined,
"type": "non_optional",
},
"birthYear": {
"message": "Please enter your birth year",
"ref": undefined,
"type": "number",
},
"email": {
"message": "Invalid email address",
"ref": {
"name": "email",
},
"type": "email",
},
"enabled": {
"message": "Invalid type",
"ref": undefined,
"type": "non_optional",
},
"like": {
"id": {
"message": "Like id is required",
"ref": undefined,
"type": "number",
},
"name": {
"message": "Invalid type",
"ref": undefined,
"type": "non_optional",
},
},
"password": {
"message": "One uppercase character",
"ref": {
"name": "password",
},
"type": "regex",
},
"repeatPassword": {
"message": "Invalid type",
"ref": undefined,
"type": "non_optional",
},
"tags": [
{
"message": "Tags should be strings",
"ref": undefined,
"type": "string",
},
{
"message": "Tags should be strings",
"ref": undefined,
"type": "string",
},
{
"message": "Tags should be strings",
"ref": undefined,
"type": "string",
},
],
"username": {
"message": "Invalid type",
"ref": {
"name": "username",
},
"type": "non_optional",
},
},
"values": {},
}
`;
24 changes: 24 additions & 0 deletions valibot/src/__tests__/valibot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* eslint-disable no-console, @typescript-eslint/ban-ts-comment */
import { valibotResolver } from '..';
import { schema, validData, fields, invalidData } from './__fixtures__/data';

const shouldUseNativeValidation = false;
describe('valibotResolver', () => {
it('should return values from valibotResolver when validation pass', async () => {
const result = await valibotResolver(schema)(validData, undefined, {
fields,
shouldUseNativeValidation,
});

expect(result).toEqual({ errors: {}, values: validData });
});

it('should return a single error from valibotResolver when validation fails', async () => {
const result = await valibotResolver(schema)(invalidData, undefined, {
fields,
shouldUseNativeValidation,
});

expect(result).toMatchSnapshot();
});
});
2 changes: 2 additions & 0 deletions valibot/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './valibot';
export * from './types';

0 comments on commit 2185330

Please sign in to comment.