-
Notifications
You must be signed in to change notification settings - Fork 8.6k
kbn-config-schema: allow parsing of array and object types from string #57189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
352a9b3
e31e96e
1249312
1a382d5
be4c0e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -250,22 +250,47 @@ 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], | ||
| }, | ||
| { | ||
| 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') { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: I'm checking |
||
| 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], | ||
| }, | ||
|
|
||
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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]"` | ||
| ); | ||
|
Comment on lines
-34
to
+57
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I switched to inline snapshots. Should probably have done this in a separate commit though. No snapshot did change except the ones that were using string data as invalid type, as the error changed to |
||
| }); | ||
|
|
||
| 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]"`); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using the parsed value to create the error message here.
"[1,2,3]"will fails with an error message stating that it received an array instead of an object, even if the actual raw input was a string.I think using the parsed value type makes more sense / is more clear, as least for route validation where the parsing is implicit forqueryand ormultipart/form-datadata.