Skip to content

Commit

Permalink
jquense#2043 add ValidationErrorNoStack to improve error creation per…
Browse files Browse the repository at this point in the history
…formance
  • Loading branch information
tedeschia committed Nov 29, 2023
1 parent a58f02d commit 5852a92
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 30 deletions.
66 changes: 66 additions & 0 deletions src/ValidationErrorNoStack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import ValidationError from './ValidationError';
import printValue from './util/printValue';
import toArray from './util/toArray';

let strReg = /\$\{\s*(\w+)\s*\}/g;

type Params = Record<string, unknown>;

export default class ValidationErrorNoStack implements ValidationError {
name: string;
message: string;
stack?: string | undefined;
value: any;
path?: string;
type?: string;
errors: string[];

params?: Params;

inner: ValidationError[];

static formatError(
message: string | ((params: Params) => string) | unknown,
params: Params,
) {
const path = params.label || params.path || 'this';
if (path !== params.path) params = { ...params, path };

if (typeof message === 'string')
return message.replace(strReg, (_, key) => printValue(params[key]));
if (typeof message === 'function') return message(params);

return message;
}

constructor(
errorOrErrors: string | ValidationError | readonly ValidationError[],
value?: any,
field?: string,
type?: string,
) {
this.name = 'ValidationError';
this.value = value;
this.path = field;
this.type = type;

this.errors = [];
this.inner = [];

toArray(errorOrErrors).forEach((err) => {
if (ValidationError.isError(err)) {
this.errors.push(...err.errors);
const innerErrors = err.inner.length ? err.inner : [err];
this.inner.push(...innerErrors);
} else {
this.errors.push(err);
}
});

this.message =
this.errors.length > 1
? `${this.errors.length} errors occurred`
: this.errors[0];
}
[Symbol.toStringTag] = 'Error';
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import TupleSchema, { create as tupleCreate } from './tuple';
import Reference, { create as refCreate } from './Reference';
import Lazy, { create as lazyCreate } from './Lazy';
import ValidationError from './ValidationError';
import ValidationErrorNoStack from './ValidationErrorNoStack';
import reach, { getIn } from './util/reach';
import isSchema from './util/isSchema';
import printValue from './util/printValue';
Expand Down Expand Up @@ -108,6 +109,7 @@ export {
setLocale,
defaultLocale,
ValidationError,
ValidationErrorNoStack,
};

export {
Expand Down
26 changes: 12 additions & 14 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import isAbsent from './util/isAbsent';
import type { Flags, Maybe, ResolveFlags, _ } from './util/types';
import toArray from './util/toArray';
import cloneDeep from './util/cloneDeep';
import ValidationErrorNoStack from './ValidationErrorNoStack';

export type SchemaSpec<TDefault> = {
coerce: boolean;
Expand Down Expand Up @@ -573,13 +574,14 @@ export default abstract class Schema<
(errors, validated) => {
if (errors.length)
reject(
new ValidationError(
errors!,
validated,
undefined,
undefined,
disableStackTrace,
),
disableStackTrace
? new ValidationErrorNoStack(
errors!,
validated,
undefined,
undefined,
)
: new ValidationError(errors!, validated, undefined, undefined),
);
else resolve(validated as this['__outputType']);
},
Expand All @@ -605,13 +607,9 @@ export default abstract class Schema<
},
(errors, validated) => {
if (errors.length)
throw new ValidationError(
errors!,
value,
undefined,
undefined,
disableStackTrace,
);
throw disableStackTrace
? new ValidationErrorNoStack(errors!, value, undefined, undefined)
: new ValidationError(errors!, value, undefined, undefined);
result = validated;
},
);
Expand Down
33 changes: 22 additions & 11 deletions src/util/createValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import Reference from '../Reference';
import type { AnySchema } from '../schema';
import isAbsent from './isAbsent';
import ValidationErrorNoStack from '../ValidationErrorNoStack';

export type PanicCallback = (err: Error) => void;

Expand Down Expand Up @@ -98,6 +99,7 @@ export default function createValidation(config: {
label: schema.spec.label,
path: overrides.path || path,
spec: schema.spec,
disableStackTrace: overrides.disableStackTrace || disableStackTrace,
...params,
...overrides.params,
};
Expand All @@ -106,13 +108,25 @@ export default function createValidation(config: {
for (const key of Object.keys(nextParams) as Keys)
nextParams[key] = resolve(nextParams[key]);

const error = new ValidationError(
ValidationError.formatError(overrides.message || message, nextParams),
value,
nextParams.path,
overrides.type || name,
overrides.disableStackTrace ?? disableStackTrace,
);
const error = nextParams.disableStackTrace
? new ValidationErrorNoStack(
ValidationErrorNoStack.formatError(
overrides.message || message,
nextParams,
),
value,
nextParams.path,
overrides.type || name,
)
: new ValidationError(
ValidationError.formatError(
overrides.message || message,
nextParams,
),
value,
nextParams.path,
overrides.type || name,
);
error.params = nextParams;
return error;
}
Expand Down Expand Up @@ -158,10 +172,7 @@ export default function createValidation(config: {
`This test will finish after the validate call has returned`,
);
}
return Promise.resolve(result).then(
handleResult,
handleError,
);
return Promise.resolve(result).then(handleResult, handleError);
}
} catch (err: any) {
handleError(err);
Expand Down
14 changes: 14 additions & 0 deletions test/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
StringSchema,
AnySchema,
ValidationError,
ValidationErrorNoStack,
} from '../src';

describe('Array types', () => {
Expand Down Expand Up @@ -158,6 +159,19 @@ describe('Array types', () => {
);
});

it('should respect disableStackTrace', async () => {
let inst = array().of(object({ str: string().required() }));

const data = [{ str: undefined }, { str: undefined }];
return Promise.all([
expect(inst.strict().validate(data)).rejects.toThrow(ValidationError),

expect(
inst.strict().validate(data, { disableStackTrace: true }),
).rejects.toThrow(ValidationErrorNoStack),
]);
});

it('should compact arrays', () => {
let arr = ['', 1, 0, 4, false, null],
inst = array();
Expand Down
22 changes: 17 additions & 5 deletions test/mixed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
string,
tuple,
ValidationError,
ValidationErrorNoStack,
} from '../src';
import ObjectSchema from '../src/object';
import { ISchema } from '../src/types';
Expand Down Expand Up @@ -338,6 +339,18 @@ describe('Mixed Types ', () => {
]);
});

it('should respect disableStackTrace', () => {
let inst = string().trim();

return Promise.all([
expect(inst.strict().validate(' hi ')).rejects.toThrow(ValidationError),

expect(
inst.strict().validate(' hi ', { disableStackTrace: true }),
).rejects.toThrow(ValidationErrorNoStack),
]);
});

it('should overload test()', () => {
let inst = mixed().test('test', noop);

Expand Down Expand Up @@ -967,11 +980,10 @@ describe('Mixed Types ', () => {
then: (s) => s.defined(),
}),
baz: tuple([string(), number()]),
})
.when(['dummy'], (_, s) => {
}).when(['dummy'], (_, s) => {
return s.shape({
when: string()
})
when: string(),
});
});
});

Expand Down Expand Up @@ -1201,7 +1213,7 @@ describe('Mixed Types ', () => {
oneOf: [],
optional: true,
tests: [],
}
},
},
});
});
Expand Down

0 comments on commit 5852a92

Please sign in to comment.