[7.x] Correctly process paths in map_of and record_of. Do not swallow and use indent for nested one_of error messages. (#49507)

This commit is contained in:
Aleh Zasypkin 2019-10-28 21:40:47 +02:00 committed by GitHub
parent bbca758a09
commit b9e45fedf3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 67 additions and 7 deletions

View file

@ -20,17 +20,18 @@
import { SchemaError, SchemaTypeError, SchemaTypesError } from '.';
export class ValidationError extends SchemaError {
public static extractMessage(error: SchemaTypeError, namespace?: string) {
private static extractMessage(error: SchemaTypeError, namespace?: string, level?: number) {
const path = typeof namespace === 'string' ? [namespace, ...error.path] : error.path;
let message = error.message;
if (error instanceof SchemaTypesError) {
const indentLevel = level || 0;
const childErrorMessages = error.errors.map(childError =>
ValidationError.extractMessage(childError, namespace)
ValidationError.extractMessage(childError, namespace, indentLevel + 1)
);
message = `${message}\n${childErrorMessages
.map(childErrorMessage => `- ${childErrorMessage}`)
.map(childErrorMessage => `${' '.repeat(indentLevel)}- ${childErrorMessage}`)
.join('\n')}`;
}

View file

@ -108,3 +108,23 @@ test('object within mapOf', () => {
expect(type.validate(value)).toEqual(expected);
});
test('error preserves full path', () => {
const type = schema.object({
grandParentKey: schema.object({
parentKey: schema.mapOf(schema.string({ minLength: 2 }), schema.number()),
}),
});
expect(() =>
type.validate({ grandParentKey: { parentKey: { a: 'some-value' } } })
).toThrowErrorMatchingInlineSnapshot(
`"[grandParentKey.parentKey.key(\\"a\\")]: value is [a] but it must have a minimum length of [2]."`
);
expect(() =>
type.validate({ grandParentKey: { parentKey: { ab: 'some-value' } } })
).toThrowErrorMatchingInlineSnapshot(
`"[grandParentKey.parentKey.ab]: expected value of type [number] but got [string]"`
);
});

View file

@ -50,7 +50,7 @@ export class MapOfType<K, V> extends Type<Map<K, V>> {
return `expected value of type [Map] or [object] but got [${typeDetect(value)}]`;
case 'map.key':
case 'map.value':
const childPathWithIndex = reason.path.slice();
const childPathWithIndex = path.slice();
childPathWithIndex.splice(
path.length,
0,

View file

@ -125,3 +125,20 @@ test('fails if not matching literal', () => {
expect(() => type.validate('bar')).toThrowErrorMatchingSnapshot();
});
test('fails if nested union type fail', () => {
const type = schema.oneOf([
schema.oneOf([schema.boolean()]),
schema.oneOf([schema.oneOf([schema.object({}), schema.number()])]),
]);
expect(() => type.validate('aaa')).toThrowErrorMatchingInlineSnapshot(`
"types that failed validation:
- [0]: types that failed validation:
- [0]: expected value of type [boolean] but got [string]
- [1]: types that failed validation:
- [0]: types that failed validation:
- [0]: expected a plain object value, but found [string] instead.
- [1]: expected value of type [number] but got [string]"
`);
});

View file

@ -120,3 +120,23 @@ test('object within recordOf', () => {
expect(type.validate(value)).toEqual({ foo: { bar: 123 } });
});
test('error preserves full path', () => {
const type = schema.object({
grandParentKey: schema.object({
parentKey: schema.recordOf(schema.string({ minLength: 2 }), schema.number()),
}),
});
expect(() =>
type.validate({ grandParentKey: { parentKey: { a: 'some-value' } } })
).toThrowErrorMatchingInlineSnapshot(
`"[grandParentKey.parentKey.key(\\"a\\")]: value is [a] but it must have a minimum length of [2]."`
);
expect(() =>
type.validate({ grandParentKey: { parentKey: { ab: 'some-value' } } })
).toThrowErrorMatchingInlineSnapshot(
`"[grandParentKey.parentKey.ab]: expected value of type [number] but got [string]"`
);
});

View file

@ -42,7 +42,7 @@ export class RecordOfType<K extends string, V> extends Type<Record<K, V>> {
return `expected value of type [object] but got [${typeDetect(value)}]`;
case 'record.key':
case 'record.value':
const childPathWithIndex = reason.path.slice();
const childPathWithIndex = path.slice();
childPathWithIndex.splice(
path.length,
0,

View file

@ -41,7 +41,9 @@ export class UnionType<RTS extends Array<Type<any>>, T> extends Type<T> {
const childPathWithIndex = e.path.slice();
childPathWithIndex.splice(path.length, 0, index.toString());
return new SchemaTypeError(e.message, childPathWithIndex);
return e instanceof SchemaTypesError
? new SchemaTypesError(e.message, childPathWithIndex, e.errors)
: new SchemaTypeError(e.message, childPathWithIndex);
})
);
}

View file

@ -337,7 +337,7 @@ describe('copy to space', () => {
expect(() =>
resolveConflicts.routeValidation.body!.validate(payload)
).toThrowErrorMatchingInlineSnapshot(
`"[key(\\"invalid-space-id!@#$%^&*()\\")]: Invalid space id: invalid-space-id!@#$%^&*()"`
`"[retries.key(\\"invalid-space-id!@#$%^&*()\\")]: Invalid space id: invalid-space-id!@#$%^&*()"`
);
});