From eefd4270d468787fffe307ee7ca87b6a2a8f9600 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 11 May 2021 21:29:18 +0200 Subject: [PATCH 01/30] bump joi to 17.4.0, start adapting stuff --- package.json | 4 +- .../kbn-config-schema/src/internals/index.ts | 468 ++++++++++-------- .../kbn-config-schema/src/types/maybe_type.ts | 2 +- packages/kbn-config-schema/src/types/type.ts | 19 +- yarn.lock | 50 +- 5 files changed, 282 insertions(+), 261 deletions(-) diff --git a/package.json b/package.json index 81ffe913f0192..19958d6837c89 100644 --- a/package.json +++ b/package.json @@ -254,7 +254,7 @@ "io-ts": "^2.0.5", "ipaddr.js": "2.0.0", "isbinaryfile": "4.0.2", - "joi": "^13.5.2", + "joi": "^17.4.0", "jquery": "^3.5.0", "js-levenshtein": "^1.1.6", "js-search": "^1.4.3", @@ -543,7 +543,7 @@ "@types/jest": "^26.0.22", "@types/jest-specific-snapshot": "^0.5.5", "@types/jest-when": "^2.7.2", - "@types/joi": "^13.4.2", + "@types/joi": "^17.2.3", "@types/jquery": "^3.3.31", "@types/js-search": "^1.4.0", "@types/js-yaml": "^3.11.1", diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index 56e855c6c1b73..d7289fbb4b217 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -7,15 +7,14 @@ */ import Joi from 'joi'; -import { +import type { AnySchema, JoiRoot, Reference, - Rules, + ExtensionRule, SchemaLike, - State, + CustomHelpers, ValidationErrorItem, - ValidationOptions, } from 'joi'; import { isPlainObject } from 'lodash'; import { isDuration } from 'moment'; @@ -29,53 +28,53 @@ function isMap(o: any): o is Map { return o instanceof Map; } -const anyCustomRule: Rules = { - name: 'custom', - params: { - validator: Joi.func().maxArity(1).required(), +const anyCustomRule: ExtensionRule = { + multi: true, + args: [ + { + name: 'validator', + assert: Joi.func().maxArity(1).required(), + }, + ], + method(validator) { + // @ts-expect-error $_ helpers not present on on the typedef + return this.$_addRule({ name: 'custom', args: { validator } }); }, - validate(params, value, state, options) { + validate(value, { error }, args, options) { let validationResultMessage; try { - validationResultMessage = params.validator(value); + validationResultMessage = args.validator(value); } catch (e) { validationResultMessage = e.message || e; } if (typeof validationResultMessage === 'string') { - return this.createError( - 'any.custom', - { value, message: validationResultMessage }, - state, - options - ); + return error('any.custom', { validator: args.validator }); } return value; }, }; -/** - * @internal - */ -export const internals = Joi.extend([ +export const internals: JoiRoot = Joi.extend( { - name: 'any', - - rules: [anyCustomRule], + type: 'any', + rules: { + custom: anyCustomRule, + }, }, { - name: 'boolean', - + type: 'boolean', base: Joi.boolean(), - coerce(value: any, state: State, options: ValidationOptions) { + coerce(value, { error }: CustomHelpers) { // If value isn't defined, let Joi handle default value if it's defined. if (value === undefined) { - return value; + return { + value, + }; } // Allow strings 'true' and 'false' to be coerced to booleans (case-insensitive). - // From Joi docs on `Joi.boolean`: // > Generates a schema object that matches a boolean data type. Can also // > be called via bool(). If the validation convert option is on @@ -87,135 +86,156 @@ export const internals = Joi.extend([ } if (typeof value !== 'boolean') { - return this.createError('boolean.base', { value }, state, options); + return { + errors: [error('boolean.base')], + }; } - return value; + return { + value, + }; + }, + rules: { + custom: anyCustomRule, }, - rules: [anyCustomRule], }, { - name: 'binary', - + type: 'binary', base: Joi.binary(), - coerce(value: any, state: State, options: ValidationOptions) { + coerce(value, { error }) { // If value isn't defined, let Joi handle default value if it's defined. if (value !== undefined && !(typeof value === 'object' && Buffer.isBuffer(value))) { - return this.createError('binary.base', { value }, state, options); + return { + errors: [error('binary.base')], + }; } - return value; + return { value }; + }, + rules: { + custom: anyCustomRule, }, - rules: [anyCustomRule], }, { - name: 'stream', + type: 'stream', - pre(value: any, state: State, options: ValidationOptions) { + prepare(value, { error }) { // If value isn't defined, let Joi handle default value if it's defined. if (value instanceof Stream) { - return value as any; + return { value }; } - - return this.createError('stream.base', { value }, state, options); + return { + errors: [error('stream.base')], + }; + }, + rules: { + custom: anyCustomRule, }, - rules: [anyCustomRule], }, { - name: 'string', - + type: 'string', base: Joi.string(), - rules: [anyCustomRule], + rules: { + custom: anyCustomRule, + }, }, { - name: 'bytes', + type: 'bytes', - coerce(value: any, state: State, options: ValidationOptions) { + coerce(value: any, { error }) { try { if (typeof value === 'string') { - return ByteSizeValue.parse(value); + return { value: ByteSizeValue.parse(value) }; } if (typeof value === 'number') { - return new ByteSizeValue(value); + return { value: new ByteSizeValue(value) }; } } catch (e) { - return this.createError('bytes.parse', { value, message: e.message }, state, options); + return { + errors: [error('bytes.parse')], + }; } - - return value; + return { value }; }, - pre(value: any, state: State, options: ValidationOptions) { + prepare(value, { error }) { // If value isn't defined, let Joi handle default value if it's defined. if (value instanceof ByteSizeValue) { - return value as any; + return { value }; } - - return this.createError('bytes.base', { value }, state, options); + return { + errors: [error('bytes.base')], + }; }, - rules: [ - anyCustomRule, - { - name: 'min', - params: { - limit: Joi.alternatives([Joi.number(), Joi.string()]).required(), - }, - validate(params, value, state, options) { - const limit = ensureByteSizeValue(params.limit); + rules: { + any: anyCustomRule, + min: { + args: [ + { + name: 'limit', + assert: Joi.alternatives([Joi.number(), Joi.string()]).required(), + }, + ], + validate(value, { error }, args, options) { + const limit = ensureByteSizeValue(args.limit); if (value.isLessThan(limit)) { - return this.createError('bytes.min', { value, limit }, state, options); + return error('bytes.min', { value, limit }); } return value; }, }, - { - name: 'max', - params: { - limit: Joi.alternatives([Joi.number(), Joi.string()]).required(), - }, - validate(params, value, state, options) { - const limit = ensureByteSizeValue(params.limit); + max: { + args: [ + { + name: 'limit', + assert: Joi.alternatives([Joi.number(), Joi.string()]).required(), + }, + ], + validate(value, { error }, args, options) { + const limit = ensureByteSizeValue(args.limit); if (value.isGreaterThan(limit)) { - return this.createError('bytes.max', { value, limit }, state, options); + return error('bytes.max', { value, limit }); } return value; }, }, - ], + }, }, { - name: 'duration', - - coerce(value: any, state: State, options: ValidationOptions) { + type: 'duration', + coerce(value, { error }) { try { if (typeof value === 'string' || typeof value === 'number') { - return ensureDuration(value); + return { value: ensureDuration(value) }; } } catch (e) { - return this.createError('duration.parse', { value, message: e.message }, state, options); + return { + errors: [error('duration.parse')], + }; } - - return value; + return { value }; }, - pre(value: any, state: State, options: ValidationOptions) { + prepare(value, { error }) { if (!isDuration(value)) { - return this.createError('duration.base', { value }, state, options); + return { + errors: [error('duration.base')], + }; } - - return value; + return { value }; + }, + rules: { + custom: anyCustomRule, }, - rules: [anyCustomRule], }, { - name: 'number', - + type: 'number', base: Joi.number(), - coerce(value: any, state: State, options: ValidationOptions) { + coerce(value, { error }) { // If value isn't defined, let Joi handle default value if it's defined. if (value === undefined) { - return value; + return { value }; } // Do we want to allow strings that can be converted, e.g. "2"? (Joi does) @@ -226,198 +246,206 @@ export const internals = Joi.extend([ // > strings that can be converted to numbers) const coercedValue: any = typeof value === 'string' ? Number(value) : value; if (typeof coercedValue !== 'number' || isNaN(coercedValue)) { - return this.createError('number.base', { value }, state, options); + return { + errors: [error('number.base')], + }; } - return value; + return { value }; + }, + rules: { + custom: anyCustomRule, }, - rules: [anyCustomRule], }, { - name: 'object', - + type: 'object', base: Joi.object(), - coerce(value: any, state: State, options: ValidationOptions) { + coerce(value: any, { error, prefs }) { if (value === undefined || isPlainObject(value)) { - return value; + return { value }; } - if (options.convert && typeof value === 'string') { + if (prefs.convert && typeof value === 'string') { try { const parsed = JSON.parse(value); if (isPlainObject(parsed)) { - return parsed; + return { value: parsed }; } - return this.createError('object.base', { value: parsed }, state, options); + return { errors: [error('object.base')] }; } catch (e) { - return this.createError('object.parse', { value }, state, options); + return { errors: [error('object.parse')] }; } } - return this.createError('object.base', { value }, state, options); + return { errors: [error('object.base')] }; + }, + rules: { + custom: anyCustomRule, }, - rules: [anyCustomRule], }, { - name: 'map', - - coerce(value: any, state: State, options: ValidationOptions) { + type: 'array', + base: Joi.array(), + coerce(value: any, { error, prefs }) { + if (value === undefined || Array.isArray(value)) { + return { value }; + } + if (prefs.convert && typeof value === 'string') { + try { + const parsed = JSON.parse(value); + if (Array.isArray(parsed)) { + return { value: parsed }; + } + return { + errors: [error('array.base')], + }; + } catch (e) { + return { + errors: [error('array.parse')], + }; + } + } + return { + errors: [error('array.base')], + }; + }, + rules: { + custom: anyCustomRule, + }, + }, + { + type: 'map', + coerce(value, { error, prefs }) { if (value === undefined) { - return value; + return { value }; } if (isPlainObject(value)) { - return new Map(Object.entries(value)); + return { value: new Map(Object.entries(value)) }; } - if (options.convert && typeof value === 'string') { + if (prefs.convert && typeof value === 'string') { try { const parsed = JSON.parse(value); if (isPlainObject(parsed)) { - return new Map(Object.entries(parsed)); + return { value: new Map(Object.entries(parsed)) }; } - return this.createError('map.base', { value: parsed }, state, options); + return { + errors: [error('map.base')], + }; } catch (e) { - return this.createError('map.parse', { value }, state, options); + return { + errors: [error('map.parse')], + }; } } - - return value; + return { value }; }, - pre(value: any, state: State, options: ValidationOptions) { + prepare(value, { error }) { if (!isMap(value)) { - return this.createError('map.base', { value }, state, options); + return { + errors: [error('map.base')], + }; } - return value as any; + return { value }; }, - rules: [ - anyCustomRule, - { - name: 'entries', - params: { - key: Joi.object().schema(), - value: Joi.object().schema(), - }, - validate(params, value, state, options) { + rules: { + custom: anyCustomRule, + entries: { + args: [ + { + name: 'key', + assert: Joi.object().schema(), + }, + { + name: 'value', + assert: Joi.object().schema(), + }, + ], + validate(value, { error }, args, options) { const result = new Map(); for (const [entryKey, entryValue] of value) { - const { value: validatedEntryKey, error: keyError } = Joi.validate( - entryKey, - params.key, - { presence: 'required' } - ); - - if (keyError) { - return this.createError('map.key', { entryKey, reason: keyError }, state, options); + let validatedEntryKey: any; + try { + validatedEntryKey = Joi.attempt(entryKey, args.key, { presence: 'required' }); + } catch (e) { + return error('map.key', { entryKey, reason: e.message }); } - const { value: validatedEntryValue, error: valueError } = Joi.validate( - entryValue, - params.value, - { presence: 'required' } - ); - - if (valueError) { - return this.createError( - 'map.value', - { entryKey, reason: valueError }, - state, - options - ); + let validatedEntryValue: any; + try { + validatedEntryValue = Joi.attempt(entryValue, args.value, { presence: 'required' }); + } catch (e) { + return error('map.value', { entryKey, reason: e.message }); } result.set(validatedEntryKey, validatedEntryValue); } - - return result as any; + return result; }, }, - ], + }, }, { - name: 'record', - pre(value: any, state: State, options: ValidationOptions) { + type: 'record', + prepare(value, { error, prefs }) { if (value === undefined || isPlainObject(value)) { - return value; + return { value }; } - if (options.convert && typeof value === 'string') { + if (prefs.convert && typeof value === 'string') { try { const parsed = JSON.parse(value); if (isPlainObject(parsed)) { - return parsed; + return { value: parsed }; } - return this.createError('record.base', { value: parsed }, state, options); + return { + errors: [error('record.base')], + }; } catch (e) { - return this.createError('record.parse', { value }, state, options); + return { + errors: [error('record.parse')], + }; } } - - return this.createError('record.base', { value }, state, options); + return { + errors: [error('record.base')], + }; }, - rules: [ - anyCustomRule, - { - name: 'entries', - params: { key: Joi.object().schema(), value: Joi.object().schema() }, - validate(params, value, state, options) { - const result = {} as Record; - for (const [entryKey, entryValue] of Object.entries(value)) { - const { value: validatedEntryKey, error: keyError } = Joi.validate( - entryKey, - params.key, - { presence: 'required' } - ); - - if (keyError) { - return this.createError('record.key', { entryKey, reason: keyError }, state, options); + rules: { + custom: anyCustomRule, + entries: { + args: [ + { + name: 'key', + assert: Joi.object().schema(), + }, + { + name: 'value', + assert: Joi.object().schema(), + }, + ], + validate(value, { error }, args, options) { + const result = new Map(); + for (const [entryKey, entryValue] of value) { + let validatedEntryKey: any; + try { + validatedEntryKey = Joi.attempt(entryKey, args.key, { presence: 'required' }); + } catch (e) { + return error('record.key', { entryKey, reason: e.message }); } - const { value: validatedEntryValue, error: valueError } = Joi.validate( - entryValue, - params.value, - { presence: 'required' } - ); - - if (valueError) { - return this.createError( - 'record.value', - { entryKey, reason: valueError }, - state, - options - ); + let validatedEntryValue: any; + try { + validatedEntryValue = Joi.attempt(entryValue, args.value, { presence: 'required' }); + } catch (e) { + return error('record.value', { entryKey, reason: e.message }); } - result[validatedEntryKey] = validatedEntryValue; + result.set(validatedEntryKey, validatedEntryValue); } - - return result as any; + return result; }, }, - ], - }, - { - name: 'array', - - base: Joi.array(), - coerce(value: any, state: State, options: ValidationOptions) { - if (value === undefined || Array.isArray(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], - }, -]) as JoiRoot; + } +); diff --git a/packages/kbn-config-schema/src/types/maybe_type.ts b/packages/kbn-config-schema/src/types/maybe_type.ts index ef901dd7a6373..a6712160a8e5f 100644 --- a/packages/kbn-config-schema/src/types/maybe_type.ts +++ b/packages/kbn-config-schema/src/types/maybe_type.ts @@ -14,7 +14,7 @@ export class MaybeType extends Type { type .getSchema() .optional() - .default(() => undefined, 'undefined') + .default(() => undefined) ); } } diff --git a/packages/kbn-config-schema/src/types/type.ts b/packages/kbn-config-schema/src/types/type.ts index aa98dbffb6de0..47c7ad5444e26 100644 --- a/packages/kbn-config-schema/src/types/type.ts +++ b/packages/kbn-config-schema/src/types/type.ts @@ -6,8 +6,9 @@ * Side Public License, v 1. */ +import type { ValidationErrorItem, AnySchema } from 'joi'; import { SchemaTypeError, ValidationError } from '../errors'; -import { AnySchema, internals, ValidationErrorItem } from '../internals'; +// import { internals } from '../internals'; import { Reference } from '../references'; export interface TypeOptions { @@ -36,12 +37,12 @@ export abstract class Type { // If default value is a function, then we must provide description for it. if (typeof options.defaultValue === 'function') { - schema = schema.default(options.defaultValue, 'Type default value'); + schema = schema.default(options.defaultValue); } else { schema = schema.default( Reference.isReference(options.defaultValue) ? options.defaultValue.getSchema() - : options.defaultValue + : (options.defaultValue as any) ); } } @@ -61,7 +62,8 @@ export abstract class Type { } public validate(value: any, context: Record = {}, namespace?: string): V { - const { value: validatedValue, error } = internals.validate(value, this.internalSchema, { + // TODO: make sure that we don't need to use `internal.attempt` here. + const { value: validatedValue, error } = this.internalSchema.validate(value, { context, presence: 'required', }); @@ -94,8 +96,9 @@ export abstract class Type { } const { context = {}, type, path, message } = error; + const convertedPath = path.map((entry) => entry.toString()); - const errorHandleResult = this.handleError(type, context, path); + const errorHandleResult = this.handleError(type, context, convertedPath); if (errorHandleResult instanceof SchemaTypeError) { return errorHandleResult; } @@ -103,15 +106,15 @@ export abstract class Type { // If error handler just defines error message, then wrap it into proper // `SchemaTypeError` instance. if (typeof errorHandleResult === 'string') { - return new SchemaTypeError(errorHandleResult, path); + return new SchemaTypeError(errorHandleResult, convertedPath); } // If error is produced by the custom validator, just extract source message // from context and wrap it into `SchemaTypeError` instance. if (type === 'any.custom') { - return new SchemaTypeError(context.message, path); + return new SchemaTypeError(context.message, convertedPath); } - return new SchemaTypeError(message || type, path); + return new SchemaTypeError(message || type, convertedPath); } } diff --git a/yarn.lock b/yarn.lock index ccf722c7728f4..404d5fcf1d1b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5181,10 +5181,12 @@ resolved "https://registry.yarnpkg.com/@types/joi/-/joi-14.3.4.tgz#eed1e14cbb07716079c814138831a520a725a1e0" integrity sha512-1TQNDJvIKlgYXGNIABfgFp9y0FziDpuGrd799Q5RcnsDu+krD+eeW/0Fs5PHARvWWFelOhIG2OPCo6KbadBM4A== -"@types/joi@^13.4.2": - version "13.6.1" - resolved "https://registry.yarnpkg.com/@types/joi/-/joi-13.6.1.tgz#325486a397504f8e22c8c551dc8b0e1d41d5d5ae" - integrity sha512-JxZ0NP8NuB0BJOXi1KvAA6rySLTPmhOy4n2gzSFq/IFM3LNFm0h+2Vn/bPPgEYlWqzS2NPeLgKqfm75baX+Hog== +"@types/joi@^17.2.3": + version "17.2.3" + resolved "https://registry.yarnpkg.com/@types/joi/-/joi-17.2.3.tgz#b7768ed9d84f1ebd393328b9f97c1cf3d2b94798" + integrity sha512-dGjs/lhrWOa+eO0HwgxCSnDm5eMGCsXuvLglMghJq32F6q5LyyNuXb41DHzrg501CKNOSSAHmfB7FDGeUnDmzw== + dependencies: + joi "*" "@types/jquery@*", "@types/jquery@^3.3.31": version "3.3.31" @@ -16702,13 +16704,6 @@ isbinaryfile@4.0.2: resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.2.tgz#bfc45642da645681c610cca831022e30af426488" integrity sha512-C3FSxJdNrEr2F4z6uFtNzECDM5hXk+46fxaa+cwBe5/XrWSmzdG8DDgyjfX6/NRdBB21q2JXuRAzPCUs+fclnQ== -isemail@3.x.x: - version "3.1.4" - resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.1.4.tgz#76e2187ff7bee59d57522c6fd1c3f09a331933cf" - integrity sha512-yE/W5osEWuAGSLVixV9pAexhkbZzglmuhO2CxdHu7IBh7uzuZogQ4bk0lE26HoZ6HD4ZYfKRKilkNuCnuJIBJw== - dependencies: - punycode "2.x.x" - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -17434,14 +17429,16 @@ jju@~1.4.0: resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" integrity sha1-o6vicYryQaKykE+EpiWXDzia4yo= -joi@^13.5.2: - version "13.7.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-13.7.0.tgz#cfd85ebfe67e8a1900432400b4d03bbd93fb879f" - integrity sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q== +joi@*, joi@^17.4.0: + version "17.4.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.4.0.tgz#b5c2277c8519e016316e49ababd41a1908d9ef20" + integrity sha512-F4WiW2xaV6wc1jxete70Rw4V/VuMd6IN+a5ilZsxG4uYtUXWu2kq9W5P2dz30e7Gmw8RCbY/u/uk+dMPma9tAg== dependencies: - hoek "5.x.x" - isemail "3.x.x" - topo "3.x.x" + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@sideway/address" "^4.1.0" + "@sideway/formula" "^3.0.0" + "@sideway/pinpoint" "^2.0.0" joi@^17.3.0: version "17.3.0" @@ -22412,16 +22409,16 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@2.x.x, punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - punycode@^1.2.4, punycode@^1.3.2: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + pupa@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" @@ -27044,13 +27041,6 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -topo@3.x.x: - version "3.0.0" - resolved "https://registry.yarnpkg.com/topo/-/topo-3.0.0.tgz#37e48c330efeac784538e0acd3e62ca5e231fe7a" - integrity sha512-Tlu1fGlR90iCdIPURqPiufqAlCZYzLjHYVVbcFWDMcX7+tK8hdZWAfsMrD/pBul9jqHHwFjNdf1WaxA9vTRRhw== - dependencies: - hoek "5.x.x" - topojson-client@3.1.0, topojson-client@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/topojson-client/-/topojson-client-3.1.0.tgz#22e8b1ed08a2b922feeb4af6f53b6ef09a467b99" From a148a25775533dc9635e8b4152f23fea6f8382b4 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 11 May 2021 21:58:00 +0200 Subject: [PATCH 02/30] remove custom validation rule, adapt instead --- .../kbn-config-schema/src/internals/index.ts | 79 +------------------ .../src/references/reference.ts | 3 +- .../src/types/object_type.ts | 3 +- packages/kbn-config-schema/src/types/type.ts | 23 +++++- .../lib/config/schema.ts | 2 +- 5 files changed, 27 insertions(+), 83 deletions(-) diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index d7289fbb4b217..1b313b2eaebfe 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -7,62 +7,18 @@ */ import Joi from 'joi'; -import type { - AnySchema, - JoiRoot, - Reference, - ExtensionRule, - SchemaLike, - CustomHelpers, - ValidationErrorItem, -} from 'joi'; +import type { JoiRoot, CustomHelpers } from 'joi'; import { isPlainObject } from 'lodash'; import { isDuration } from 'moment'; import { Stream } from 'stream'; import { ByteSizeValue, ensureByteSizeValue } from '../byte_size_value'; import { ensureDuration } from '../duration'; -export { AnySchema, Reference, SchemaLike, ValidationErrorItem }; - function isMap(o: any): o is Map { return o instanceof Map; } -const anyCustomRule: ExtensionRule = { - multi: true, - args: [ - { - name: 'validator', - assert: Joi.func().maxArity(1).required(), - }, - ], - method(validator) { - // @ts-expect-error $_ helpers not present on on the typedef - return this.$_addRule({ name: 'custom', args: { validator } }); - }, - validate(value, { error }, args, options) { - let validationResultMessage; - try { - validationResultMessage = args.validator(value); - } catch (e) { - validationResultMessage = e.message || e; - } - - if (typeof validationResultMessage === 'string') { - return error('any.custom', { validator: args.validator }); - } - - return value; - }, -}; - export const internals: JoiRoot = Joi.extend( - { - type: 'any', - rules: { - custom: anyCustomRule, - }, - }, { type: 'boolean', base: Joi.boolean(), @@ -95,9 +51,6 @@ export const internals: JoiRoot = Joi.extend( value, }; }, - rules: { - custom: anyCustomRule, - }, }, { type: 'binary', @@ -112,13 +65,9 @@ export const internals: JoiRoot = Joi.extend( return { value }; }, - rules: { - custom: anyCustomRule, - }, }, { type: 'stream', - prepare(value, { error }) { // If value isn't defined, let Joi handle default value if it's defined. if (value instanceof Stream) { @@ -128,20 +77,9 @@ export const internals: JoiRoot = Joi.extend( errors: [error('stream.base')], }; }, - rules: { - custom: anyCustomRule, - }, - }, - { - type: 'string', - base: Joi.string(), - rules: { - custom: anyCustomRule, - }, }, { type: 'bytes', - coerce(value: any, { error }) { try { if (typeof value === 'string') { @@ -168,7 +106,6 @@ export const internals: JoiRoot = Joi.extend( }; }, rules: { - any: anyCustomRule, min: { args: [ { @@ -225,9 +162,6 @@ export const internals: JoiRoot = Joi.extend( } return { value }; }, - rules: { - custom: anyCustomRule, - }, }, { type: 'number', @@ -253,9 +187,6 @@ export const internals: JoiRoot = Joi.extend( return { value }; }, - rules: { - custom: anyCustomRule, - }, }, { type: 'object', @@ -279,9 +210,6 @@ export const internals: JoiRoot = Joi.extend( return { errors: [error('object.base')] }; }, - rules: { - custom: anyCustomRule, - }, }, { type: 'array', @@ -309,9 +237,6 @@ export const internals: JoiRoot = Joi.extend( errors: [error('array.base')], }; }, - rules: { - custom: anyCustomRule, - }, }, { type: 'map', @@ -349,7 +274,6 @@ export const internals: JoiRoot = Joi.extend( return { value }; }, rules: { - custom: anyCustomRule, entries: { args: [ { @@ -412,7 +336,6 @@ export const internals: JoiRoot = Joi.extend( }; }, rules: { - custom: anyCustomRule, entries: { args: [ { diff --git a/packages/kbn-config-schema/src/references/reference.ts b/packages/kbn-config-schema/src/references/reference.ts index e5731325ef7ea..888d6c17704a2 100644 --- a/packages/kbn-config-schema/src/references/reference.ts +++ b/packages/kbn-config-schema/src/references/reference.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { internals, Reference as InternalReference } from '../internals'; +import type { Reference as InternalReference } from 'joi'; +import { internals } from '../internals'; export class Reference { public static isReference(value: V | Reference | undefined): value is Reference { diff --git a/packages/kbn-config-schema/src/types/object_type.ts b/packages/kbn-config-schema/src/types/object_type.ts index 6cce8df60e8c0..dd9e3f4e06a91 100644 --- a/packages/kbn-config-schema/src/types/object_type.ts +++ b/packages/kbn-config-schema/src/types/object_type.ts @@ -6,8 +6,9 @@ * Side Public License, v 1. */ +import type { AnySchema } from 'joi'; import typeDetect from 'type-detect'; -import { AnySchema, internals } from '../internals'; +import { internals } from '../internals'; import { Type, TypeOptions } from './type'; import { ValidationError } from '../errors'; diff --git a/packages/kbn-config-schema/src/types/type.ts b/packages/kbn-config-schema/src/types/type.ts index 47c7ad5444e26..8e700ed16a0e8 100644 --- a/packages/kbn-config-schema/src/types/type.ts +++ b/packages/kbn-config-schema/src/types/type.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ValidationErrorItem, AnySchema } from 'joi'; +import type { ValidationErrorItem, AnySchema, CustomValidator } from 'joi'; import { SchemaTypeError, ValidationError } from '../errors'; // import { internals } from '../internals'; import { Reference } from '../references'; @@ -16,6 +16,25 @@ export interface TypeOptions { validate?: (value: T) => string | void; } +const convertValidationFunction = ( + validate: (value: T) => string | void +): CustomValidator => { + return (value, { error }) => { + let validationResultMessage; + try { + validationResultMessage = validate(value); + } catch (e) { + validationResultMessage = e.message || e; + } + + if (typeof validationResultMessage === 'string') { + return error('any.custom'); + } + + return value; + }; +}; + export abstract class Type { // This is just to enable the `TypeOf` helper, and because TypeScript would // fail if it wasn't initialized we use a "trick" to which basically just @@ -48,7 +67,7 @@ export abstract class Type { } if (options.validate) { - schema = schema.custom(options.validate); + schema = schema.custom(convertValidationFunction(options.validate)); } // Attach generic error handler only if it hasn't been attached yet since diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index d82b7b83e8f15..2f5b20fdc7b27 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -54,7 +54,7 @@ const dockerServerSchema = () => image: requiredWhenEnabled(Joi.string()), port: requiredWhenEnabled(Joi.number()), portInContainer: requiredWhenEnabled(Joi.number()), - waitForLogLine: Joi.alternatives(Joi.object().type(RegExp), Joi.string()).optional(), + waitForLogLine: Joi.alternatives(Joi.object().instance(RegExp), Joi.string()).optional(), waitFor: Joi.func().optional(), args: Joi.array().items(Joi.string()).optional(), }) From f7591b2b5d5138d86ac04b171570f262fb010143 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 11 May 2021 23:10:53 +0200 Subject: [PATCH 03/30] fix error handling --- packages/kbn-config-schema/src/types/type.ts | 22 +++++++++++++------- packages/kbn-config-schema/types/joi.d.ts | 7 +++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/packages/kbn-config-schema/src/types/type.ts b/packages/kbn-config-schema/src/types/type.ts index 8e700ed16a0e8..334cafefcead9 100644 --- a/packages/kbn-config-schema/src/types/type.ts +++ b/packages/kbn-config-schema/src/types/type.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ValidationErrorItem, AnySchema, CustomValidator } from 'joi'; +import type { AnySchema, CustomValidator, ErrorReport } from 'joi'; import { SchemaTypeError, ValidationError } from '../errors'; // import { internals } from '../internals'; import { Reference } from '../references'; @@ -72,8 +72,7 @@ export abstract class Type { // Attach generic error handler only if it hasn't been attached yet since // only the last error handler is counted. - const schemaFlags = (schema.describe().flags as Record) || {}; - if (schemaFlags.error === undefined) { + if (schema.$_getFlag('error') === undefined) { schema = schema.error(([error]) => this.onError(error)); } @@ -109,15 +108,22 @@ export abstract class Type { return undefined; } - private onError(error: SchemaTypeError | ValidationErrorItem): SchemaTypeError { + private onError(error: SchemaTypeError | ErrorReport): SchemaTypeError { if (error instanceof SchemaTypeError) { return error; } - const { context = {}, type, path, message } = error; + // `message` is only initialized once `toString` has been called (...) + // see https://github.com/sideway/joi/blob/master/lib/errors.js + const { local, code, path, message, value } = error; const convertedPath = path.map((entry) => entry.toString()); - const errorHandleResult = this.handleError(type, context, convertedPath); + const context: Record = { + ...local, + value, + }; + + const errorHandleResult = this.handleError(code, context, convertedPath); if (errorHandleResult instanceof SchemaTypeError) { return errorHandleResult; } @@ -130,10 +136,10 @@ export abstract class Type { // If error is produced by the custom validator, just extract source message // from context and wrap it into `SchemaTypeError` instance. - if (type === 'any.custom') { + if (code === 'any.custom') { return new SchemaTypeError(context.message, convertedPath); } - return new SchemaTypeError(message || type, convertedPath); + return new SchemaTypeError(message || code, convertedPath); } } diff --git a/packages/kbn-config-schema/types/joi.d.ts b/packages/kbn-config-schema/types/joi.d.ts index 88bdffe8f77b9..341d8a7babe29 100644 --- a/packages/kbn-config-schema/types/joi.d.ts +++ b/packages/kbn-config-schema/types/joi.d.ts @@ -12,6 +12,7 @@ import { ByteSizeValue } from '../src/byte_size_value'; declare module 'joi' { interface BytesSchema extends AnySchema { min(limit: number | string | ByteSizeValue): this; + max(limit: number | string | ByteSizeValue): this; } @@ -23,6 +24,12 @@ declare module 'joi' { entries(key: AnySchema, value: AnySchema): this; } + interface ErrorReport { + // missing from the typedef + // see https://github.com/sideway/joi/blob/master/lib/errors.js + local?: Record; + } + export type JoiRoot = Joi.Root & { bytes: () => BytesSchema; duration: () => AnySchema; From 6c86026f5b9727ce8bee00c6c85311d9051ab737 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 11 May 2021 23:26:37 +0200 Subject: [PATCH 04/30] fix error handling again --- packages/kbn-config-schema/src/types/literal_type.ts | 2 +- packages/kbn-config-schema/src/types/type.ts | 3 ++- packages/kbn-config-schema/types/joi.d.ts | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/kbn-config-schema/src/types/literal_type.ts b/packages/kbn-config-schema/src/types/literal_type.ts index 0737070acaf8a..d0e1d954c7257 100644 --- a/packages/kbn-config-schema/src/types/literal_type.ts +++ b/packages/kbn-config-schema/src/types/literal_type.ts @@ -17,7 +17,7 @@ export class LiteralType extends Type { protected handleError(type: string, { value, valids: [expectedValue] }: Record) { switch (type) { case 'any.required': - case 'any.allowOnly': + case 'any.only': return `expected value to equal [${expectedValue}]`; } } diff --git a/packages/kbn-config-schema/src/types/type.ts b/packages/kbn-config-schema/src/types/type.ts index 334cafefcead9..b94d7146b6127 100644 --- a/packages/kbn-config-schema/src/types/type.ts +++ b/packages/kbn-config-schema/src/types/type.ts @@ -113,9 +113,10 @@ export abstract class Type { return error; } + const { local, code, path, value } = error; // `message` is only initialized once `toString` has been called (...) // see https://github.com/sideway/joi/blob/master/lib/errors.js - const { local, code, path, message, value } = error; + const message = error.toString(); const convertedPath = path.map((entry) => entry.toString()); const context: Record = { diff --git a/packages/kbn-config-schema/types/joi.d.ts b/packages/kbn-config-schema/types/joi.d.ts index 341d8a7babe29..402250ca9dca6 100644 --- a/packages/kbn-config-schema/types/joi.d.ts +++ b/packages/kbn-config-schema/types/joi.d.ts @@ -28,6 +28,7 @@ declare module 'joi' { // missing from the typedef // see https://github.com/sideway/joi/blob/master/lib/errors.js local?: Record; + toString(): string; } export type JoiRoot = Joi.Root & { From e23bcb0802613d9685e1298ea1a9f5697be306e6 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 01:14:41 +0200 Subject: [PATCH 05/30] fix strings type & validation --- .../src/types/string_type.test.ts | 42 ++++++++++--------- .../src/types/string_type.ts | 38 ++++++++++------- packages/kbn-config-schema/src/types/type.ts | 7 ++-- packages/kbn-config-schema/types/joi.d.ts | 4 -- 4 files changed, 47 insertions(+), 44 deletions(-) diff --git a/packages/kbn-config-schema/src/types/string_type.test.ts b/packages/kbn-config-schema/src/types/string_type.test.ts index f08634ba28b07..55b2f050c5894 100644 --- a/packages/kbn-config-schema/src/types/string_type.test.ts +++ b/packages/kbn-config-schema/src/types/string_type.test.ts @@ -30,6 +30,20 @@ test('includes namespace in failure', () => { ); }); +test('returns error when not string', () => { + expect(() => schema.string().validate(123)).toThrowErrorMatchingInlineSnapshot( + `"expected value of type [string] but got [number]"` + ); + + expect(() => schema.string().validate([1, 2, 3])).toThrowErrorMatchingInlineSnapshot( + `"expected value of type [string] but got [Array]"` + ); + + expect(() => schema.string().validate(/abc/)).toThrowErrorMatchingInlineSnapshot( + `"expected value of type [string] but got [RegExp]"` + ); +}); + describe('#minLength', () => { test('returns value when longer string', () => { expect(schema.string({ minLength: 2 }).validate('foo')).toBe('foo'); @@ -71,17 +85,19 @@ describe('#hostname', () => { expect(hostNameSchema.validate('www.example.com')).toBe('www.example.com'); expect(hostNameSchema.validate('3domain.local')).toBe('3domain.local'); expect(hostNameSchema.validate('hostname')).toBe('hostname'); - expect(hostNameSchema.validate('2387628')).toBe('2387628'); + // DOMAIN_INVALID_TLDS_CHARS (last segment must start with alphanum) - https://github.com/sideway/address/blob/master/lib/domain.js#L88 + // expect(hostNameSchema.validate('2387628')).toBe('2387628'); expect(hostNameSchema.validate('::1')).toBe('::1'); expect(hostNameSchema.validate('0:0:0:0:0:0:0:1')).toBe('0:0:0:0:0:0:0:1'); expect(hostNameSchema.validate('xn----ascii-7gg5ei7b1i.xn--90a3a')).toBe( 'xn----ascii-7gg5ei7b1i.xn--90a3a' ); - const hostNameWithMaxAllowedLength = 'a'.repeat(255); - expect(hostNameSchema.validate(hostNameWithMaxAllowedLength)).toBe( - hostNameWithMaxAllowedLength - ); + // DOMAIN_LONG_SEGMENT (63 chars max per segment) - https://github.com/sideway/address/blob/master/lib/domain.js#L79 + //const hostNameWithMaxAllowedLength = 'a'.repeat(100); + //expect(hostNameSchema.validate(hostNameWithMaxAllowedLength)).toBe( + // hostNameWithMaxAllowedLength + //); }); test('returns error when value is not a valid hostname', () => { @@ -108,7 +124,7 @@ describe('#hostname', () => { test('returns error when empty string', () => { expect(() => schema.string({ hostname: true }).validate('')).toThrowErrorMatchingInlineSnapshot( - `"any.empty"` + `"\\"value\\" is not allowed to be empty"` ); }); @@ -176,17 +192,3 @@ describe('#validate', () => { ); }); }); - -test('returns error when not string', () => { - expect(() => schema.string().validate(123)).toThrowErrorMatchingInlineSnapshot( - `"expected value of type [string] but got [number]"` - ); - - expect(() => schema.string().validate([1, 2, 3])).toThrowErrorMatchingInlineSnapshot( - `"expected value of type [string] but got [Array]"` - ); - - expect(() => schema.string().validate(/abc/)).toThrowErrorMatchingInlineSnapshot( - `"expected value of type [string] but got [RegExp]"` - ); -}); diff --git a/packages/kbn-config-schema/src/types/string_type.ts b/packages/kbn-config-schema/src/types/string_type.ts index 2772af6b9bab9..1442c5b9b4efb 100644 --- a/packages/kbn-config-schema/src/types/string_type.ts +++ b/packages/kbn-config-schema/src/types/string_type.ts @@ -8,7 +8,7 @@ import typeDetect from 'type-detect'; import { internals } from '../internals'; -import { Type, TypeOptions } from './type'; +import { Type, TypeOptions, convertValidationFunction } from './type'; export type StringOptions = TypeOptions & { minLength?: number; @@ -25,26 +25,32 @@ export class StringType extends Type { let schema = options.hostname === true ? internals.string().hostname() - : internals.any().custom((value) => { - if (typeof value !== 'string') { - return `expected value of type [string] but got [${typeDetect(value)}]`; - } - }); + : internals.any().custom( + convertValidationFunction((value) => { + if (typeof value !== 'string') { + return `expected value of type [string] but got [${typeDetect(value)}]`; + } + }) + ); if (options.minLength !== undefined) { - schema = schema.custom((value) => { - if (value.length < options.minLength!) { - return `value has length [${value.length}] but it must have a minimum length of [${options.minLength}].`; - } - }); + schema = schema.custom( + convertValidationFunction((value) => { + if (value.length < options.minLength!) { + return `value has length [${value.length}] but it must have a minimum length of [${options.minLength}].`; + } + }) + ); } if (options.maxLength !== undefined) { - schema = schema.custom((value) => { - if (value.length > options.maxLength!) { - return `value has length [${value.length}] but it must have a maximum length of [${options.maxLength}].`; - } - }); + schema = schema.custom( + convertValidationFunction((value) => { + if (value.length > options.maxLength!) { + return `value has length [${value.length}] but it must have a maximum length of [${options.maxLength}].`; + } + }) + ); } super(schema, options); diff --git a/packages/kbn-config-schema/src/types/type.ts b/packages/kbn-config-schema/src/types/type.ts index b94d7146b6127..53d75548bff10 100644 --- a/packages/kbn-config-schema/src/types/type.ts +++ b/packages/kbn-config-schema/src/types/type.ts @@ -16,7 +16,7 @@ export interface TypeOptions { validate?: (value: T) => string | void; } -const convertValidationFunction = ( +export const convertValidationFunction = ( validate: (value: T) => string | void ): CustomValidator => { return (value, { error }) => { @@ -28,7 +28,7 @@ const convertValidationFunction = ( } if (typeof validationResultMessage === 'string') { - return error('any.custom'); + return error('any.custom', { message: validationResultMessage }); } return value; @@ -80,7 +80,6 @@ export abstract class Type { } public validate(value: any, context: Record = {}, namespace?: string): V { - // TODO: make sure that we don't need to use `internal.attempt` here. const { value: validatedValue, error } = this.internalSchema.validate(value, { context, presence: 'required', @@ -137,7 +136,7 @@ export abstract class Type { // If error is produced by the custom validator, just extract source message // from context and wrap it into `SchemaTypeError` instance. - if (code === 'any.custom') { + if (code === 'any.custom' && context.message) { return new SchemaTypeError(context.message, convertedPath); } diff --git a/packages/kbn-config-schema/types/joi.d.ts b/packages/kbn-config-schema/types/joi.d.ts index 402250ca9dca6..711c8f0f5332f 100644 --- a/packages/kbn-config-schema/types/joi.d.ts +++ b/packages/kbn-config-schema/types/joi.d.ts @@ -39,10 +39,6 @@ declare module 'joi' { stream: () => AnySchema; }; - interface AnySchema { - custom(validator: (value: any) => string | void): this; - } - // Joi types don't include `schema` function even though it's supported. interface ObjectSchema { schema(): this; From a8789e4ecd9e989da516ff923bc303dc8ec5a2b0 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 09:15:38 +0200 Subject: [PATCH 06/30] fix buffers and arrays --- .../kbn-config-schema/src/internals/index.ts | 23 ++----------------- .../src/types/buffer_type.test.ts | 8 +++---- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index 1b313b2eaebfe..bb2a535b2dae1 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -52,20 +52,6 @@ export const internals: JoiRoot = Joi.extend( }; }, }, - { - type: 'binary', - base: Joi.binary(), - coerce(value, { error }) { - // If value isn't defined, let Joi handle default value if it's defined. - if (value !== undefined && !(typeof value === 'object' && Buffer.isBuffer(value))) { - return { - errors: [error('binary.base')], - }; - } - - return { value }; - }, - }, { type: 'stream', prepare(value, { error }) { @@ -220,13 +206,8 @@ export const internals: JoiRoot = Joi.extend( } if (prefs.convert && typeof value === 'string') { try { - const parsed = JSON.parse(value); - if (Array.isArray(parsed)) { - return { value: parsed }; - } - return { - errors: [error('array.base')], - }; + // ensuring that the parsed object is an array is done by the base's validation + return { value: JSON.parse(value) }; } catch (e) { return { errors: [error('array.parse')], diff --git a/packages/kbn-config-schema/src/types/buffer_type.test.ts b/packages/kbn-config-schema/src/types/buffer_type.test.ts index 52805aa0452d1..6300c5009d08c 100644 --- a/packages/kbn-config-schema/src/types/buffer_type.test.ts +++ b/packages/kbn-config-schema/src/types/buffer_type.test.ts @@ -27,6 +27,10 @@ test('includes namespace in failure', () => { ); }); +test('coerces strings to buffer', () => { + expect(schema.buffer().validate('abc')).toStrictEqual(Buffer.from('abc')); +}); + describe('#defaultValue', () => { test('returns default when undefined', () => { const value = Buffer.from('Hi!'); @@ -49,8 +53,4 @@ test('returns error when not a buffer', () => { expect(() => schema.buffer().validate([1, 2, 3])).toThrowErrorMatchingInlineSnapshot( `"expected value of type [Buffer] but got [Array]"` ); - - expect(() => schema.buffer().validate('abc')).toThrowErrorMatchingInlineSnapshot( - `"expected value of type [Buffer] but got [string]"` - ); }); From 1c454ebd855f3d94330ad5602e3969f43fb285b9 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 09:20:00 +0200 Subject: [PATCH 07/30] fix bytes --- packages/kbn-config-schema/src/internals/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index bb2a535b2dae1..ffbf3569088ed 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -82,7 +82,7 @@ export const internals: JoiRoot = Joi.extend( } return { value }; }, - prepare(value, { error }) { + validate(value, { error }) { // If value isn't defined, let Joi handle default value if it's defined. if (value instanceof ByteSizeValue) { return { value }; From f4578faf6a4914e35e2184bafe830b00efecd271 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 09:37:23 +0200 Subject: [PATCH 08/30] fix bytes_size type --- packages/kbn-config-schema/src/internals/index.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index ffbf3569088ed..0aa756b559caf 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -77,7 +77,7 @@ export const internals: JoiRoot = Joi.extend( } } catch (e) { return { - errors: [error('bytes.parse')], + errors: [error('bytes.parse', { message: e.message })], }; } return { value }; @@ -99,7 +99,10 @@ export const internals: JoiRoot = Joi.extend( assert: Joi.alternatives([Joi.number(), Joi.string()]).required(), }, ], - validate(value, { error }, args, options) { + method(limit) { + return this.$_addRule({ name: 'min', args: { limit } }); + }, + validate(value, { error }, args) { const limit = ensureByteSizeValue(args.limit); if (value.isLessThan(limit)) { return error('bytes.min', { value, limit }); @@ -115,7 +118,10 @@ export const internals: JoiRoot = Joi.extend( assert: Joi.alternatives([Joi.number(), Joi.string()]).required(), }, ], - validate(value, { error }, args, options) { + method(limit) { + return this.$_addRule({ name: 'max', args: { limit } }); + }, + validate(value, { error }, args) { const limit = ensureByteSizeValue(args.limit); if (value.isGreaterThan(limit)) { return error('bytes.max', { value, limit }); From 5eab4505911ec0d1cdd4b39e84402eed02d092e6 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 09:46:31 +0200 Subject: [PATCH 09/30] update conditional_type error messages in tests --- .../src/types/conditional_type.test.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/kbn-config-schema/src/types/conditional_type.test.ts b/packages/kbn-config-schema/src/types/conditional_type.test.ts index b9fe5c94b6d39..0145ee742a660 100644 --- a/packages/kbn-config-schema/src/types/conditional_type.test.ts +++ b/packages/kbn-config-schema/src/types/conditional_type.test.ts @@ -347,16 +347,12 @@ test('works within `oneOf`', () => { expect(type.validate(true, { type: 'boolean' })).toEqual(true); expect(type.validate(['a', 'b'], { type: 'array' })).toEqual(['a', 'b']); - expect(() => type.validate(1, { type: 'string' })).toThrowErrorMatchingInlineSnapshot(` -"types that failed validation: -- [0]: expected value of type [string] but got [number] -- [1]: expected value of type [array] but got [number]" -`); - expect(() => type.validate(true, { type: 'string' })).toThrowErrorMatchingInlineSnapshot(` -"types that failed validation: -- [0]: expected value of type [string] but got [boolean] -- [1]: expected value of type [array] but got [boolean]" -`); + expect(() => type.validate(1, { type: 'string' })).toThrowErrorMatchingInlineSnapshot( + `"\\"value\\" does not match any of the allowed types"` + ); + expect(() => type.validate(true, { type: 'string' })).toThrowErrorMatchingInlineSnapshot( + `"\\"value\\" does not match any of the allowed types"` + ); }); describe('#validate', () => { From 831649f2ee6d984f44cb40e3f3a938f931cbca6f Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 10:27:02 +0200 Subject: [PATCH 10/30] fix duration and map types --- packages/kbn-config-schema/src/internals/index.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index 0aa756b559caf..e1ee29a40e02f 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -141,12 +141,12 @@ export const internals: JoiRoot = Joi.extend( } } catch (e) { return { - errors: [error('duration.parse')], + errors: [error('duration.parse', { message: e.message })], }; } return { value }; }, - prepare(value, { error }) { + validate(value, { error }) { if (!isDuration(value)) { return { errors: [error('duration.base')], @@ -241,7 +241,7 @@ export const internals: JoiRoot = Joi.extend( return { value: new Map(Object.entries(parsed)) }; } return { - errors: [error('map.base')], + value: parsed, }; } catch (e) { return { @@ -251,7 +251,7 @@ export const internals: JoiRoot = Joi.extend( } return { value }; }, - prepare(value, { error }) { + validate(value, { error }) { if (!isMap(value)) { return { errors: [error('map.base')], @@ -272,6 +272,9 @@ export const internals: JoiRoot = Joi.extend( assert: Joi.object().schema(), }, ], + method(key, value) { + return this.$_addRule({ name: 'entries', args: { key, value } }); + }, validate(value, { error }, args, options) { const result = new Map(); for (const [entryKey, entryValue] of value) { @@ -279,14 +282,14 @@ export const internals: JoiRoot = Joi.extend( try { validatedEntryKey = Joi.attempt(entryKey, args.key, { presence: 'required' }); } catch (e) { - return error('map.key', { entryKey, reason: e.message }); + return error('map.key', { entryKey, reason: e }); } let validatedEntryValue: any; try { validatedEntryValue = Joi.attempt(entryValue, args.value, { presence: 'required' }); } catch (e) { - return error('map.value', { entryKey, reason: e.message }); + return error('map.value', { entryKey, reason: e }); } result.set(validatedEntryKey, validatedEntryValue); From e76029d1fcb020c2d630693250568d2f2d770013 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 11:15:50 +0200 Subject: [PATCH 11/30] first attempt to fix union type error messages --- .../kbn-config-schema/src/types/union_type.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/kbn-config-schema/src/types/union_type.ts b/packages/kbn-config-schema/src/types/union_type.ts index c67362daea717..ef233d9d4a64b 100644 --- a/packages/kbn-config-schema/src/types/union_type.ts +++ b/packages/kbn-config-schema/src/types/union_type.ts @@ -13,20 +13,21 @@ import { Type, TypeOptions } from './type'; export class UnionType>, T> extends Type { constructor(types: RTS, options?: TypeOptions) { - const schema = internals.alternatives(types.map((type) => type.getSchema())); + const schema = internals.alternatives(types.map((type) => type.getSchema())).match('any'); super(schema, options); } - protected handleError(type: string, { reason, value }: Record, path: string[]) { + protected handleError(type: string, { value, details }: Record, path: string[]) { switch (type) { case 'any.required': return `expected at least one defined value but got [${typeDetect(value)}]`; - case 'alternatives.child': + case 'alternatives.match': return new SchemaTypesError( 'types that failed validation:', path, - reason.map((e: SchemaTypeError, index: number) => { + details.map((detail: AlternativeErrorDetail, index: number) => { + const e = detail.context.error; const childPathWithIndex = e.path.slice(); childPathWithIndex.splice(path.length, 0, index.toString()); @@ -38,3 +39,9 @@ export class UnionType>, T> extends Type { } } } + +interface AlternativeErrorDetail { + context: { + error: SchemaTypeError; + }; +} From a6fd0e6e40370918023bbacb69e8a9fa60d6257c Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 11:21:17 +0200 Subject: [PATCH 12/30] revert conditional type assertions back to master state --- .../src/types/conditional_type.test.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/kbn-config-schema/src/types/conditional_type.test.ts b/packages/kbn-config-schema/src/types/conditional_type.test.ts index 0145ee742a660..0c211cc226619 100644 --- a/packages/kbn-config-schema/src/types/conditional_type.test.ts +++ b/packages/kbn-config-schema/src/types/conditional_type.test.ts @@ -347,12 +347,16 @@ test('works within `oneOf`', () => { expect(type.validate(true, { type: 'boolean' })).toEqual(true); expect(type.validate(['a', 'b'], { type: 'array' })).toEqual(['a', 'b']); - expect(() => type.validate(1, { type: 'string' })).toThrowErrorMatchingInlineSnapshot( - `"\\"value\\" does not match any of the allowed types"` - ); - expect(() => type.validate(true, { type: 'string' })).toThrowErrorMatchingInlineSnapshot( - `"\\"value\\" does not match any of the allowed types"` - ); + expect(() => type.validate(1, { type: 'string' })).toThrowErrorMatchingInlineSnapshot(` + "types that failed validation: + - [0]: expected value of type [string] but got [number] + - [1]: expected value of type [array] but got [number]" + `); + expect(() => type.validate(true, { type: 'string' })).toThrowErrorMatchingInlineSnapshot(` + "types that failed validation: + - [0]: expected value of type [string] but got [boolean] + - [1]: expected value of type [array] but got [boolean]" + `); }); describe('#validate', () => { From 83d41ab16f7f43348265189ce6b723fc3ef75e8c Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 11:34:08 +0200 Subject: [PATCH 13/30] fix object type --- packages/kbn-config-schema/src/internals/index.ts | 5 +---- packages/kbn-config-schema/src/types/object_type.test.ts | 9 +++++---- packages/kbn-config-schema/src/types/object_type.ts | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index e1ee29a40e02f..c57ba22e87050 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -191,10 +191,7 @@ export const internals: JoiRoot = Joi.extend( if (prefs.convert && typeof value === 'string') { try { const parsed = JSON.parse(value); - if (isPlainObject(parsed)) { - return { value: parsed }; - } - return { errors: [error('object.base')] }; + return { value: parsed }; } catch (e) { return { errors: [error('object.parse')] }; } diff --git a/packages/kbn-config-schema/src/types/object_type.test.ts b/packages/kbn-config-schema/src/types/object_type.test.ts index e101f05e02f2a..67f0963fefdda 100644 --- a/packages/kbn-config-schema/src/types/object_type.test.ts +++ b/packages/kbn-config-schema/src/types/object_type.test.ts @@ -181,14 +181,15 @@ test('called with wrong type', () => { test('handles oneOf', () => { const type = schema.object({ - key: schema.oneOf([schema.string()]), + key: schema.oneOf([schema.string(), schema.arrayOf(schema.string())]), }); expect(type.validate({ key: 'foo' })).toEqual({ key: 'foo' }); expect(() => type.validate({ key: 123 })).toThrowErrorMatchingInlineSnapshot(` -"[key]: types that failed validation: -- [key.0]: expected value of type [string] but got [number]" -`); + "[key]: types that failed validation: + - [key.0]: expected value of type [string] but got [number] + - [key.1]: expected value of type [array] but got [number]" + `); }); test('handles references', () => { diff --git a/packages/kbn-config-schema/src/types/object_type.ts b/packages/kbn-config-schema/src/types/object_type.ts index dd9e3f4e06a91..284ea6fddb35b 100644 --- a/packages/kbn-config-schema/src/types/object_type.ts +++ b/packages/kbn-config-schema/src/types/object_type.ts @@ -186,7 +186,7 @@ export class ObjectType

extends Type> return `expected a plain object value, but found [${typeDetect(value)}] instead.`; case 'object.parse': return `could not parse object value from json input`; - case 'object.allowUnknown': + case 'object.unknown': return `definition for this key is missing`; case 'object.child': return reason[0]; From 69007a377343136cfbba47cb9cc7f6039e2327ee Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 12:34:35 +0200 Subject: [PATCH 14/30] fix record type --- .../kbn-config-schema/src/internals/index.ts | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index c57ba22e87050..3700668c48d18 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -298,7 +298,7 @@ export const internals: JoiRoot = Joi.extend( }, { type: 'record', - prepare(value, { error, prefs }) { + coerce(value, { error, prefs }) { if (value === undefined || isPlainObject(value)) { return { value }; } @@ -306,12 +306,7 @@ export const internals: JoiRoot = Joi.extend( if (prefs.convert && typeof value === 'string') { try { const parsed = JSON.parse(value); - if (isPlainObject(parsed)) { - return { value: parsed }; - } - return { - errors: [error('record.base')], - }; + return { value: parsed }; } catch (e) { return { errors: [error('record.parse')], @@ -322,6 +317,15 @@ export const internals: JoiRoot = Joi.extend( errors: [error('record.base')], }; }, + validate(value, { error }) { + if (!isPlainObject(value)) { + return { + errors: [error('record.base')], + }; + } + + return { value }; + }, rules: { entries: { args: [ @@ -334,24 +338,27 @@ export const internals: JoiRoot = Joi.extend( assert: Joi.object().schema(), }, ], - validate(value, { error }, args, options) { - const result = new Map(); - for (const [entryKey, entryValue] of value) { + method(key, value) { + return this.$_addRule({ name: 'entries', args: { key, value } }); + }, + validate(value, { error }, args) { + const result = {} as Record; + for (const [entryKey, entryValue] of Object.entries(value)) { let validatedEntryKey: any; try { validatedEntryKey = Joi.attempt(entryKey, args.key, { presence: 'required' }); } catch (e) { - return error('record.key', { entryKey, reason: e.message }); + return error('record.key', { entryKey, reason: e }); } let validatedEntryValue: any; try { validatedEntryValue = Joi.attempt(entryValue, args.value, { presence: 'required' }); } catch (e) { - return error('record.value', { entryKey, reason: e.message }); + return error('record.value', { entryKey, reason: e }); } - result.set(validatedEntryKey, validatedEntryValue); + result[validatedEntryKey] = validatedEntryValue; } return result; }, From 56eb879985eab71b7666efd5a70b6dcf1bf0115c Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 12:37:53 +0200 Subject: [PATCH 15/30] fix stream types --- packages/kbn-config-schema/src/internals/index.ts | 1 - packages/kbn-config-schema/src/types/stream_type.test.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index 3700668c48d18..5ab5bd6a9ef02 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -55,7 +55,6 @@ export const internals: JoiRoot = Joi.extend( { type: 'stream', prepare(value, { error }) { - // If value isn't defined, let Joi handle default value if it's defined. if (value instanceof Stream) { return { value }; } diff --git a/packages/kbn-config-schema/src/types/stream_type.test.ts b/packages/kbn-config-schema/src/types/stream_type.test.ts index 34323176e9433..7600feac4a508 100644 --- a/packages/kbn-config-schema/src/types/stream_type.test.ts +++ b/packages/kbn-config-schema/src/types/stream_type.test.ts @@ -51,6 +51,7 @@ describe('#defaultValue', () => { "_events": Object {}, "_eventsCount": 0, "_maxListeners": undefined, + Symbol(kCapture): false, } `); }); From cbf105e72709ec67c17f97b08b04c860257310d3 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 12:39:37 +0200 Subject: [PATCH 16/30] rename test files to match sources --- .../src/types/{record_of_type.test.ts => record_type.test.ts} | 0 .../src/types/{one_of_type.test.ts => union_type.test.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename packages/kbn-config-schema/src/types/{record_of_type.test.ts => record_type.test.ts} (100%) rename packages/kbn-config-schema/src/types/{one_of_type.test.ts => union_type.test.ts} (100%) diff --git a/packages/kbn-config-schema/src/types/record_of_type.test.ts b/packages/kbn-config-schema/src/types/record_type.test.ts similarity index 100% rename from packages/kbn-config-schema/src/types/record_of_type.test.ts rename to packages/kbn-config-schema/src/types/record_type.test.ts diff --git a/packages/kbn-config-schema/src/types/one_of_type.test.ts b/packages/kbn-config-schema/src/types/union_type.test.ts similarity index 100% rename from packages/kbn-config-schema/src/types/one_of_type.test.ts rename to packages/kbn-config-schema/src/types/union_type.test.ts From c2385194d81b660860051684a721ae61c16f8732 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 12:54:56 +0200 Subject: [PATCH 17/30] fix union type tests --- .../src/types/union_type.test.ts | 81 +++++++++++-------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/packages/kbn-config-schema/src/types/union_type.test.ts b/packages/kbn-config-schema/src/types/union_type.test.ts index 7e2077db5412d..c18e516ecd3e4 100644 --- a/packages/kbn-config-schema/src/types/union_type.test.ts +++ b/packages/kbn-config-schema/src/types/union_type.test.ts @@ -62,22 +62,42 @@ test('handles object', () => { }); test('handles object with wrong type', () => { - const type = schema.oneOf([schema.object({ age: schema.number() })]); + const type = schema.oneOf([schema.object({ age: schema.number() }), schema.string()]); expect(() => type.validate({ age: 'foo' })).toThrowErrorMatchingInlineSnapshot(` -"types that failed validation: -- [0.age]: expected value of type [number] but got [string]" -`); + "types that failed validation: + - [0.age]: expected value of type [number] but got [string] + - [1]: expected value of type [string] but got [Object]" + `); }); -test('includes namespace in failure', () => { +test('use shorter error messages when defining only one type', () => { const type = schema.oneOf([schema.object({ age: schema.number() })]); + expect(() => type.validate({ age: 'foo' })).toThrowErrorMatchingInlineSnapshot( + `"[age]: expected value of type [number] but got [string]"` + ); +}); + +test('includes namespace in failure', () => { + const type = schema.oneOf([schema.object({ age: schema.number() }), schema.string()]); + expect(() => type.validate({ age: 'foo' }, {}, 'foo-namespace')) .toThrowErrorMatchingInlineSnapshot(` -"[foo-namespace]: types that failed validation: -- [foo-namespace.0.age]: expected value of type [number] but got [string]" -`); + "[foo-namespace]: types that failed validation: + - [foo-namespace.0.age]: expected value of type [number] but got [string] + - [foo-namespace.1]: expected value of type [string] but got [Object]" + `); +}); + +test('includes namespace in failure in shorthand mode', () => { + const type = schema.oneOf([schema.object({ age: schema.number() })]); + + expect(() => + type.validate({ age: 'foo' }, {}, 'foo-namespace') + ).toThrowErrorMatchingInlineSnapshot( + `"[foo-namespace.age]: expected value of type [number] but got [string]"` + ); }); test('handles multiple objects with same key', () => { @@ -106,33 +126,32 @@ test('handles maybe', () => { test('fails if not matching type', () => { const type = schema.oneOf([schema.string()]); - expect(() => type.validate(false)).toThrowErrorMatchingInlineSnapshot(` -"types that failed validation: -- [0]: expected value of type [string] but got [boolean]" -`); - expect(() => type.validate(123)).toThrowErrorMatchingInlineSnapshot(` -"types that failed validation: -- [0]: expected value of type [string] but got [number]" -`); + expect(() => type.validate(false)).toThrowErrorMatchingInlineSnapshot( + `"expected value of type [string] but got [boolean]"` + ); + expect(() => type.validate(123)).toThrowErrorMatchingInlineSnapshot( + `"expected value of type [string] but got [number]"` + ); }); test('fails if not matching multiple types', () => { const type = schema.oneOf([schema.string(), schema.number()]); expect(() => type.validate(false)).toThrowErrorMatchingInlineSnapshot(` -"types that failed validation: -- [0]: expected value of type [string] but got [boolean] -- [1]: expected value of type [number] but got [boolean]" -`); + "types that failed validation: + - [0]: expected value of type [string] but got [boolean] + - [1]: expected value of type [number] but got [boolean]" + `); }); test('fails if not matching literal', () => { - const type = schema.oneOf([schema.literal('foo')]); + const type = schema.oneOf([schema.literal('foo'), schema.literal('dolly')]); expect(() => type.validate('bar')).toThrowErrorMatchingInlineSnapshot(` -"types that failed validation: -- [0]: expected value to equal [foo]" -`); + "types that failed validation: + - [0]: expected value to equal [foo] + - [1]: expected value to equal [dolly]" + `); }); test('fails if nested union type fail', () => { @@ -142,12 +161,10 @@ test('fails if nested union type fail', () => { ]); 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]: could not parse object value from json input - - [1]: expected value of type [number] but got [string]" -`); + "types that failed validation: + - [0]: expected value of type [boolean] but got [string] + - [1]: types that failed validation: + - [0]: could not parse object value from json input + - [1]: expected value of type [number] but got [string]" + `); }); From 178302377301c76a9c98dbcd1e7d0954009cd319 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 16:06:45 +0200 Subject: [PATCH 18/30] temporary adapt feature/home usages of Joi --- .../sample_data/lib/sample_dataset_schema.ts | 4 ++-- .../services/sample_data/sample_data_registry.ts | 9 ++++++--- .../server/services/tutorials/lib/tutorial_schema.ts | 12 ++++++++---- .../server/services/tutorials/tutorials_registry.ts | 6 +++--- x-pack/plugins/features/server/feature_schema.ts | 12 ++++-------- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts b/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts index eb0b2252774b5..b21e8849988bb 100644 --- a/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts +++ b/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts @@ -41,7 +41,7 @@ const appLinkSchema = Joi.object({ icon: Joi.string().required(), }); -export const sampleDataSchema = { +export const sampleDataSchema = Joi.object({ id: Joi.string() .regex(/^[a-zA-Z0-9-]+$/) .required(), @@ -61,4 +61,4 @@ export const sampleDataSchema = { // Should provide a nice demo of Kibana's functionality with the sample data set savedObjects: Joi.array().items(Joi.object()).required(), dataIndices: Joi.array().items(dataIndexSchema).required(), -}; +}); diff --git a/src/plugins/home/server/services/sample_data/sample_data_registry.ts b/src/plugins/home/server/services/sample_data/sample_data_registry.ts index ca75d20dc1d3f..85ef99f4524dd 100644 --- a/src/plugins/home/server/services/sample_data/sample_data_registry.ts +++ b/src/plugins/home/server/services/sample_data/sample_data_registry.ts @@ -29,6 +29,7 @@ const ecommerceSampleDataset = ecommerceSpecProvider(); export class SampleDataRegistry { constructor(private readonly initContext: PluginInitializerContext) {} + private readonly sampleDatasets: SampleDatasetSchema[] = [ flightsSampleDataset, logsSampleDataset, @@ -55,9 +56,10 @@ export class SampleDataRegistry { return { registerSampleDataset: (specProvider: SampleDatasetProvider) => { - const { error, value } = Joi.validate(specProvider(), sampleDataSchema); - - if (error) { + let value: any; + try { + value = Joi.attempt(specProvider(), sampleDataSchema); + } catch (error) { throw new Error(`Unable to register sample dataset spec because it's invalid. ${error}`); } const defaultIndexSavedObjectJson = value.savedObjects.find((savedObjectJson: any) => { @@ -164,6 +166,7 @@ export class SampleDataRegistry { return {}; } } + /** @public */ export type SampleDataRegistrySetup = ReturnType; diff --git a/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts b/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts index 0f06b6c3257c2..ffb6104011dbd 100644 --- a/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts +++ b/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts @@ -85,7 +85,9 @@ const paramSchema = Joi.object({ .regex(/^[a-zA-Z_]+$/) .required(), label: Joi.string().required(), - type: Joi.string().valid(Object.values(PARAM_TYPES)).required(), + type: Joi.string() + .valid(...Object.values(PARAM_TYPES)) + .required(), }); const instructionsSchema = Joi.object({ @@ -93,11 +95,13 @@ const instructionsSchema = Joi.object({ params: Joi.array().items(paramSchema), }); -export const tutorialSchema = { +export const tutorialSchema = Joi.object({ id: Joi.string() .regex(/^[a-zA-Z0-9-]+$/) .required(), - category: Joi.string().valid(Object.values(TUTORIAL_CATEGORY)).required(), + category: Joi.string() + .valid(...Object.values(TUTORIAL_CATEGORY)) + .required(), name: Joi.string().required(), moduleName: Joi.string(), isBeta: Joi.boolean().default(false), @@ -122,4 +126,4 @@ export const tutorialSchema = { // saved objects used by data module. savedObjects: Joi.array().items(), savedObjectsInstallMsg: Joi.string(), -}; +}); diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.ts index f21f2ccd719c5..9fd04df1adc77 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.ts @@ -43,9 +43,9 @@ export class TutorialsRegistry { return { registerTutorial: (specProvider: TutorialProvider) => { const emptyContext = {}; - const { error } = Joi.validate(specProvider(emptyContext), tutorialSchema); - - if (error) { + try { + Joi.attempt(specProvider(emptyContext), tutorialSchema); + } catch (error) { throw new Error(`Unable to register tutorial spec because its invalid. ${error}`); } diff --git a/x-pack/plugins/features/server/feature_schema.ts b/x-pack/plugins/features/server/feature_schema.ts index 204c5bdfe2469..0025f4398fd79 100644 --- a/x-pack/plugins/features/server/feature_schema.ts +++ b/x-pack/plugins/features/server/feature_schema.ts @@ -156,10 +156,8 @@ const elasticsearchFeatureSchema = Joi.object({ }); export function validateKibanaFeature(feature: KibanaFeatureConfig) { - const validateResult = Joi.validate(feature, kibanaFeatureSchema); - if (validateResult.error) { - throw validateResult.error; - } + Joi.attempt(feature, kibanaFeatureSchema); + // the following validation can't be enforced by the Joi schema, since it'd require us looking "up" the object graph for the list of valid value, which they explicitly forbid. const { app = [], management = {}, catalogue = [], alerting = [] } = feature; @@ -343,10 +341,8 @@ export function validateKibanaFeature(feature: KibanaFeatureConfig) { } export function validateElasticsearchFeature(feature: ElasticsearchFeatureConfig) { - const validateResult = Joi.validate(feature, elasticsearchFeatureSchema); - if (validateResult.error) { - throw validateResult.error; - } + Joi.attempt(feature, elasticsearchFeatureSchema); + // the following validation can't be enforced by the Joi schema without a very convoluted and verbose definition const { privileges } = feature; privileges.forEach((privilege, index) => { From f3445832ee0ba97a9f8fc3110734afc467472998 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 16:07:01 +0200 Subject: [PATCH 19/30] fix lint --- packages/kbn-config-schema/src/types/string_type.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kbn-config-schema/src/types/string_type.test.ts b/packages/kbn-config-schema/src/types/string_type.test.ts index 55b2f050c5894..595827fd6a863 100644 --- a/packages/kbn-config-schema/src/types/string_type.test.ts +++ b/packages/kbn-config-schema/src/types/string_type.test.ts @@ -94,10 +94,10 @@ describe('#hostname', () => { ); // DOMAIN_LONG_SEGMENT (63 chars max per segment) - https://github.com/sideway/address/blob/master/lib/domain.js#L79 - //const hostNameWithMaxAllowedLength = 'a'.repeat(100); - //expect(hostNameSchema.validate(hostNameWithMaxAllowedLength)).toBe( + // const hostNameWithMaxAllowedLength = 'a'.repeat(100); + // expect(hostNameSchema.validate(hostNameWithMaxAllowedLength)).toBe( // hostNameWithMaxAllowedLength - //); + // ); }); test('returns error when value is not a valid hostname', () => { From acf5073487ef6037c33c309617efddc7882407b1 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 16:10:54 +0200 Subject: [PATCH 20/30] adapt test assertion --- src/core/server/csp/config.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/server/csp/config.test.ts b/src/core/server/csp/config.test.ts index c7f6c4a214fac..8036ebeeaad31 100644 --- a/src/core/server/csp/config.test.ts +++ b/src/core/server/csp/config.test.ts @@ -13,7 +13,7 @@ describe('config.validate()', () => { // This is intentionally not editable in the raw CSP config. // Users should set `server.securityResponseHeaders.disableEmbedding` to control this config property. expect(() => config.schema.validate({ disableEmbedding: true })).toThrowError( - '[disableEmbedding.0]: expected value to equal [false]' + '[disableEmbedding]: expected value to equal [false]' ); }); }); From 3513b246f622862d7e7e257c405cb1c1f37113c7 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 17:06:12 +0200 Subject: [PATCH 21/30] fix http config schema validation --- src/core/server/http/__snapshots__/http_config.test.ts.snap | 2 +- src/core/server/http/http_config.ts | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/core/server/http/__snapshots__/http_config.test.ts.snap b/src/core/server/http/__snapshots__/http_config.test.ts.snap index 42710aad40ac1..65ac08f6ce5f7 100644 --- a/src/core/server/http/__snapshots__/http_config.test.ts.snap +++ b/src/core/server/http/__snapshots__/http_config.test.ts.snap @@ -119,7 +119,7 @@ Object { exports[`throws if invalid hostname 1`] = `"[host]: value must be a valid hostname (see RFC 1123)."`; -exports[`throws if invalid hostname 2`] = `"[host]: value 0 is not a valid hostname (use \\"0.0.0.0\\" to bind to all interfaces)"`; +exports[`throws if invalid hostname 2`] = `"[host]: value must be a valid hostname (see RFC 1123)."`; exports[`with TLS throws if TLS is enabled but \`redirectHttpFromPort\` is equal to \`port\` 1`] = `"Kibana does not accept http traffic to [port] when ssl is enabled (only https is allowed), so [ssl.redirectHttpFromPort] cannot be configured to the same value. Both are [1234]."`; diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 1f8fd95d69051..a6a133753c3fe 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -87,11 +87,6 @@ const configSchema = schema.object( host: schema.string({ defaultValue: 'localhost', hostname: true, - validate(value) { - if (value === '0') { - return 'value 0 is not a valid hostname (use "0.0.0.0" to bind to all interfaces)'; - } - }, }), maxPayload: schema.byteSize({ defaultValue: '1048576b', From 73bd9f61201936f611d499accdf3927a5d20076e Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 19:31:44 +0200 Subject: [PATCH 22/30] fix @kbn/test Config class --- .../src/functional_test_runner/lib/config/config.ts | 6 +++--- .../src/functional_test_runner/lib/config/schema.ts | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts index 42da2572f8852..9875fae8b0a86 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts @@ -50,20 +50,20 @@ export class Config { values: Record, childSchema: any ): boolean { - if (!childSchema._inner) { + if (!childSchema.$_terms.keys) { return false; } // normalize child and pattern checks so we can iterate the checks in a single loop const checks: Array<{ test: (k: string) => boolean; schema: Schema }> = [ // match children first, they have priority - ...(childSchema._inner.children || []).map((child: { key: string; schema: Schema }) => ({ + ...(childSchema.$_terms.keys || []).map((child: { key: string; schema: Schema }) => ({ test: (k: string) => child.key === k, schema: child.schema, })), // match patterns on any key that doesn't match an explicit child - ...(childSchema._inner.patterns || []).map((pattern: { regex: RegExp; rule: Schema }) => ({ + ...(childSchema.$_terms.patterns || []).map((pattern: { regex: RegExp; rule: Schema }) => ({ test: (k: string) => pattern.regex.test(k) && has(values, k), schema: pattern.rule, })), diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index 2f5b20fdc7b27..3ffa84ab9514f 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -9,6 +9,7 @@ import { dirname, resolve } from 'path'; import Joi from 'joi'; +import type { CustomHelpers } from 'joi'; // valid pattern for ID // enforced camel-case identifiers for consistency @@ -61,8 +62,10 @@ const dockerServerSchema = () => .default(); const defaultRelativeToConfigPath = (path: string) => { - const makeDefault: any = (_: any, options: any) => resolve(dirname(options.context.path), path); - makeDefault.description = `/${path}`; + const makeDefault = (parent: any, helpers: CustomHelpers) => { + helpers.schema.description(`/${path}`); + return resolve(dirname(helpers.prefs.context!.path), path); + }; return makeDefault; }; From 3aa3b71b7383537a3d572d97f2f1109b99946646 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Wed, 12 May 2021 21:56:50 +0200 Subject: [PATCH 23/30] fix config again --- .../lib/config/config.test.ts | 26 +++++++++++++++++++ .../lib/config/config.ts | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 packages/kbn-test/src/functional_test_runner/lib/config/config.test.ts diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/config.test.ts b/packages/kbn-test/src/functional_test_runner/lib/config/config.test.ts new file mode 100644 index 0000000000000..2d764da785573 --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/config/config.test.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Config } from './config'; + +describe('Config', () => { + it('recognize keys under `object().pattern`', () => { + const config = new Config({ + settings: { + services: { + foo: () => 42, + }, + }, + primary: true, + path: process.cwd(), + }); + + expect(config.has('services.foo')).toEqual(true); + expect(config.get('services.foo')()).toEqual(42); + }); +}); diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts index 9875fae8b0a86..1d4af9c33fb79 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts @@ -50,7 +50,7 @@ export class Config { values: Record, childSchema: any ): boolean { - if (!childSchema.$_terms.keys) { + if (!childSchema.$_terms.keys && !childSchema.$_terms.patterns) { return false; } From 1dfe1b0d61d1081704f48cf854a25ec0c19cde4c Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 18 May 2021 15:27:00 +0200 Subject: [PATCH 24/30] fix reporting schema tests --- x-pack/plugins/reporting/server/config/schema.test.ts | 2 +- x-pack/plugins/reporting/server/config/schema.ts | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/reporting/server/config/schema.test.ts b/x-pack/plugins/reporting/server/config/schema.test.ts index e299db2405125..0998a80103131 100644 --- a/x-pack/plugins/reporting/server/config/schema.test.ts +++ b/x-pack/plugins/reporting/server/config/schema.test.ts @@ -302,7 +302,7 @@ describe('Reporting Config Schema', () => { // kibanaServer const throwValidationErr = () => ConfigSchema.validate({ kibanaServer: { hostname: '0' } }); expect(throwValidationErr).toThrowError( - `[kibanaServer.hostname]: must not be "0" for the headless browser to correctly resolve the host` + `[kibanaServer.hostname]: value must be a valid hostname (see RFC 1123).` ); }); }); diff --git a/x-pack/plugins/reporting/server/config/schema.ts b/x-pack/plugins/reporting/server/config/schema.ts index f56bf5520072b..d616a18289df0 100644 --- a/x-pack/plugins/reporting/server/config/schema.ts +++ b/x-pack/plugins/reporting/server/config/schema.ts @@ -9,16 +9,7 @@ import { ByteSizeValue, schema, TypeOf } from '@kbn/config-schema'; import moment from 'moment'; const KibanaServerSchema = schema.object({ - hostname: schema.maybe( - schema.string({ - validate(value) { - if (value === '0') { - return 'must not be "0" for the headless browser to correctly resolve the host'; - } - }, - hostname: true, - }) - ), + hostname: schema.maybe(schema.string({ hostname: true })), port: schema.maybe(schema.number()), protocol: schema.maybe( schema.string({ From 76d5445858d05a47778beaa157d4bdb41bc050af Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 18 May 2021 16:18:02 +0200 Subject: [PATCH 25/30] fix security solution schema --- .../common/endpoint/schema/trusted_apps.ts | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts index 54d0becd2446e..d6f1307b5d1be 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/trusted_apps.ts @@ -87,39 +87,37 @@ const MacEntrySchema = schema.object({ ...CommonEntrySchema, }); +const entriesSchemaOptions = { + minSize: 1, + validate(entries: ConditionEntry[]) { + return ( + getDuplicateFields(entries) + .map((field) => `duplicatedEntry.${field}`) + .join(', ') || undefined + ); + }, +}; + /* - * Entry Schema depending on Os type using schema.conditional. + * Entities array schema depending on Os type using schema.conditional. * If OS === WINDOWS then use Windows schema, * else if OS === LINUX then use Linux schema, * else use Mac schema + * + * The validate function checks there is no duplicated entry inside the array */ -const EntrySchemaDependingOnOS = schema.conditional( +const EntriesSchema = schema.conditional( schema.siblingRef('os'), OperatingSystem.WINDOWS, - WindowsEntrySchema, + schema.arrayOf(WindowsEntrySchema, entriesSchemaOptions), schema.conditional( schema.siblingRef('os'), OperatingSystem.LINUX, - LinuxEntrySchema, - MacEntrySchema + schema.arrayOf(LinuxEntrySchema, entriesSchemaOptions), + schema.arrayOf(MacEntrySchema, entriesSchemaOptions) ) ); -/* - * Entities array schema. - * The validate function checks there is no duplicated entry inside the array - */ -const EntriesSchema = schema.arrayOf(EntrySchemaDependingOnOS, { - minSize: 1, - validate(entries: ConditionEntry[]) { - return ( - getDuplicateFields(entries) - .map((field) => `duplicatedEntry.${field}`) - .join(', ') || undefined - ); - }, -}); - const getTrustedAppForOsScheme = (forUpdateFlow: boolean = false) => schema.object({ name: schema.string({ minLength: 1, maxLength: 256 }), From 344926dfebc93493750c0e15bd60c14615485dca Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 18 May 2021 17:20:53 +0200 Subject: [PATCH 26/30] adapt url tests --- .../src/types/string_type.test.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/kbn-config-schema/src/types/string_type.test.ts b/packages/kbn-config-schema/src/types/string_type.test.ts index 595827fd6a863..7eb6f386fcea1 100644 --- a/packages/kbn-config-schema/src/types/string_type.test.ts +++ b/packages/kbn-config-schema/src/types/string_type.test.ts @@ -85,24 +85,27 @@ describe('#hostname', () => { expect(hostNameSchema.validate('www.example.com')).toBe('www.example.com'); expect(hostNameSchema.validate('3domain.local')).toBe('3domain.local'); expect(hostNameSchema.validate('hostname')).toBe('hostname'); - // DOMAIN_INVALID_TLDS_CHARS (last segment must start with alphanum) - https://github.com/sideway/address/blob/master/lib/domain.js#L88 - // expect(hostNameSchema.validate('2387628')).toBe('2387628'); expect(hostNameSchema.validate('::1')).toBe('::1'); expect(hostNameSchema.validate('0:0:0:0:0:0:0:1')).toBe('0:0:0:0:0:0:0:1'); expect(hostNameSchema.validate('xn----ascii-7gg5ei7b1i.xn--90a3a')).toBe( 'xn----ascii-7gg5ei7b1i.xn--90a3a' ); - // DOMAIN_LONG_SEGMENT (63 chars max per segment) - https://github.com/sideway/address/blob/master/lib/domain.js#L79 - // const hostNameWithMaxAllowedLength = 'a'.repeat(100); - // expect(hostNameSchema.validate(hostNameWithMaxAllowedLength)).toBe( - // hostNameWithMaxAllowedLength - // ); + const hostNameWithMaxAllowedLength = Array(4).fill('a'.repeat(63)).join('.'); + expect(hostNameSchema.validate(hostNameWithMaxAllowedLength)).toBe( + hostNameWithMaxAllowedLength + ); }); test('returns error when value is not a valid hostname', () => { const hostNameSchema = schema.string({ hostname: true }); + expect(() => hostNameSchema.validate('2387628')).toThrowErrorMatchingInlineSnapshot( + `"value must be a valid hostname (see RFC 1123)."` + ); + expect(() => + hostNameSchema.validate(Array(4).fill('a'.repeat(64)).join('.')) + ).toThrowErrorMatchingInlineSnapshot(`"value must be a valid hostname (see RFC 1123)."`); expect(() => hostNameSchema.validate('host:name')).toThrowErrorMatchingInlineSnapshot( `"value must be a valid hostname (see RFC 1123)."` ); @@ -115,9 +118,7 @@ describe('#hostname', () => { expect(() => hostNameSchema.validate('0:?:0:0:0:0:0:1')).toThrowErrorMatchingInlineSnapshot( `"value must be a valid hostname (see RFC 1123)."` ); - - const tooLongHostName = 'a'.repeat(256); - expect(() => hostNameSchema.validate(tooLongHostName)).toThrowErrorMatchingInlineSnapshot( + expect(() => hostNameSchema.validate('a'.repeat(256))).toThrowErrorMatchingInlineSnapshot( `"value must be a valid hostname (see RFC 1123)."` ); }); From 0952a6d8c8a726dd2bf523efb9474bc41e6374e4 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 18 May 2021 17:25:08 +0200 Subject: [PATCH 27/30] remove useless comment --- packages/kbn-config-schema/src/types/type.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kbn-config-schema/src/types/type.ts b/packages/kbn-config-schema/src/types/type.ts index 53d75548bff10..e92c070c8ab1a 100644 --- a/packages/kbn-config-schema/src/types/type.ts +++ b/packages/kbn-config-schema/src/types/type.ts @@ -8,7 +8,6 @@ import type { AnySchema, CustomValidator, ErrorReport } from 'joi'; import { SchemaTypeError, ValidationError } from '../errors'; -// import { internals } from '../internals'; import { Reference } from '../references'; export interface TypeOptions { From 645f9fb5514e107771ab3641ecfad2457e1cb82b Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 18 May 2021 17:35:48 +0200 Subject: [PATCH 28/30] remove space --- packages/kbn-config-schema/types/joi.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kbn-config-schema/types/joi.d.ts b/packages/kbn-config-schema/types/joi.d.ts index 711c8f0f5332f..17c1763488182 100644 --- a/packages/kbn-config-schema/types/joi.d.ts +++ b/packages/kbn-config-schema/types/joi.d.ts @@ -12,7 +12,6 @@ import { ByteSizeValue } from '../src/byte_size_value'; declare module 'joi' { interface BytesSchema extends AnySchema { min(limit: number | string | ByteSizeValue): this; - max(limit: number | string | ByteSizeValue): this; } From d1c61fe5a5872b3e886c0272afca907b9a444bad Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 18 May 2021 17:36:51 +0200 Subject: [PATCH 29/30] typo --- .../src/functional_test_runner/lib/config/config.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/config.test.ts b/packages/kbn-test/src/functional_test_runner/lib/config/config.test.ts index 2d764da785573..88c1fd99f0014 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/config.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/config.test.ts @@ -9,7 +9,7 @@ import { Config } from './config'; describe('Config', () => { - it('recognize keys under `object().pattern`', () => { + it('recognizes keys under `object().pattern`', () => { const config = new Config({ settings: { services: { From c69cbf14a2e26d352630128adcc6a4fc64a34636 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 20 May 2021 08:27:29 +0200 Subject: [PATCH 30/30] review comments --- packages/kbn-config-schema/src/types/stream_type.test.ts | 9 +-------- packages/kbn-config-schema/src/types/type.ts | 7 +++---- packages/kbn-config-schema/types/joi.d.ts | 7 ++----- yarn.lock | 5 ----- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/packages/kbn-config-schema/src/types/stream_type.test.ts b/packages/kbn-config-schema/src/types/stream_type.test.ts index 7600feac4a508..0f3e1d42dc3a5 100644 --- a/packages/kbn-config-schema/src/types/stream_type.test.ts +++ b/packages/kbn-config-schema/src/types/stream_type.test.ts @@ -46,14 +46,7 @@ test('includes namespace in failure', () => { describe('#defaultValue', () => { test('returns default when undefined', () => { const value = new Stream(); - expect(schema.stream({ defaultValue: value }).validate(undefined)).toMatchInlineSnapshot(` - Stream { - "_events": Object {}, - "_eventsCount": 0, - "_maxListeners": undefined, - Symbol(kCapture): false, - } - `); + expect(schema.stream({ defaultValue: value }).validate(undefined)).toBeInstanceOf(Stream); }); test('returns value when specified', () => { diff --git a/packages/kbn-config-schema/src/types/type.ts b/packages/kbn-config-schema/src/types/type.ts index e92c070c8ab1a..696101fb2c223 100644 --- a/packages/kbn-config-schema/src/types/type.ts +++ b/packages/kbn-config-schema/src/types/type.ts @@ -112,11 +112,7 @@ export abstract class Type { } const { local, code, path, value } = error; - // `message` is only initialized once `toString` has been called (...) - // see https://github.com/sideway/joi/blob/master/lib/errors.js - const message = error.toString(); const convertedPath = path.map((entry) => entry.toString()); - const context: Record = { ...local, value, @@ -139,6 +135,9 @@ export abstract class Type { return new SchemaTypeError(context.message, convertedPath); } + // `message` is only initialized once `toString` has been called (...) + // see https://github.com/sideway/joi/blob/master/lib/errors.js + const message = error.toString(); return new SchemaTypeError(message || code, convertedPath); } } diff --git a/packages/kbn-config-schema/types/joi.d.ts b/packages/kbn-config-schema/types/joi.d.ts index 17c1763488182..5dd695cb05e88 100644 --- a/packages/kbn-config-schema/types/joi.d.ts +++ b/packages/kbn-config-schema/types/joi.d.ts @@ -12,6 +12,7 @@ import { ByteSizeValue } from '../src/byte_size_value'; declare module 'joi' { interface BytesSchema extends AnySchema { min(limit: number | string | ByteSizeValue): this; + max(limit: number | string | ByteSizeValue): this; } @@ -27,6 +28,7 @@ declare module 'joi' { // missing from the typedef // see https://github.com/sideway/joi/blob/master/lib/errors.js local?: Record; + toString(): string; } @@ -37,9 +39,4 @@ declare module 'joi' { record: () => RecordSchema; stream: () => AnySchema; }; - - // Joi types don't include `schema` function even though it's supported. - interface ObjectSchema { - schema(): this; - } } diff --git a/yarn.lock b/yarn.lock index e75cddc2f9bdc..fefcdfa32a437 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15333,11 +15333,6 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoek@5.x.x: - version "5.0.4" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.4.tgz#0f7fa270a1cafeb364a4b2ddfaa33f864e4157da" - integrity sha512-Alr4ZQgoMlnere5FZJsIyfIjORBqZll5POhDsF4q64dPuJR6rNxXdDxtHSQq8OXRurhmx+PWYEE8bXRROY8h0w== - hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.5, hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"