[Mappings Editor] Remove Joi (#54913)

This commit is contained in:
Jean-Louis Leysens 2020-01-15 18:42:02 +01:00 committed by Alison Goryachev
parent 5d4cb4767e
commit 781ef7a2c0
4 changed files with 140 additions and 89 deletions

View file

@ -6,7 +6,7 @@
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import Joi from 'joi';
import * as t from 'io-ts';
import { EuiLink, EuiCode } from '@elastic/eui';
import {
@ -100,11 +100,14 @@ const fielddataFrequencyFilterParam = {
},
},
},
schema: Joi.object().keys({
min: Joi.number(),
max: Joi.number(),
min_segment_size: Joi.number(),
}),
schema: t.intersection([
t.partial({
min: t.number,
max: t.number,
min_segment_size: t.number,
}),
t.brand(t.UnknownRecord, (v: any): v is any => !Array.isArray(v), 'Array'),
]),
};
const analyzerValidations = [
@ -178,40 +181,40 @@ export const PARAMETERS_DEFINITION = {
},
],
},
schema: Joi.string(),
schema: t.string,
},
store: {
fieldConfig: {
type: FIELD_TYPES.CHECKBOX,
defaultValue: false,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
index: {
fieldConfig: {
type: FIELD_TYPES.CHECKBOX,
defaultValue: true,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
doc_values: {
fieldConfig: {
defaultValue: true,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
doc_values_binary: {
fieldConfig: {
defaultValue: false,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
fielddata: {
fieldConfig: {
type: FIELD_TYPES.CHECKBOX,
defaultValue: false,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
fielddata_frequency_filter: fielddataFrequencyFilterParam,
fielddata_frequency_filter_percentage: {
@ -280,19 +283,19 @@ export const PARAMETERS_DEFINITION = {
fieldConfig: {
defaultValue: true,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
coerce_shape: {
fieldConfig: {
defaultValue: false,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
ignore_malformed: {
fieldConfig: {
defaultValue: false,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
null_value: {
fieldConfig: {
@ -300,7 +303,7 @@ export const PARAMETERS_DEFINITION = {
type: FIELD_TYPES.TEXT,
label: nullValueLabel,
},
schema: Joi.string(),
schema: t.string,
},
null_value_ip: {
fieldConfig: {
@ -323,7 +326,7 @@ export const PARAMETERS_DEFINITION = {
},
],
},
schema: Joi.number(),
schema: t.number,
},
null_value_boolean: {
fieldConfig: {
@ -332,7 +335,7 @@ export const PARAMETERS_DEFINITION = {
deserializer: (value: string | boolean) => mapIndexToValue.indexOf(value),
serializer: (value: number) => mapIndexToValue[value],
},
schema: Joi.any().valid([true, false, 'true', 'false']),
schema: t.union([t.literal(true), t.literal(false), t.literal('true'), t.literal('false')]),
},
null_value_geo_point: {
fieldConfig: {
@ -376,7 +379,7 @@ export const PARAMETERS_DEFINITION = {
}
},
},
schema: Joi.any(),
schema: t.any,
},
copy_to: {
fieldConfig: {
@ -398,7 +401,7 @@ export const PARAMETERS_DEFINITION = {
},
],
},
schema: Joi.string(),
schema: t.string,
},
max_input_length: {
fieldConfig: {
@ -421,7 +424,7 @@ export const PARAMETERS_DEFINITION = {
},
],
},
schema: Joi.number(),
schema: t.number,
},
locale: {
fieldConfig: {
@ -454,7 +457,7 @@ export const PARAMETERS_DEFINITION = {
},
],
},
schema: Joi.string(),
schema: t.string,
},
orientation: {
fieldConfig: {
@ -464,7 +467,7 @@ export const PARAMETERS_DEFINITION = {
defaultMessage: 'Orientation',
}),
},
schema: Joi.string(),
schema: t.string,
},
boost: {
fieldConfig: {
@ -484,7 +487,7 @@ export const PARAMETERS_DEFINITION = {
},
],
} as FieldConfig,
schema: Joi.number(),
schema: t.number,
},
scaling_factor: {
title: i18n.translate('xpack.idxMgmt.mappingsEditor.parameters.scalingFactorFieldTitle', {
@ -535,7 +538,7 @@ export const PARAMETERS_DEFINITION = {
defaultMessage: 'Value must be greater than 0.',
}),
} as FieldConfig,
schema: Joi.number(),
schema: t.number,
},
dynamic: {
fieldConfig: {
@ -545,7 +548,7 @@ export const PARAMETERS_DEFINITION = {
type: FIELD_TYPES.CHECKBOX,
defaultValue: true,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
enabled: {
fieldConfig: {
@ -555,7 +558,7 @@ export const PARAMETERS_DEFINITION = {
type: FIELD_TYPES.CHECKBOX,
defaultValue: true,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
format: {
fieldConfig: {
@ -577,7 +580,7 @@ export const PARAMETERS_DEFINITION = {
/>
),
},
schema: Joi.string(),
schema: t.string,
},
analyzer: {
fieldConfig: {
@ -587,7 +590,7 @@ export const PARAMETERS_DEFINITION = {
defaultValue: INDEX_DEFAULT,
validations: analyzerValidations,
},
schema: Joi.string(),
schema: t.string,
},
search_analyzer: {
fieldConfig: {
@ -597,7 +600,7 @@ export const PARAMETERS_DEFINITION = {
defaultValue: INDEX_DEFAULT,
validations: analyzerValidations,
},
schema: Joi.string(),
schema: t.string,
},
search_quote_analyzer: {
fieldConfig: {
@ -607,7 +610,7 @@ export const PARAMETERS_DEFINITION = {
defaultValue: INDEX_DEFAULT,
validations: analyzerValidations,
},
schema: Joi.string(),
schema: t.string,
},
normalizer: {
fieldConfig: {
@ -636,76 +639,76 @@ export const PARAMETERS_DEFINITION = {
defaultMessage: `The name of a normalizer defined in the index's settings.`,
}),
},
schema: Joi.string(),
schema: t.string,
},
index_options: {
fieldConfig: {
...indexOptionsConfig,
defaultValue: 'positions',
},
schema: Joi.string(),
schema: t.string,
},
index_options_keyword: {
fieldConfig: {
...indexOptionsConfig,
defaultValue: 'docs',
},
schema: Joi.string(),
schema: t.string,
},
index_options_flattened: {
fieldConfig: {
...indexOptionsConfig,
defaultValue: 'docs',
},
schema: Joi.string(),
schema: t.string,
},
eager_global_ordinals: {
fieldConfig: {
defaultValue: false,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
index_phrases: {
fieldConfig: {
defaultValue: false,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
preserve_separators: {
fieldConfig: {
defaultValue: true,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
preserve_position_increments: {
fieldConfig: {
defaultValue: true,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
ignore_z_value: {
fieldConfig: {
defaultValue: true,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
points_only: {
fieldConfig: {
defaultValue: false,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
norms: {
fieldConfig: {
defaultValue: true,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
norms_keyword: {
fieldConfig: {
defaultValue: false,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
term_vector: {
fieldConfig: {
@ -715,7 +718,7 @@ export const PARAMETERS_DEFINITION = {
}),
defaultValue: 'no',
},
schema: Joi.string(),
schema: t.string,
},
path: {
fieldConfig: {
@ -741,7 +744,7 @@ export const PARAMETERS_DEFINITION = {
serializer: (value: AliasOption[]) => (value.length === 0 ? '' : value[0].id),
} as FieldConfig<any, string>,
targetTypesNotAllowed: ['object', 'nested', 'alias'] as DataType[],
schema: Joi.string(),
schema: t.string,
},
position_increment_gap: {
fieldConfig: {
@ -771,7 +774,7 @@ export const PARAMETERS_DEFINITION = {
},
],
},
schema: Joi.number(),
schema: t.number,
},
index_prefixes: {
fieldConfig: { defaultValue: {} }, // Needed for FieldParams typing
@ -791,9 +794,9 @@ export const PARAMETERS_DEFINITION = {
} as FieldConfig,
},
},
schema: Joi.object().keys({
min_chars: Joi.number(),
max_chars: Joi.number(),
schema: t.partial({
min_chars: t.number,
max_chars: t.number,
}),
},
similarity: {
@ -804,13 +807,13 @@ export const PARAMETERS_DEFINITION = {
defaultMessage: 'Similarity algorithm',
}),
},
schema: Joi.string(),
schema: t.string,
},
split_queries_on_whitespace: {
fieldConfig: {
defaultValue: false,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
ignore_above: {
fieldConfig: {
@ -842,13 +845,13 @@ export const PARAMETERS_DEFINITION = {
},
],
},
schema: Joi.number(),
schema: t.number,
},
enable_position_increments: {
fieldConfig: {
defaultValue: true,
},
schema: Joi.boolean().strict(),
schema: t.boolean,
},
depth_limit: {
fieldConfig: {
@ -868,7 +871,7 @@ export const PARAMETERS_DEFINITION = {
},
],
},
schema: Joi.number(),
schema: t.number,
},
dims: {
fieldConfig: {
@ -894,6 +897,6 @@ export const PARAMETERS_DEFINITION = {
},
],
},
schema: Joi.string(),
schema: t.string,
},
};

View file

@ -0,0 +1,42 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { ValidationError, Validation } from 'io-ts';
import { fold } from 'fp-ts/lib/Either';
import { Reporter } from 'io-ts/lib/Reporter';
export type ReporterResult = Array<{ path: string[]; message: string }>;
export const failure = (validation: any): ReporterResult => {
return validation.map((e: ValidationError) => {
const path: string[] = [];
let validationName = '';
e.context.forEach((ctx, idx) => {
if (ctx.key) {
path.push(ctx.key);
}
if (idx === e.context.length - 1) {
validationName = ctx.type.name;
}
});
const lastItemName = path[path.length - 1];
return {
path,
message:
'Invalid value ' +
JSON.stringify(e.value) +
` supplied to ${lastItemName}(${validationName})`,
};
});
};
const empty: never[] = [];
const success = () => empty;
export const ErrorReporter: Reporter<ReporterResult> = {
report: (validation: Validation<any>) => fold(failure, success)(validation as any),
};

View file

@ -63,9 +63,9 @@ describe('Mappings configuration validator', () => {
expect(errors).not.toBe(undefined);
expect(errors!.length).toBe(3);
expect(errors!).toEqual([
{ code: 'ERR_CONFIG', configName: 'numeric_detection' },
{ code: 'ERR_CONFIG', configName: 'dynamic_date_formats' },
{ code: 'ERR_CONFIG', configName: '_source' },
{ code: 'ERR_CONFIG', configName: 'dynamic_date_formats' },
{ code: 'ERR_CONFIG', configName: 'numeric_detection' },
]);
});
});

View file

@ -3,7 +3,12 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import Joi from 'joi';
import { pick } from 'lodash';
import * as t from 'io-ts';
import { ordString } from 'fp-ts/lib/Ord';
import { toArray } from 'fp-ts/lib/Set';
import { isLeft, isRight } from 'fp-ts/lib/Either';
import { ErrorReporter } from './error_reporter';
import { ALL_DATA_TYPES, PARAMETERS_DEFINITION } from '../constants';
import { FieldMeta } from '../types';
import { getFieldMeta } from '../lib';
@ -72,7 +77,7 @@ const validateParameter = (parameter: string, value: any): boolean => {
const parameterSchema = (PARAMETERS_DEFINITION as any)[parameter]!.schema;
if (parameterSchema) {
return Boolean(Joi.validate(value, parameterSchema).error) === false;
return isRight(parameterSchema.decode(value));
}
// Fallback, if no schema defined for the parameter (this should not happen in theory)
@ -192,54 +197,55 @@ export const validateProperties = (properties = {}): PropertiesValidatorResponse
* Single source of truth to validate the *configuration* of the mappings.
* Whenever a user loads a JSON object it will be validate against this Joi schema.
*/
export const mappingsConfigurationSchema = Joi.object().keys({
dynamic: Joi.any().valid([true, false, 'strict']),
date_detection: Joi.boolean().strict(),
numeric_detection: Joi.boolean().strict(),
dynamic_date_formats: Joi.array().items(Joi.string()),
_source: Joi.object().keys({
enabled: Joi.boolean().strict(),
includes: Joi.array().items(Joi.string()),
excludes: Joi.array().items(Joi.string()),
export const mappingsConfigurationSchema = t.partial({
dynamic: t.union([t.literal(true), t.literal(false), t.literal('strict')]),
date_detection: t.boolean,
numeric_detection: t.boolean,
dynamic_date_formats: t.array(t.string),
_source: t.partial({
enabled: t.boolean,
includes: t.array(t.string),
excludes: t.array(t.string),
}),
_meta: Joi.object(),
_routing: Joi.object().keys({
required: Joi.boolean().strict(),
_meta: t.UnknownRecord,
_routing: t.partial({
required: t.boolean,
}),
});
const mappingsConfigurationSchemaKeys = Object.keys(mappingsConfigurationSchema.props);
const validateMappingsConfiguration = (
mappingsConfiguration: any
): { value: any; errors: MappingsValidationError[] } => {
// Array to keep track of invalid configuration parameters.
const configurationRemoved: string[] = [];
const configurationRemoved: Set<string> = new Set();
const { value: parsedConfiguration, error: configurationError } = Joi.validate(
mappingsConfiguration,
mappingsConfigurationSchema,
{
stripUnknown: true,
abortEarly: false,
}
);
let copyOfMappingsConfig = { ...mappingsConfiguration };
const result = mappingsConfigurationSchema.decode(mappingsConfiguration);
if (configurationError) {
if (isLeft(result)) {
/**
* To keep the logic simple we will strip out the parameters that contain errors
*/
configurationError.details.forEach(error => {
const errors = ErrorReporter.report(result);
errors.forEach(error => {
const configurationName = error.path[0];
configurationRemoved.push(configurationName);
delete parsedConfiguration[configurationName];
configurationRemoved.add(configurationName);
delete copyOfMappingsConfig[configurationName];
});
}
const errors: MappingsValidationError[] = configurationRemoved.map(configName => ({
code: 'ERR_CONFIG',
configName,
}));
copyOfMappingsConfig = pick(copyOfMappingsConfig, mappingsConfigurationSchemaKeys);
return { value: parsedConfiguration, errors };
const errors: MappingsValidationError[] = toArray<string>(ordString)(configurationRemoved)
.map(configName => ({
code: 'ERR_CONFIG',
configName,
}))
.sort((a, b) => a.configName.localeCompare(b.configName)) as MappingsValidationError[];
return { value: copyOfMappingsConfig, errors };
};
export const validateMappings = (mappings: any = {}): MappingsValidatorResponse => {