Skip to content

Commit

Permalink
fix: lazy validation errors thrown in builders should resolve async l…
Browse files Browse the repository at this point in the history
…ike other validations
  • Loading branch information
jquense committed Dec 17, 2024
1 parent f27fa44 commit c7d7f97
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 6 deletions.
30 changes: 26 additions & 4 deletions src/Lazy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
ISchema,
ValidateOptions,
NestedTestConfig,
InferType,
} from './types';
import type { ResolveOptions } from './Condition';

Expand All @@ -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<TContext>,
Expand All @@ -28,6 +30,15 @@ export function create<
return new Lazy<InferType<TSchema>, 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<string, unknown> | undefined;
optional: boolean;
Expand Down Expand Up @@ -113,15 +124,19 @@ class Lazy<T, TContext = AnyObject, TFlags extends Flags = any>
}

validate(value: any, options?: ValidateOptions<TContext>): Promise<T> {
return this._resolve(value, options).validate(value, options);
return catchValidationError(() =>
this._resolve(value, options).validate(value, options),
);
}

validateSync(value: any, options?: ValidateOptions<TContext>): T {
return this._resolve(value, options).validateSync(value, options);
}

validateAt(path: string, value: any, options?: ValidateOptions<TContext>) {
return this._resolve(value, options).validateAt(path, value, options);
return catchValidationError(() =>
this._resolve(value, options).validateAt(path, value, options),
);
}

validateSyncAt(
Expand All @@ -133,7 +148,14 @@ class Lazy<T, TContext = AnyObject, TFlags extends Flags = any>
}

isValid(value: any, options?: ValidateOptions<TContext>) {
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<TContext>) {
Expand Down
29 changes: 27 additions & 2 deletions test/lazy.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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');
});
});
});

0 comments on commit c7d7f97

Please sign in to comment.