Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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