diff --git a/README.md b/README.md index 86c1df76f..46a404695 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ const parsedUser = await userSchema.validate( + - [Schema basics](#schema-basics) - [Parsing: Transforms](#parsing-transforms) - [Validation: Tests](#validation-tests) diff --git a/src/ValidationError.ts b/src/ValidationError.ts index f02879ff8..89047b909 100644 --- a/src/ValidationError.ts +++ b/src/ValidationError.ts @@ -5,7 +5,10 @@ let strReg = /\$\{\s*(\w+)\s*\}/g; type Params = Record; -export default class ValidationError extends Error { +export default class ValidationError implements Error { + name: string; + message: string; + stack?: string | undefined; value: any; path?: string; type?: string; @@ -38,9 +41,8 @@ export default class ValidationError extends Error { value?: any, field?: string, type?: string, + disableStack?: boolean, ) { - super(); - this.name = 'ValidationError'; this.value = value; this.path = field; @@ -52,7 +54,8 @@ export default class ValidationError extends Error { toArray(errorOrErrors).forEach((err) => { if (ValidationError.isError(err)) { this.errors.push(...err.errors); - this.inner = this.inner.concat(err.inner.length ? err.inner : err); + const innerErrors = err.inner.length ? err.inner : [err]; + this.inner.push(...innerErrors); } else { this.errors.push(err); } @@ -63,6 +66,7 @@ export default class ValidationError extends Error { ? `${this.errors.length} errors occurred` : this.errors[0]; - if (Error.captureStackTrace) Error.captureStackTrace(this, ValidationError); + if (!disableStack && Error.captureStackTrace) + Error.captureStackTrace(this, ValidationError); } } diff --git a/src/schema.ts b/src/schema.ts index b8cf53d26..b43973d0e 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -43,6 +43,7 @@ export type SchemaSpec = { strip?: boolean; strict?: boolean; recursive?: boolean; + disableStackTrace?: boolean; label?: string | undefined; meta?: SchemaMetadata; }; @@ -191,6 +192,7 @@ export default abstract class Schema< strict: false, abortEarly: true, recursive: true, + disableStackTrace: false, nullable: false, optional: true, coerce: true, @@ -345,6 +347,8 @@ export default abstract class Schema< strict: options.strict ?? this.spec.strict, abortEarly: options.abortEarly ?? this.spec.abortEarly, recursive: options.recursive ?? this.spec.recursive, + disableStackTrace: + options.disableStackTrace ?? this.spec.disableStackTrace, }; } @@ -499,7 +503,9 @@ export default abstract class Schema< test(args!, panicOnce, function finishTestRun(err) { if (err) { - nestedErrors = nestedErrors.concat(err); + Array.isArray(err) + ? nestedErrors.push(...err) + : nestedErrors.push(err); } if (--count <= 0) { nextOnce(nestedErrors); @@ -553,6 +559,8 @@ export default abstract class Schema< options?: ValidateOptions, ): Promise { let schema = this.resolve({ ...options, value }); + let disableStackTrace = + options?.disableStackTrace ?? schema.spec.disableStackTrace; return new Promise((resolve, reject) => schema._validate( @@ -563,7 +571,16 @@ export default abstract class Schema< reject(error); }, (errors, validated) => { - if (errors.length) reject(new ValidationError(errors!, validated)); + if (errors.length) + reject( + new ValidationError( + errors!, + validated, + undefined, + undefined, + disableStackTrace, + ), + ); else resolve(validated as this['__outputType']); }, ), @@ -576,6 +593,8 @@ export default abstract class Schema< ): this['__outputType'] { let schema = this.resolve({ ...options, value }); let result: any; + let disableStackTrace = + options?.disableStackTrace ?? schema.spec.disableStackTrace; schema._validate( value, @@ -585,7 +604,14 @@ export default abstract class Schema< throw error; }, (errors, validated) => { - if (errors.length) throw new ValidationError(errors!, value); + if (errors.length) + throw new ValidationError( + errors!, + value, + undefined, + undefined, + disableStackTrace, + ); result = validated; }, ); diff --git a/src/types.ts b/src/types.ts index c63fee563..4a04d59a0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -61,6 +61,10 @@ export interface ValidateOptions { * When false validations will not descend into nested schema (relevant for objects or arrays). Default - true */ recursive?: boolean; + /** + * When true ValidationError instance won't include stack trace information. Default - false + */ + disableStackTrace?: boolean; /** * Any context needed for validating schema conditions (see: when()) */ diff --git a/src/util/createValidation.ts b/src/util/createValidation.ts index 4d946bab5..036fbfdb5 100644 --- a/src/util/createValidation.ts +++ b/src/util/createValidation.ts @@ -22,6 +22,7 @@ export type CreateErrorOptions = { message?: Message; params?: ExtraParams; type?: string; + disableStackTrace?: boolean; }; export type TestContext = { @@ -79,7 +80,12 @@ export default function createValidation(config: { next: NextCallback, ) { const { name, test, params, message, skipAbsent } = config; - let { parent, context, abortEarly = schema.spec.abortEarly } = options; + let { + parent, + context, + abortEarly = schema.spec.abortEarly, + disableStackTrace = schema.spec.disableStackTrace, + } = options; function resolve(item: T | Reference) { return Ref.isRef(item) ? item.getValue(value, parent, context) : item; @@ -105,6 +111,7 @@ export default function createValidation(config: { value, nextParams.path, overrides.type || name, + overrides.disableStackTrace ?? disableStackTrace, ); error.params = nextParams; return error;