Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions packages/kbn-config-schema/src/errors/validation_error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')}`;
}

Expand Down
20 changes: 20 additions & 0 deletions packages/kbn-config-schema/src/types/map_of_type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]"`
);
});
2 changes: 1 addition & 1 deletion packages/kbn-config-schema/src/types/map_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
17 changes: 17 additions & 0 deletions packages/kbn-config-schema/src/types/one_of_type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]"
`);
});
20 changes: 20 additions & 0 deletions packages/kbn-config-schema/src/types/record_of_type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]"`
);
});
2 changes: 1 addition & 1 deletion packages/kbn-config-schema/src/types/record_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion packages/kbn-config-schema/src/types/union_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
})
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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!@#$%^&*()"`
);
});

Expand Down