diff --git a/src/Lazy.ts b/src/Lazy.ts index 41af84c6..0c23a32c 100644 --- a/src/Lazy.ts +++ b/src/Lazy.ts @@ -4,6 +4,7 @@ import type { ISchema, ValidateOptions, NestedTestConfig, + InferType, } from './types'; import type { ResolveOptions } from './Condition'; @@ -14,7 +15,8 @@ import type { SchemaLazyDescription, } from './schema'; import { Flags, Maybe } from './util/types'; -import { InferType, Schema } from '.'; +import ValidationError from './ValidationError'; +import Schema from './schema'; export type LazyBuilder< TSchema extends ISchema, @@ -28,6 +30,15 @@ export function create< return new Lazy, TContext>(builder); } +function catchValidationError(fn: () => any) { + try { + return fn(); + } catch (err) { + if (ValidationError.isError(err)) return Promise.reject(err); + throw err; + } +} + export interface LazySpec { meta: Record | undefined; optional: boolean; @@ -113,7 +124,9 @@ class Lazy } validate(value: any, options?: ValidateOptions): Promise { - return this._resolve(value, options).validate(value, options); + return catchValidationError(() => + this._resolve(value, options).validate(value, options), + ); } validateSync(value: any, options?: ValidateOptions): T { @@ -121,7 +134,9 @@ class Lazy } validateAt(path: string, value: any, options?: ValidateOptions) { - return this._resolve(value, options).validateAt(path, value, options); + return catchValidationError(() => + this._resolve(value, options).validateAt(path, value, options), + ); } validateSyncAt( @@ -133,7 +148,14 @@ class Lazy } isValid(value: any, options?: ValidateOptions) { - return this._resolve(value, options).isValid(value, options); + try { + return this._resolve(value, options).isValid(value, options); + } catch (err) { + if (ValidationError.isError(err)) { + return Promise.resolve(false); + } + throw err; + } } isValidSync(value: any, options?: ValidateOptions) { diff --git a/test/lazy.ts b/test/lazy.ts index 7ebaa3b8..3b3cbf3c 100644 --- a/test/lazy.ts +++ b/test/lazy.ts @@ -1,9 +1,16 @@ -import { lazy, object, mixed, AnyObject, MixedSchema } from '../src'; +import { + lazy, + object, + mixed, + AnyObject, + MixedSchema, + ValidationError, +} from '../src'; describe('lazy', function () { it('should throw on a non-schema value', () => { // @ts-expect-error testing incorrect usage - expect(() => lazy(() => undefined).validate(undefined)).toThrowError(); + expect(() => lazy(() => undefined).validateSync(undefined)).toThrowError(); }); describe('mapper', () => { @@ -54,5 +61,23 @@ describe('lazy', function () { added: true, }); }); + + it('should allow throwing validation error in builder', async () => { + const schema = lazy(() => { + throw new ValidationError('oops'); + }); + + await expect(schema.validate(value)).rejects.toThrowError('oops'); + await expect(schema.isValid(value)).resolves.toEqual(false); + + expect(() => schema.validateSync(value)).toThrowError('oops'); + + const schema2 = lazy(() => { + throw new Error('error'); + }); + // none validation errors are thrown sync to maintain back compat + expect(() => schema2.validate(value)).toThrowError('error'); + expect(() => schema2.isValid(value)).toThrowError('error'); + }); }); });