Skip to content

Commit 0983d54

Browse files
feat: implement joi schema support (#4463)
1 parent c6a1edc commit 0983d54

File tree

6 files changed

+616
-0
lines changed

6 files changed

+616
-0
lines changed

packages/joi/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Change Log

packages/joi/README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# @vee-validate/joi
2+
3+
<p align="center">
4+
<a href="https://vee-validate.logaretm.com/v4/integrations/joi-schema-validation/" target="_blank">
5+
<img width="150" src="https://github.com/logaretm/vee-validate/raw/main/logo.png">
6+
</a>
7+
8+
<a href="https://github.com/hapijs/joi/" target="_blank">
9+
<img width="150" src="https://joi.dev/img/joiTransparent.png">
10+
</a>
11+
</p>
12+
13+
> Official vee-validate integration with Joi schema validation
14+
15+
<p align="center">
16+
<a href="https://github.com/sponsors/logaretm">
17+
<img src='https://sponsors.logaretm.com/sponsors.svg'>
18+
</a>
19+
</p>
20+
21+
## Guide
22+
23+
[Joi](https://github.com/hapijs/joi/) is a feature rich validation library for the browser and nodejs
24+
25+
In their own words it is a:
26+
27+
> The most powerful schema description language and data validator for JavaScript.
28+
29+
You can use joi as a typed schema with the `@vee-validate/joi` package:
30+
31+
```sh
32+
# npm
33+
npm install @vee-validate/joi
34+
# yarn
35+
yarn add @vee-validate/joi
36+
# pnpm
37+
pnpm add @vee-validate/joi
38+
```
39+
40+
The `@vee-valdiate/joi` package exposes a `toTypedSchema` function that accepts any joi schema. Which then you can pass along to `validationSchema` option on `useForm`.
41+
42+
This makes the form values and submitted values typed automatically and caters for both input and output types of that schema.
43+
44+
```ts
45+
import { useForm } from 'vee-validate';
46+
import { object, string } from 'joi';
47+
import { toTypedSchema } from '@vee-validate/joi';
48+
49+
interface FormData {
50+
email: string;
51+
password: string;
52+
name?: string;
53+
}
54+
55+
const { values, handleSubmit } = useForm({
56+
validationSchema: toTypedSchema(
57+
object<FormData>({
58+
email: string().min(1).required().message('required'),
59+
password: string().min(1).message('required'),
60+
name: string().optional(),
61+
}),
62+
),
63+
});
64+
65+
// ❌ Type error, which means `values` is type-safe
66+
values.email.endsWith('@gmail.com');
67+
68+
handleSubmit(submitted => {
69+
// No errors, because email is required!
70+
submitted.email.endsWith('@gmail.com');
71+
72+
// ❌ Type error, because `name` is not required so it could be undefined
73+
// Means that your fields are now type safe!
74+
submitted.name.length;
75+
});
76+
```
77+
78+
### Joi default values
79+
80+
You can also define default values on your joi schema directly and it will be picked up by the form:
81+
82+
```ts
83+
import { useForm } from 'vee-validate';
84+
import { object, string } from 'joi';
85+
import { toTypedSchema } from '@vee-validate/joi';
86+
87+
const { values, handleSubmit } = useForm({
88+
validationSchema: toTypedSchema(
89+
object({
90+
email: string().default('[email protected]'),
91+
password: string().default(''),
92+
}),
93+
),
94+
});
95+
```
96+
97+
Your initial values will be using the schema defaults, and also the defaults will be used if the values submitted is missing these fields.

packages/joi/package.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "@vee-validate/joi",
3+
"version": "4.11.3",
4+
"description": "vee-validate integration with joi schema validation",
5+
"author": "Abdelrahman Awad <[email protected]>",
6+
"license": "MIT",
7+
"module": "dist/vee-validate-joi.esm.js",
8+
"unpkg": "dist/vee-validate-joi.js",
9+
"main": "dist/vee-validate-joi.js",
10+
"types": "dist/vee-validate-joi.d.ts",
11+
"homepage": "https://vee-validate.logaretm.com/v4/integrations/joi-schema-validation/",
12+
"repository": {
13+
"url": "https://github.com/logaretm/vee-validate.git",
14+
"type": "git",
15+
"directory": "packages/joi"
16+
},
17+
"sideEffects": false,
18+
"keywords": [
19+
"VueJS",
20+
"Vue",
21+
"validation",
22+
"validator",
23+
"inputs",
24+
"form"
25+
],
26+
"files": [
27+
"dist/*.js",
28+
"dist/*.d.ts"
29+
],
30+
"dependencies": {
31+
"joi": "17.10.1",
32+
"type-fest": "^4.2.0",
33+
"vee-validate": "4.11.6"
34+
}
35+
}

packages/joi/src/index.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import type { TypedSchema, TypedSchemaError } from 'vee-validate';
2+
import { AsyncValidationOptions, Schema, ValidationError } from 'joi';
3+
import { merge, normalizeFormPath } from '../../shared';
4+
import { PartialDeep } from 'type-fest';
5+
6+
/**
7+
* Gets the type of data from the schema
8+
*/
9+
type DataTypeOf<JoiSchema> = JoiSchema extends Schema<infer U> ? U : never;
10+
11+
/**
12+
* Transform a joi schema into TypedSchema
13+
*
14+
* @param joiSchema joi schema for transforming
15+
* @param opts validation options to pass to the joi validate function
16+
* @returns TypedSchema for using with vee-validate
17+
*/
18+
export function toTypedSchema<
19+
TSchema extends Schema,
20+
TOutput = DataTypeOf<TSchema>,
21+
TInput = PartialDeep<DataTypeOf<TSchema>>,
22+
>(joiSchema: TSchema, opts?: AsyncValidationOptions): TypedSchema<TInput, TOutput> {
23+
const validationOptions: AsyncValidationOptions = merge({ abortEarly: false }, opts || {});
24+
25+
const schema: TypedSchema = {
26+
__type: 'VVTypedSchema',
27+
async parse(value) {
28+
try {
29+
const result = await joiSchema.validateAsync(value, validationOptions);
30+
31+
return {
32+
value: result,
33+
errors: [],
34+
};
35+
} catch (err) {
36+
if (!(err instanceof ValidationError)) {
37+
throw err;
38+
}
39+
40+
const error = err as ValidationError;
41+
42+
return {
43+
errors: processIssues(error),
44+
rawError: err,
45+
};
46+
}
47+
},
48+
cast(values) {
49+
// Joi doesn't allow us to cast without validating
50+
const result = joiSchema.validate(values);
51+
52+
if (result.error) {
53+
return values;
54+
}
55+
56+
return result.value;
57+
},
58+
};
59+
60+
return schema;
61+
}
62+
63+
function processIssues(error: ValidationError): TypedSchemaError[] {
64+
const errors: Record<string, TypedSchemaError> = {};
65+
66+
error.details.forEach(detail => {
67+
const path = normalizeFormPath(detail.path.join('.'));
68+
69+
if (errors[path]) {
70+
errors[path].errors.push(detail.message);
71+
72+
return;
73+
}
74+
75+
errors[path] = {
76+
path,
77+
errors: [detail.message],
78+
};
79+
});
80+
81+
return Object.values(errors);
82+
}

0 commit comments

Comments
 (0)