[Mappings Editor] Remove Joi (#54913)
This commit is contained in:
parent
5d4cb4767e
commit
781ef7a2c0
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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),
|
||||
};
|
|
@ -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' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 => {
|
||||
|
|
Loading…
Reference in a new issue