mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
* allow parsing from string for object-ish and array types * update snapshots * fix FTR assertion * add documentation note about using a json string as input
This commit is contained in:
parent
47cb2d15fd
commit
3a06f9717d
18 changed files with 277 additions and 94 deletions
|
@ -227,6 +227,9 @@ __Usage:__
|
|||
const valueSchema = schema.arrayOf(schema.number());
|
||||
```
|
||||
|
||||
__Notes:__
|
||||
* The `schema.arrayOf()` also supports a json string as input if it can be safely parsed using `JSON.parse` and if the resulting value is an array.
|
||||
|
||||
#### `schema.object()`
|
||||
|
||||
Validates input data as an object with a predefined set of properties.
|
||||
|
@ -249,6 +252,7 @@ const valueSchema = schema.object({
|
|||
__Notes:__
|
||||
* Using `allowUnknowns` is discouraged and should only be used in exceptional circumstances. Consider using `schema.recordOf()` instead.
|
||||
* Currently `schema.object()` always has a default value of `{}`, but this may change in the near future. Try to not rely on this behaviour and specify default value explicitly or use `schema.maybe()` if the value is optional.
|
||||
* `schema.object()` also supports a json string as input if it can be safely parsed using `JSON.parse` and if the resulting value is a plain object.
|
||||
|
||||
#### `schema.recordOf()`
|
||||
|
||||
|
@ -267,6 +271,7 @@ const valueSchema = schema.recordOf(schema.string(), schema.number());
|
|||
|
||||
__Notes:__
|
||||
* You can use a union of literal types as a record's key schema to restrict record to a specific set of keys, e.g. `schema.oneOf([schema.literal('isEnabled'), schema.literal('name')])`.
|
||||
* `schema.recordOf()` also supports a json string as input if it can be safely parsed using `JSON.parse` and if the resulting value is a plain object.
|
||||
|
||||
#### `schema.mapOf()`
|
||||
|
||||
|
@ -283,6 +288,10 @@ __Usage:__
|
|||
const valueSchema = schema.mapOf(schema.string(), schema.number());
|
||||
```
|
||||
|
||||
__Notes:__
|
||||
* You can use a union of literal types as a record's key schema to restrict record to a specific set of keys, e.g. `schema.oneOf([schema.literal('isEnabled'), schema.literal('name')])`.
|
||||
* `schema.mapOf()` also supports a json string as input if it can be safely parsed using `JSON.parse` and if the resulting value is a plain object.
|
||||
|
||||
### Advanced types
|
||||
|
||||
#### `schema.oneOf()`
|
||||
|
|
|
@ -250,12 +250,23 @@ export const internals = Joi.extend([
|
|||
|
||||
base: Joi.object(),
|
||||
coerce(value: any, state: State, options: ValidationOptions) {
|
||||
// If value isn't defined, let Joi handle default value if it's defined.
|
||||
if (value !== undefined && !isPlainObject(value)) {
|
||||
return this.createError('object.base', { value }, state, options);
|
||||
if (value === undefined || isPlainObject(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value;
|
||||
if (options.convert && typeof value === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (isPlainObject(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
return this.createError('object.base', { value: parsed }, state, options);
|
||||
} catch (e) {
|
||||
return this.createError('object.parse', { value }, state, options);
|
||||
}
|
||||
}
|
||||
|
||||
return this.createError('object.base', { value }, state, options);
|
||||
},
|
||||
rules: [anyCustomRule],
|
||||
},
|
||||
|
@ -263,9 +274,23 @@ export const internals = Joi.extend([
|
|||
name: 'map',
|
||||
|
||||
coerce(value: any, state: State, options: ValidationOptions) {
|
||||
if (value === undefined) {
|
||||
return value;
|
||||
}
|
||||
if (isPlainObject(value)) {
|
||||
return new Map(Object.entries(value));
|
||||
}
|
||||
if (options.convert && typeof value === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (isPlainObject(parsed)) {
|
||||
return new Map(Object.entries(parsed));
|
||||
}
|
||||
return this.createError('map.base', { value: parsed }, state, options);
|
||||
} catch (e) {
|
||||
return this.createError('map.parse', { value }, state, options);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
@ -321,11 +346,23 @@ export const internals = Joi.extend([
|
|||
{
|
||||
name: 'record',
|
||||
pre(value: any, state: State, options: ValidationOptions) {
|
||||
if (!isPlainObject(value)) {
|
||||
return this.createError('record.base', { value }, state, options);
|
||||
if (value === undefined || isPlainObject(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value as any;
|
||||
if (options.convert && typeof value === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (isPlainObject(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
return this.createError('record.base', { value: parsed }, state, options);
|
||||
} catch (e) {
|
||||
return this.createError('record.parse', { value }, state, options);
|
||||
}
|
||||
}
|
||||
|
||||
return this.createError('record.base', { value }, state, options);
|
||||
},
|
||||
rules: [
|
||||
anyCustomRule,
|
||||
|
@ -371,12 +408,23 @@ export const internals = Joi.extend([
|
|||
|
||||
base: Joi.array(),
|
||||
coerce(value: any, state: State, options: ValidationOptions) {
|
||||
// If value isn't defined, let Joi handle default value if it's defined.
|
||||
if (value !== undefined && !Array.isArray(value)) {
|
||||
return this.createError('array.base', { value }, state, options);
|
||||
if (value === undefined || Array.isArray(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value;
|
||||
if (options.convert && typeof value === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
return this.createError('array.base', { value: parsed }, state, options);
|
||||
} catch (e) {
|
||||
return this.createError('array.parse', { value }, state, options);
|
||||
}
|
||||
}
|
||||
|
||||
return this.createError('array.base', { value }, state, options);
|
||||
},
|
||||
rules: [anyCustomRule],
|
||||
},
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`#maxSize returns error when more items 1`] = `"array size is [2], but cannot be greater than [1]"`;
|
||||
|
||||
exports[`#minSize returns error when fewer items 1`] = `"array size is [1], but cannot be smaller than [2]"`;
|
||||
|
||||
exports[`fails for null values if optional 1`] = `"[0]: expected value of type [string] but got [null]"`;
|
||||
|
||||
exports[`fails if mixed types of content in array 1`] = `"[2]: expected value of type [string] but got [boolean]"`;
|
||||
|
||||
exports[`fails if wrong input type 1`] = `"expected value of type [array] but got [string]"`;
|
||||
|
||||
exports[`fails if wrong type of content in array 1`] = `"[0]: expected value of type [string] but got [number]"`;
|
||||
|
||||
exports[`includes namespace in failure when wrong item type 1`] = `"[foo-namespace.0]: expected value of type [string] but got [number]"`;
|
||||
|
||||
exports[`includes namespace in failure when wrong top-level type 1`] = `"[foo-namespace]: expected value of type [array] but got [string]"`;
|
||||
|
||||
exports[`object within array with required 1`] = `"[0.foo]: expected value of type [string] but got [undefined]"`;
|
|
@ -1,11 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`fails when not receiving expected key type 1`] = `"[key(\\"name\\")]: expected value of type [number] but got [string]"`;
|
||||
|
||||
exports[`fails when not receiving expected value type 1`] = `"[name]: expected value of type [string] but got [number]"`;
|
||||
|
||||
exports[`includes namespace in failure when wrong key type 1`] = `"[foo-namespace.key(\\"name\\")]: expected value of type [number] but got [string]"`;
|
||||
|
||||
exports[`includes namespace in failure when wrong top-level type 1`] = `"[foo-namespace]: expected value of type [Map] or [object] but got [Array]"`;
|
||||
|
||||
exports[`includes namespace in failure when wrong value type 1`] = `"[foo-namespace.name]: expected value of type [string] but got [number]"`;
|
|
@ -1,24 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`allowUnknowns = true affects only own keys 1`] = `"[foo.baz]: definition for this key is missing"`;
|
||||
|
||||
exports[`called with wrong type 1`] = `"expected a plain object value, but found [string] instead."`;
|
||||
|
||||
exports[`called with wrong type 2`] = `"expected a plain object value, but found [number] instead."`;
|
||||
|
||||
exports[`does not allow unknown keys when allowUnknowns = false 1`] = `"[bar]: definition for this key is missing"`;
|
||||
|
||||
exports[`fails if key does not exist in schema 1`] = `"[bar]: definition for this key is missing"`;
|
||||
|
||||
exports[`fails if missing required value 1`] = `"[name]: expected value of type [string] but got [undefined]"`;
|
||||
|
||||
exports[`handles oneOf 1`] = `
|
||||
"[key]: types that failed validation:
|
||||
- [key.0]: expected value of type [string] but got [number]"
|
||||
`;
|
||||
|
||||
exports[`includes namespace in failure when wrong top-level type 1`] = `"[foo-namespace]: expected a plain object value, but found [Array] instead."`;
|
||||
|
||||
exports[`includes namespace in failure when wrong value type 1`] = `"[foo-namespace.foo]: expected value of type [string] but got [number]"`;
|
||||
|
||||
exports[`object within object with required 1`] = `"[foo.bar]: expected value of type [string] but got [undefined]"`;
|
|
@ -24,29 +24,65 @@ test('returns value if it matches the type', () => {
|
|||
expect(type.validate(['foo', 'bar', 'baz'])).toEqual(['foo', 'bar', 'baz']);
|
||||
});
|
||||
|
||||
test('properly parse the value if input is a string', () => {
|
||||
const type = schema.arrayOf(schema.string());
|
||||
expect(type.validate('["foo", "bar", "baz"]')).toEqual(['foo', 'bar', 'baz']);
|
||||
});
|
||||
|
||||
test('fails if wrong input type', () => {
|
||||
const type = schema.arrayOf(schema.string());
|
||||
expect(() => type.validate('test')).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate(12)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"expected value of type [array] but got [number]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails if string input cannot be parsed', () => {
|
||||
const type = schema.arrayOf(schema.string());
|
||||
expect(() => type.validate('test')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"could not parse array value from [test]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails with correct type if parsed input is not an array', () => {
|
||||
const type = schema.arrayOf(schema.string());
|
||||
expect(() => type.validate('{"foo": "bar"}')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"expected value of type [array] but got [Object]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('includes namespace in failure when wrong top-level type', () => {
|
||||
const type = schema.arrayOf(schema.string());
|
||||
expect(() => type.validate('test', {}, 'foo-namespace')).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate('test', {}, 'foo-namespace')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[foo-namespace]: could not parse array value from [test]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('includes namespace in failure when wrong item type', () => {
|
||||
const type = schema.arrayOf(schema.string());
|
||||
expect(() => type.validate([123], {}, 'foo-namespace')).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate([123], {}, 'foo-namespace')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[foo-namespace.0]: expected value of type [string] but got [number]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails if wrong type of content in array', () => {
|
||||
const type = schema.arrayOf(schema.string());
|
||||
expect(() => type.validate([1, 2, 3])).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate([1, 2, 3])).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[0]: expected value of type [string] but got [number]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails when parsing if wrong type of content in array', () => {
|
||||
const type = schema.arrayOf(schema.string());
|
||||
expect(() => type.validate('[1, 2, 3]')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[0]: expected value of type [string] but got [number]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails if mixed types of content in array', () => {
|
||||
const type = schema.arrayOf(schema.string());
|
||||
expect(() => type.validate(['foo', 'bar', true, {}])).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate(['foo', 'bar', true, {}])).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[2]: expected value of type [string] but got [boolean]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('returns empty array if input is empty but type has default value', () => {
|
||||
|
@ -61,7 +97,9 @@ test('returns empty array if input is empty even if type is required', () => {
|
|||
|
||||
test('fails for null values if optional', () => {
|
||||
const type = schema.arrayOf(schema.maybe(schema.string()));
|
||||
expect(() => type.validate([null])).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate([null])).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[0]: expected value of type [string] but got [null]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('handles default values for undefined values', () => {
|
||||
|
@ -108,7 +146,9 @@ test('object within array with required', () => {
|
|||
|
||||
const value = [{}];
|
||||
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[0.foo]: expected value of type [string] but got [undefined]"`
|
||||
);
|
||||
});
|
||||
|
||||
describe('#minSize', () => {
|
||||
|
@ -119,7 +159,7 @@ describe('#minSize', () => {
|
|||
test('returns error when fewer items', () => {
|
||||
expect(() =>
|
||||
schema.arrayOf(schema.string(), { minSize: 2 }).validate(['foo'])
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
).toThrowErrorMatchingInlineSnapshot(`"array size is [1], but cannot be smaller than [2]"`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -131,6 +171,6 @@ describe('#maxSize', () => {
|
|||
test('returns error when more items', () => {
|
||||
expect(() =>
|
||||
schema.arrayOf(schema.string(), { maxSize: 1 }).validate(['foo', 'bar'])
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
).toThrowErrorMatchingInlineSnapshot(`"array size is [2], but cannot be greater than [1]"`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -49,6 +49,8 @@ export class ArrayType<T> extends Type<T[]> {
|
|||
case 'any.required':
|
||||
case 'array.base':
|
||||
return `expected value of type [array] but got [${typeDetect(value)}]`;
|
||||
case 'array.parse':
|
||||
return `could not parse array value from [${value}]`;
|
||||
case 'array.min':
|
||||
return `array size is [${value.length}], but cannot be smaller than [${limit}]`;
|
||||
case 'array.max':
|
||||
|
|
|
@ -29,13 +29,46 @@ test('handles object as input', () => {
|
|||
expect(type.validate(value)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('properly parse the value if input is a string', () => {
|
||||
const type = schema.mapOf(schema.string(), schema.string());
|
||||
const value = `{"name": "foo"}`;
|
||||
const expected = new Map([['name', 'foo']]);
|
||||
|
||||
expect(type.validate(value)).toEqual(expected);
|
||||
});
|
||||
|
||||
test('fails if string input cannot be parsed', () => {
|
||||
const type = schema.mapOf(schema.string(), schema.string());
|
||||
expect(() => type.validate(`invalidjson`)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"could not parse map value from [invalidjson]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails with correct type if parsed input is not an object', () => {
|
||||
const type = schema.mapOf(schema.string(), schema.string());
|
||||
expect(() => type.validate('[1,2,3]')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"expected value of type [Map] or [object] but got [Array]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails when not receiving expected value type', () => {
|
||||
const type = schema.mapOf(schema.string(), schema.string());
|
||||
const value = {
|
||||
name: 123,
|
||||
};
|
||||
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[name]: expected value of type [string] but got [number]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails after parsing when not receiving expected value type', () => {
|
||||
const type = schema.mapOf(schema.string(), schema.string());
|
||||
const value = `{"name": 123}`;
|
||||
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[name]: expected value of type [string] but got [number]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails when not receiving expected key type', () => {
|
||||
|
@ -44,12 +77,25 @@ test('fails when not receiving expected key type', () => {
|
|||
name: 'foo',
|
||||
};
|
||||
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[key(\\"name\\")]: expected value of type [number] but got [string]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails after parsing when not receiving expected key type', () => {
|
||||
const type = schema.mapOf(schema.number(), schema.string());
|
||||
const value = `{"name": "foo"}`;
|
||||
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[key(\\"name\\")]: expected value of type [number] but got [string]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('includes namespace in failure when wrong top-level type', () => {
|
||||
const type = schema.mapOf(schema.string(), schema.string());
|
||||
expect(() => type.validate([], {}, 'foo-namespace')).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate([], {}, 'foo-namespace')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[foo-namespace]: expected value of type [Map] or [object] but got [Array]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('includes namespace in failure when wrong value type', () => {
|
||||
|
@ -58,7 +104,9 @@ test('includes namespace in failure when wrong value type', () => {
|
|||
name: 123,
|
||||
};
|
||||
|
||||
expect(() => type.validate(value, {}, 'foo-namespace')).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate(value, {}, 'foo-namespace')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[foo-namespace.name]: expected value of type [string] but got [number]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('includes namespace in failure when wrong key type', () => {
|
||||
|
@ -67,7 +115,9 @@ test('includes namespace in failure when wrong key type', () => {
|
|||
name: 'foo',
|
||||
};
|
||||
|
||||
expect(() => type.validate(value, {}, 'foo-namespace')).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate(value, {}, 'foo-namespace')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[foo-namespace.key(\\"name\\")]: expected value of type [number] but got [string]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('returns default value if undefined', () => {
|
||||
|
|
|
@ -48,6 +48,8 @@ export class MapOfType<K, V> extends Type<Map<K, V>> {
|
|||
case 'any.required':
|
||||
case 'map.base':
|
||||
return `expected value of type [Map] or [object] but got [${typeDetect(value)}]`;
|
||||
case 'map.parse':
|
||||
return `could not parse map value from [${value}]`;
|
||||
case 'map.key':
|
||||
case 'map.value':
|
||||
const childPathWithIndex = path.slice();
|
||||
|
|
|
@ -30,13 +30,42 @@ test('returns value by default', () => {
|
|||
expect(type.validate(value)).toEqual({ name: 'test' });
|
||||
});
|
||||
|
||||
test('properly parse the value if input is a string', () => {
|
||||
const type = schema.object({
|
||||
name: schema.string(),
|
||||
});
|
||||
const value = `{"name": "test"}`;
|
||||
|
||||
expect(type.validate(value)).toEqual({ name: 'test' });
|
||||
});
|
||||
|
||||
test('fails if string input cannot be parsed', () => {
|
||||
const type = schema.object({
|
||||
name: schema.string(),
|
||||
});
|
||||
expect(() => type.validate(`invalidjson`)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"could not parse object value from [invalidjson]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails with correct type if parsed input is not an object', () => {
|
||||
const type = schema.object({
|
||||
name: schema.string(),
|
||||
});
|
||||
expect(() => type.validate('[1,2,3]')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"expected a plain object value, but found [Array] instead."`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails if missing required value', () => {
|
||||
const type = schema.object({
|
||||
name: schema.string(),
|
||||
});
|
||||
const value = {};
|
||||
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[name]: expected value of type [string] but got [undefined]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('returns value if undefined string with default', () => {
|
||||
|
@ -57,7 +86,9 @@ test('fails if key does not exist in schema', () => {
|
|||
foo: 'bar',
|
||||
};
|
||||
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[bar]: definition for this key is missing"`
|
||||
);
|
||||
});
|
||||
|
||||
test('defined object within object', () => {
|
||||
|
@ -96,7 +127,9 @@ test('object within object with required', () => {
|
|||
});
|
||||
const value = { foo: {} };
|
||||
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[foo.bar]: expected value of type [string] but got [undefined]"`
|
||||
);
|
||||
});
|
||||
|
||||
describe('#validate', () => {
|
||||
|
@ -127,8 +160,12 @@ describe('#validate', () => {
|
|||
test('called with wrong type', () => {
|
||||
const type = schema.object({});
|
||||
|
||||
expect(() => type.validate('foo')).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate(123)).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate('foo')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"could not parse object value from [foo]"`
|
||||
);
|
||||
expect(() => type.validate(123)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"expected a plain object value, but found [number] instead."`
|
||||
);
|
||||
});
|
||||
|
||||
test('handles oneOf', () => {
|
||||
|
@ -137,7 +174,10 @@ test('handles oneOf', () => {
|
|||
});
|
||||
|
||||
expect(type.validate({ key: 'foo' })).toEqual({ key: 'foo' });
|
||||
expect(() => type.validate({ key: 123 })).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate({ key: 123 })).toThrowErrorMatchingInlineSnapshot(`
|
||||
"[key]: types that failed validation:
|
||||
- [key.0]: expected value of type [string] but got [number]"
|
||||
`);
|
||||
});
|
||||
|
||||
test('handles references', () => {
|
||||
|
@ -186,7 +226,9 @@ test('includes namespace in failure when wrong top-level type', () => {
|
|||
foo: schema.string(),
|
||||
});
|
||||
|
||||
expect(() => type.validate([], {}, 'foo-namespace')).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate([], {}, 'foo-namespace')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[foo-namespace]: expected a plain object value, but found [Array] instead."`
|
||||
);
|
||||
});
|
||||
|
||||
test('includes namespace in failure when wrong value type', () => {
|
||||
|
@ -197,7 +239,9 @@ test('includes namespace in failure when wrong value type', () => {
|
|||
foo: 123,
|
||||
};
|
||||
|
||||
expect(() => type.validate(value, {}, 'foo-namespace')).toThrowErrorMatchingSnapshot();
|
||||
expect(() => type.validate(value, {}, 'foo-namespace')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[foo-namespace.foo]: expected value of type [string] but got [number]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('individual keys can validated', () => {
|
||||
|
@ -241,7 +285,7 @@ test('allowUnknowns = true affects only own keys', () => {
|
|||
baz: 'baz',
|
||||
},
|
||||
})
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
).toThrowErrorMatchingInlineSnapshot(`"[foo.baz]: definition for this key is missing"`);
|
||||
});
|
||||
|
||||
test('does not allow unknown keys when allowUnknowns = false', () => {
|
||||
|
@ -253,5 +297,5 @@ test('does not allow unknown keys when allowUnknowns = false', () => {
|
|||
type.validate({
|
||||
bar: 'baz',
|
||||
})
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
).toThrowErrorMatchingInlineSnapshot(`"[bar]: definition for this key is missing"`);
|
||||
});
|
||||
|
|
|
@ -61,6 +61,8 @@ export class ObjectType<P extends Props = any> extends Type<ObjectResultType<P>>
|
|||
case 'any.required':
|
||||
case 'object.base':
|
||||
return `expected a plain object value, but found [${typeDetect(value)}] instead.`;
|
||||
case 'object.parse':
|
||||
return `could not parse object value from [${value}]`;
|
||||
case 'object.allowUnknown':
|
||||
return `definition for this key is missing`;
|
||||
case 'object.child':
|
||||
|
|
|
@ -138,7 +138,7 @@ test('fails if nested union type fail', () => {
|
|||
- [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.
|
||||
- [0]: could not parse object value from [aaa]
|
||||
- [1]: expected value of type [number] but got [string]"
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -27,6 +27,20 @@ test('handles object as input', () => {
|
|||
expect(type.validate(value)).toEqual({ name: 'foo' });
|
||||
});
|
||||
|
||||
test('properly parse the value if input is a string', () => {
|
||||
const type = schema.recordOf(schema.string(), schema.string());
|
||||
const value = `{"name": "foo"}`;
|
||||
expect(type.validate(value)).toEqual({ name: 'foo' });
|
||||
});
|
||||
|
||||
test('fails with correct type if parsed input is a plain object', () => {
|
||||
const type = schema.recordOf(schema.string(), schema.string());
|
||||
const value = `["a", "b"]`;
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"expected value of type [object] but got [Array]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails when not receiving expected value type', () => {
|
||||
const type = schema.recordOf(schema.string(), schema.string());
|
||||
const value = {
|
||||
|
@ -38,6 +52,15 @@ test('fails when not receiving expected value type', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('fails after parsing when not receiving expected value type', () => {
|
||||
const type = schema.recordOf(schema.string(), schema.string());
|
||||
const value = `{"name": 123}`;
|
||||
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[name]: expected value of type [string] but got [number]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('fails when not receiving expected key type', () => {
|
||||
const type = schema.recordOf(
|
||||
schema.oneOf([schema.literal('nickName'), schema.literal('lastName')]),
|
||||
|
@ -55,6 +78,21 @@ test('fails when not receiving expected key type', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
test('fails after parsing when not receiving expected key type', () => {
|
||||
const type = schema.recordOf(
|
||||
schema.oneOf([schema.literal('nickName'), schema.literal('lastName')]),
|
||||
schema.string()
|
||||
);
|
||||
|
||||
const value = `{"name": "foo"}`;
|
||||
|
||||
expect(() => type.validate(value)).toThrowErrorMatchingInlineSnapshot(`
|
||||
"[key(\\"name\\")]: types that failed validation:
|
||||
- [0]: expected value to equal [nickName] but got [name]
|
||||
- [1]: expected value to equal [lastName] but got [name]"
|
||||
`);
|
||||
});
|
||||
|
||||
test('includes namespace in failure when wrong top-level type', () => {
|
||||
const type = schema.recordOf(schema.string(), schema.string());
|
||||
expect(() => type.validate([], {}, 'foo-namespace')).toThrowErrorMatchingInlineSnapshot(
|
||||
|
|
|
@ -40,6 +40,8 @@ export class RecordOfType<K extends string, V> extends Type<Record<K, V>> {
|
|||
case 'any.required':
|
||||
case 'record.base':
|
||||
return `expected value of type [object] but got [${typeDetect(value)}]`;
|
||||
case 'record.parse':
|
||||
return `could not parse record value from [${value}]`;
|
||||
case 'record.key':
|
||||
case 'record.value':
|
||||
const childPathWithIndex = path.slice();
|
||||
|
|
|
@ -150,7 +150,7 @@ describe('params validation', () => {
|
|||
expect(() => {
|
||||
validateParams(actionType, { documents: ['should be an object'] });
|
||||
}).toThrowErrorMatchingInlineSnapshot(
|
||||
`"error validating action params: [documents.0]: expected value of type [object] but got [string]"`
|
||||
`"error validating action params: [documents.0]: could not parse record value from [should be an object]"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -130,7 +130,7 @@ describe('config validation', () => {
|
|||
validateConfig(actionType, config);
|
||||
}).toThrowErrorMatchingInlineSnapshot(`
|
||||
"error validating action type config: [headers]: types that failed validation:
|
||||
- [headers.0]: expected value of type [object] but got [string]
|
||||
- [headers.0]: could not parse record value from [application/json]
|
||||
- [headers.1]: expected value to equal [null] but got [application/json]"
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -93,7 +93,7 @@ describe('#disabledFeatures', () => {
|
|||
disabledFeatures: 'foo',
|
||||
})
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[disabledFeatures]: expected value of type [array] but got [string]"`
|
||||
`"[disabledFeatures]: could not parse array value from [foo]"`
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ export default function({ getService }) {
|
|||
uri = `${BASE_URI}?${querystring.stringify(params)}`;
|
||||
({ body } = await supertest.get(uri).expect(400));
|
||||
expect(body.message).to.contain(
|
||||
'[request query.meta_fields]: expected value of type [array]'
|
||||
'[request query.meta_fields]: could not parse array value from [stringValue]'
|
||||
);
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue