Skip to content

Commit

Permalink
jquense#2043 add a ValidationErrorNoStack class to be used when no st…
Browse files Browse the repository at this point in the history
…ack trace is required
  • Loading branch information
tedeschia committed Nov 24, 2023
1 parent 35085a8 commit 0161196
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 17 deletions.
69 changes: 69 additions & 0 deletions src/BasicValidationError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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: ValidationErrorNoStack[];

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
| ValidationErrorNoStack
| readonly ValidationErrorNoStack[],
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';
}
15 changes: 5 additions & 10 deletions src/ValidationError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ let strReg = /\$\{\s*(\w+)\s*\}/g;

type Params = Record<string, unknown>;

export default class ValidationError implements Error {
name: string;
message: string;
stack?: string | undefined;
export default class ValidationError extends Error {
value: any;
path?: string;
type?: string;
Expand Down Expand Up @@ -41,8 +38,9 @@ export default class ValidationError implements Error {
value?: any,
field?: string,
type?: string,
disableStack?: boolean,
) {
super();

this.name = 'ValidationError';
this.value = value;
this.path = field;
Expand All @@ -54,8 +52,7 @@ export default class ValidationError implements Error {
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);
this.inner = this.inner.concat(err.inner.length ? err.inner : err);
} else {
this.errors.push(err);
}
Expand All @@ -66,8 +63,6 @@ export default class ValidationError implements Error {
? `${this.errors.length} errors occurred`
: this.errors[0];

if (!disableStack && Error.captureStackTrace)
Error.captureStackTrace(this, ValidationError);
if (Error.captureStackTrace) Error.captureStackTrace(this, ValidationError);
}
[Symbol.toStringTag] = 'Error';
}
28 changes: 21 additions & 7 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 '../BasicValidationError';

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
14 changes: 14 additions & 0 deletions test/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
AnySchema,
ValidationError,
} from '../src';
import ValidationErrorNoStack from '../src/BasicValidationError';

describe('Array types', () => {
describe('casting', () => {
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
13 changes: 13 additions & 0 deletions test/mixed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
tuple,
ValidationError,
} from '../src';
import ValidationErrorNoStack from '../src/BasicValidationError';
import ObjectSchema from '../src/object';
import { ISchema } from '../src/types';
import { ensureSync, validateAll } from './helpers';
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

0 comments on commit 0161196

Please sign in to comment.