Skip to content

Commit

Permalink
feat: add Joi sync validation (#116)
Browse files Browse the repository at this point in the history
* feat: add Joi sync validation

* fix(joi): sync error

* test: add validateAllCriteria test

* test(joi): update test description
  • Loading branch information
jorisre authored Jan 22, 2021
1 parent 22e7545 commit 8e3b54a
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 5 deletions.
82 changes: 82 additions & 0 deletions joi/src/__tests__/__snapshots__/joi.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,38 @@ Object {
}
`;

exports[`joiResolver should return a single error from joiResolver with \`mode: sync\` when validation fails 1`] = `
Object {
"errors": Object {
"birthYear": Object {
"message": "\\"birthYear\\" must be a number",
"type": "number.base",
},
"email": Object {
"message": "\\"email\\" is not allowed to be empty",
"type": "string.empty",
},
"enabled": Object {
"message": "\\"enabled\\" is required",
"type": "any.required",
},
"password": Object {
"message": "\\"password\\" with value \\"___\\" fails to match the required pattern: /^[a-zA-Z0-9]{3,30}$/",
"type": "string.pattern.base",
},
"tags": Object {
"message": "\\"tags\\" is required",
"type": "any.required",
},
"username": Object {
"message": "\\"username\\" is required",
"type": "any.required",
},
},
"values": Object {},
}
`;

exports[`joiResolver should return all the errors from joiResolver when validation fails with \`validateAllFieldCriteria\` set to true 1`] = `
Object {
"errors": Object {
Expand Down Expand Up @@ -81,3 +113,53 @@ Object {
"values": Object {},
}
`;

exports[`joiResolver should return all the errors from joiResolver when validation fails with \`validateAllFieldCriteria\` set to true and \`mode: sync\` 1`] = `
Object {
"errors": Object {
"birthYear": Object {
"message": "\\"birthYear\\" must be a number",
"type": "number.base",
"types": Object {
"number.base": "\\"birthYear\\" must be a number",
},
},
"email": Object {
"message": "\\"email\\" is not allowed to be empty",
"type": "string.empty",
"types": Object {
"string.empty": "\\"email\\" is not allowed to be empty",
},
},
"enabled": Object {
"message": "\\"enabled\\" is required",
"type": "any.required",
"types": Object {
"any.required": "\\"enabled\\" is required",
},
},
"password": Object {
"message": "\\"password\\" with value \\"___\\" fails to match the required pattern: /^[a-zA-Z0-9]{3,30}$/",
"type": "string.pattern.base",
"types": Object {
"string.pattern.base": "\\"password\\" with value \\"___\\" fails to match the required pattern: /^[a-zA-Z0-9]{3,30}$/",
},
},
"tags": Object {
"message": "\\"tags\\" is required",
"type": "any.required",
"types": Object {
"any.required": "\\"tags\\" is required",
},
},
"username": Object {
"message": "\\"username\\" is required",
"type": "any.required",
"types": Object {
"any.required": "\\"username\\" is required",
},
},
},
"values": Object {},
}
`;
59 changes: 59 additions & 0 deletions joi/src/__tests__/joi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,34 @@ describe('joiResolver', () => {
enabled: true,
};

const validateAsyncSpy = jest.spyOn(schema, 'validateAsync');
const validateSpy = jest.spyOn(schema, 'validate');

const result = await joiResolver(schema)(data);

expect(validateSpy).not.toHaveBeenCalled();
expect(validateAsyncSpy).toHaveBeenCalledTimes(1);
expect(result).toEqual({ errors: {}, values: data });
});

it('should return values from joiResolver with `mode: sync` when validation pass', async () => {
const data: Data = {
username: 'Doe',
password: 'Password123',
repeatPassword: 'Password123',
birthYear: 2000,
email: 'john@doe.com',
tags: ['tag1', 'tag2'],
enabled: true,
};

const validateAsyncSpy = jest.spyOn(schema, 'validateAsync');
const validateSpy = jest.spyOn(schema, 'validate');

const result = await joiResolver(schema, undefined, { mode: 'sync' })(data);

expect(validateAsyncSpy).not.toHaveBeenCalled();
expect(validateSpy).toHaveBeenCalledTimes(1);
expect(result).toEqual({ errors: {}, values: data });
});

Expand All @@ -55,6 +81,23 @@ describe('joiResolver', () => {
expect(result).toMatchSnapshot();
});

it('should return a single error from joiResolver with `mode: sync` when validation fails', async () => {
const data = {
password: '___',
email: '',
birthYear: 'birthYear',
};

const validateAsyncSpy = jest.spyOn(schema, 'validateAsync');
const validateSpy = jest.spyOn(schema, 'validate');

const result = await joiResolver(schema, undefined, { mode: 'sync' })(data);

expect(validateAsyncSpy).not.toHaveBeenCalled();
expect(validateSpy).toHaveBeenCalledTimes(1);
expect(result).toMatchSnapshot();
});

it('should return all the errors from joiResolver when validation fails with `validateAllFieldCriteria` set to true', async () => {
const data = {
password: '___',
Expand All @@ -66,4 +109,20 @@ describe('joiResolver', () => {

expect(result).toMatchSnapshot();
});

it('should return all the errors from joiResolver when validation fails with `validateAllFieldCriteria` set to true and `mode: sync`', async () => {
const data = {
password: '___',
email: '',
birthYear: 'birthYear',
};

const result = await joiResolver(schema, undefined, { mode: 'sync' })(
data,
undefined,
true,
);

expect(result).toMatchSnapshot();
});
});
20 changes: 16 additions & 4 deletions joi/src/joi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,27 @@ const parseErrorSchema = (

export const joiResolver: Resolver = (
schema,
options = {
schemaOptions = {
abortEarly: false,
},
{ mode } = { mode: 'async' },
) => async (values, _, validateAllFieldCriteria = false) => {
try {
let result;
if (mode === 'async') {
result = await schema.validateAsync(values, schemaOptions);
} else {
const { value, error } = schema.validate(values, schemaOptions);

if (error) {
throw error;
}

result = value;
}

return {
values: await schema.validateAsync(values, {
...options,
}),
values: result,
errors: {},
};
} catch (e) {
Expand Down
3 changes: 2 additions & 1 deletion joi/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import type { AsyncValidationOptions, Schema } from 'joi';

export type Resolver = <T extends Schema>(
schema: T,
options?: AsyncValidationOptions,
schemaOptions?: AsyncValidationOptions,
resolverOptions?: { mode: 'async' | 'sync' },
) => <TFieldValues extends FieldValues, TContext>(
values: UnpackNestedValue<TFieldValues>,
context?: TContext,
Expand Down

0 comments on commit 8e3b54a

Please sign in to comment.