mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Security Solutions] (Phase 1) Copies io-ts shared utilities into kibana/packages (#98999)
## Summary We are removing duplicated code in sections of plugins into the kibana/packages folder. This is phase 1 of 4+ where: Phase 1: Lift and shift the io-ts code into `kibana/packages/kbn-securitysolution-io-ts-utils` Phase 2: Deprecate the utils across plugins any copied code Phase 3: Replace the deprecated types with the ones in here where we can. [Strangle pattern](https://martinfowler.com/bliki/StranglerFigApplication.html) Phase 4+: (potentially) consolidating any duplication or everything altogether with the `kbn-io-ts-utils` project ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
9e2e8b9f19
commit
bcde9e2b15
182 changed files with 8279 additions and 0 deletions
|
@ -76,6 +76,7 @@ yarn kbn watch-bazel
|
|||
- @kbn/expect
|
||||
- @kbn/legacy-logging
|
||||
- @kbn/logging
|
||||
- @kbn/securitysolution-io-ts-utils
|
||||
- @kbn/std
|
||||
- @kbn/tinymath
|
||||
- @kbn/utility-types
|
||||
|
|
|
@ -135,6 +135,7 @@
|
|||
"@kbn/legacy-logging": "link:bazel-bin/packages/kbn-legacy-logging/npm_module",
|
||||
"@kbn/logging": "link:bazel-bin/packages/kbn-logging/npm_module",
|
||||
"@kbn/monaco": "link:packages/kbn-monaco",
|
||||
"@kbn/securitysolution-io-ts-utils": "link:bazel-bin/packages/kbn-securitysolution-io-ts-utils/npm_module",
|
||||
"@kbn/server-http-tools": "link:packages/kbn-server-http-tools",
|
||||
"@kbn/server-route-repository": "link:packages/kbn-server-route-repository",
|
||||
"@kbn/std": "link:bazel-bin/packages/kbn-std/npm_module",
|
||||
|
|
|
@ -18,6 +18,7 @@ filegroup(
|
|||
"//packages/kbn-expect:build",
|
||||
"//packages/kbn-legacy-logging:build",
|
||||
"//packages/kbn-logging:build",
|
||||
"//packages/kbn-securitysolution-io-ts-utils:build",
|
||||
"//packages/kbn-std:build",
|
||||
"//packages/kbn-tinymath:build",
|
||||
"//packages/kbn-utility-types:build",
|
||||
|
|
92
packages/kbn-securitysolution-io-ts-utils/BUILD.bazel
Normal file
92
packages/kbn-securitysolution-io-ts-utils/BUILD.bazel
Normal file
|
@ -0,0 +1,92 @@
|
|||
load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm")
|
||||
|
||||
PKG_BASE_NAME = "kbn-securitysolution-io-ts-utils"
|
||||
PKG_REQUIRE_NAME = "@kbn/securitysolution-io-ts-utils"
|
||||
|
||||
SOURCE_FILES = glob(
|
||||
[
|
||||
"src/**/*.ts",
|
||||
],
|
||||
exclude = [
|
||||
"**/*.test.*",
|
||||
"**/*.mock.*"
|
||||
],
|
||||
)
|
||||
|
||||
SRCS = SOURCE_FILES
|
||||
|
||||
filegroup(
|
||||
name = "srcs",
|
||||
srcs = SRCS,
|
||||
)
|
||||
|
||||
NPM_MODULE_EXTRA_FILES = [
|
||||
"package.json",
|
||||
"README.md",
|
||||
]
|
||||
|
||||
SRC_DEPS = [
|
||||
"//packages/elastic-datemath",
|
||||
"@npm//fp-ts",
|
||||
"@npm//io-ts",
|
||||
"@npm//lodash",
|
||||
"@npm//moment",
|
||||
"@npm//tslib",
|
||||
"@npm//uuid",
|
||||
]
|
||||
|
||||
TYPES_DEPS = [
|
||||
"@npm//@types/flot",
|
||||
"@npm//@types/jest",
|
||||
"@npm//@types/lodash",
|
||||
"@npm//@types/node",
|
||||
"@npm//@types/uuid"
|
||||
]
|
||||
|
||||
DEPS = SRC_DEPS + TYPES_DEPS
|
||||
|
||||
ts_config(
|
||||
name = "tsconfig",
|
||||
src = "tsconfig.json",
|
||||
deps = [
|
||||
"//:tsconfig.base.json",
|
||||
],
|
||||
)
|
||||
|
||||
ts_project(
|
||||
name = "tsc",
|
||||
args = ['--pretty'],
|
||||
srcs = SRCS,
|
||||
deps = DEPS,
|
||||
declaration = True,
|
||||
declaration_map = True,
|
||||
incremental = True,
|
||||
out_dir = "target",
|
||||
source_map = True,
|
||||
root_dir = "src",
|
||||
tsconfig = ":tsconfig",
|
||||
)
|
||||
|
||||
js_library(
|
||||
name = PKG_BASE_NAME,
|
||||
srcs = NPM_MODULE_EXTRA_FILES,
|
||||
deps = [":tsc"] + DEPS,
|
||||
package_name = PKG_REQUIRE_NAME,
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
pkg_npm(
|
||||
name = "npm_module",
|
||||
deps = [
|
||||
":%s" % PKG_BASE_NAME,
|
||||
]
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "build",
|
||||
srcs = [
|
||||
":npm_module",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
10
packages/kbn-securitysolution-io-ts-utils/README.md
Normal file
10
packages/kbn-securitysolution-io-ts-utils/README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# kbn-securitysolution-io-ts-utils
|
||||
|
||||
Temporary location for all the io-ts-utils from security solutions. This is a lift-and-shift, where
|
||||
we are moving them here for phase 1.
|
||||
|
||||
Phase 2 is deprecating across plugins any copied code or sharing of io-ts utils that are now in here.
|
||||
|
||||
Phase 3 is replacing those deprecated types with the ones in here.
|
||||
|
||||
Phase 4+ is (potentially) consolidating any duplication or everything altogether with the `kbn-io-ts-utils` project
|
13
packages/kbn-securitysolution-io-ts-utils/jest.config.js
Normal file
13
packages/kbn-securitysolution-io-ts-utils/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-securitysolution-io-ts-utils'],
|
||||
};
|
9
packages/kbn-securitysolution-io-ts-utils/package.json
Normal file
9
packages/kbn-securitysolution-io-ts-utils/package.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "@kbn/securitysolution-io-ts-utils",
|
||||
"version": "1.0.0",
|
||||
"description": "io ts utilities and types to be shared with plugins from the security solution project",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||
"main": "./target/index.js",
|
||||
"types": "./target/index.d.ts",
|
||||
"private": true
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { saved_object_attributes } from '../saved_object_attributes';
|
||||
|
||||
/**
|
||||
* Params is an "object", since it is a type of AlertActionParams which is action templates.
|
||||
* @see x-pack/plugins/alerting/common/alert.ts
|
||||
*/
|
||||
export const action_group = t.string;
|
||||
export const action_id = t.string;
|
||||
export const action_action_type_id = t.string;
|
||||
export const action_params = saved_object_attributes;
|
||||
|
||||
export const action = t.exact(
|
||||
t.type({
|
||||
group: action_group,
|
||||
id: action_id,
|
||||
action_type_id: action_action_type_id,
|
||||
params: action_params,
|
||||
})
|
||||
);
|
||||
|
||||
export const actions = t.array(action);
|
||||
export type Actions = t.TypeOf<typeof actions>;
|
||||
|
||||
export const actionsCamel = t.array(
|
||||
t.exact(
|
||||
t.type({
|
||||
group: action_group,
|
||||
id: action_id,
|
||||
actionTypeId: action_action_type_id,
|
||||
params: action_params,
|
||||
})
|
||||
)
|
||||
);
|
||||
export type ActionsCamel = t.TypeOf<typeof actions>;
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
export const ENTRY_VALUE = 'some host name';
|
||||
export const FIELD = 'host.name';
|
||||
export const MATCH = 'match';
|
||||
export const MATCH_ANY = 'match_any';
|
||||
export const OPERATOR = 'included';
|
||||
export const NESTED = 'nested';
|
||||
export const NESTED_FIELD = 'parent.field';
|
||||
export const LIST_ID = 'some-list-id';
|
||||
export const LIST = 'list';
|
||||
export const TYPE = 'ip';
|
||||
export const EXISTS = 'exists';
|
||||
export const WILDCARD = 'wildcard';
|
||||
export const USER = 'some user';
|
||||
export const DATE_NOW = '2020-04-20T15:25:31.830Z';
|
||||
|
||||
// Exception List specific
|
||||
export const ID = 'uuid_here';
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This ID is used for _both_ the Saved Object ID and for the list_id
|
||||
* for the single global space agnostic endpoint list
|
||||
* TODO: Create a kbn-securitysolution-constants and add this to it.
|
||||
* @deprecated Use the ENDPOINT_LIST_ID from the kbn-securitysolution-constants.
|
||||
*/
|
||||
export const ENDPOINT_LIST_ID = 'endpoint_list';
|
||||
|
||||
/**
|
||||
* TODO: Create a kbn-securitysolution-constants and add this to it.
|
||||
* @deprecated Use the DEFAULT_MAX_SIGNALS from the kbn-securitysolution-constants.
|
||||
*/
|
||||
export const DEFAULT_MAX_SIGNALS = 100;
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
export const created_at = t.string; // TODO: Make this into an ISO Date string check
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
export const created_by = t.string;
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultVersionNumber } from '../default_version_number';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_version_number', () => {
|
||||
test('it should validate a version number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultVersionNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate a 0', () => {
|
||||
const payload = 0;
|
||||
const decoded = DefaultVersionNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "0" supplied to "DefaultVersionNumber"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate a -1', () => {
|
||||
const payload = -1;
|
||||
const decoded = DefaultVersionNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "-1" supplied to "DefaultVersionNumber"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate a string', () => {
|
||||
const payload = '5';
|
||||
const decoded = DefaultVersionNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultVersionNumber"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default of 1', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultVersionNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { version, Version } from '../version';
|
||||
|
||||
/**
|
||||
* Types the DefaultVersionNumber as:
|
||||
* - If null or undefined, then a default of the number 1 will be used
|
||||
*/
|
||||
export const DefaultVersionNumber = new t.Type<Version, Version | undefined, unknown>(
|
||||
'DefaultVersionNumber',
|
||||
version.is,
|
||||
(input, context): Either<t.Errors, Version> =>
|
||||
input == null ? t.success(1) : version.validate(input, context),
|
||||
t.identity
|
||||
);
|
||||
|
||||
export type DefaultVersionNumberDecoded = t.TypeOf<typeof DefaultVersionNumber>;
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { actions, Actions } from '../actions';
|
||||
|
||||
export const DefaultActionsArray = new t.Type<Actions, Actions | undefined, unknown>(
|
||||
'DefaultActionsArray',
|
||||
actions.is,
|
||||
(input, context): Either<t.Errors, Actions> =>
|
||||
input == null ? t.success([]) : actions.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultArray } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
const testSchema = t.keyof({
|
||||
valid: true,
|
||||
also_valid: true,
|
||||
});
|
||||
type TestSchema = t.TypeOf<typeof testSchema>;
|
||||
|
||||
const defaultArraySchema = DefaultArray(testSchema);
|
||||
|
||||
describe('default_array', () => {
|
||||
test('it should validate an empty array', () => {
|
||||
const payload: string[] = [];
|
||||
const decoded = defaultArraySchema.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of testSchema', () => {
|
||||
const payload: TestSchema[] = ['valid'];
|
||||
const decoded = defaultArraySchema.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of valid testSchema strings', () => {
|
||||
const payload = ['valid', 'also_valid'];
|
||||
const decoded = defaultArraySchema.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate an array with a number', () => {
|
||||
const payload = ['valid', 123];
|
||||
const decoded = defaultArraySchema.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "123" supplied to "DefaultArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate an array with an invalid string', () => {
|
||||
const payload = ['valid', 'invalid'];
|
||||
const decoded = defaultArraySchema.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "invalid" supplied to "DefaultArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default array entry', () => {
|
||||
const payload = null;
|
||||
const decoded = defaultArraySchema.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
/**
|
||||
* Types the DefaultArray<C> as:
|
||||
* - If undefined, then a default array will be set
|
||||
* - If an array is sent in, then the array will be validated to ensure all elements are type C
|
||||
*/
|
||||
export const DefaultArray = <C extends t.Mixed>(codec: C) => {
|
||||
const arrType = t.array(codec);
|
||||
type ArrType = t.TypeOf<typeof arrType>;
|
||||
return new t.Type<ArrType, ArrType | undefined, unknown>(
|
||||
'DefaultArray',
|
||||
arrType.is,
|
||||
(input, context): Either<t.Errors, ArrType> =>
|
||||
input == null ? t.success([]) : arrType.validate(input, context),
|
||||
t.identity
|
||||
);
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultBooleanFalse } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_boolean_false', () => {
|
||||
test('it should validate a boolean false', () => {
|
||||
const payload = false;
|
||||
const decoded = DefaultBooleanFalse.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a boolean true', () => {
|
||||
const payload = true;
|
||||
const decoded = DefaultBooleanFalse.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate a number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultBooleanFalse.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultBooleanFalse"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default false', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultBooleanFalse.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
/**
|
||||
* Types the DefaultBooleanFalse as:
|
||||
* - If null or undefined, then a default false will be set
|
||||
*/
|
||||
export const DefaultBooleanFalse = new t.Type<boolean, boolean | undefined, unknown>(
|
||||
'DefaultBooleanFalse',
|
||||
t.boolean.is,
|
||||
(input, context): Either<t.Errors, boolean> =>
|
||||
input == null ? t.success(false) : t.boolean.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultBooleanTrue } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_boolean_true', () => {
|
||||
test('it should validate a boolean false', () => {
|
||||
const payload = false;
|
||||
const decoded = DefaultBooleanTrue.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a boolean true', () => {
|
||||
const payload = true;
|
||||
const decoded = DefaultBooleanTrue.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate a number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultBooleanTrue.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultBooleanTrue"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default true', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultBooleanTrue.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
/**
|
||||
* Types the DefaultBooleanTrue as:
|
||||
* - If null or undefined, then a default true will be set
|
||||
*/
|
||||
export const DefaultBooleanTrue = new t.Type<boolean, boolean | undefined, unknown>(
|
||||
'DefaultBooleanTrue',
|
||||
t.boolean.is,
|
||||
(input, context): Either<t.Errors, boolean> =>
|
||||
input == null ? t.success(true) : t.boolean.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultEmptyString } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_empty_string', () => {
|
||||
test('it should validate a regular string', () => {
|
||||
const payload = 'some string';
|
||||
const decoded = DefaultEmptyString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate a number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultEmptyString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultEmptyString"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default of ""', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultEmptyString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual('');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
/**
|
||||
* Types the DefaultEmptyString as:
|
||||
* - If null or undefined, then a default of an empty string "" will be used
|
||||
*/
|
||||
export const DefaultEmptyString = new t.Type<string, string | undefined, unknown>(
|
||||
'DefaultEmptyString',
|
||||
t.string.is,
|
||||
(input, context): Either<t.Errors, string> =>
|
||||
input == null ? t.success('') : t.string.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultExportFileName } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_export_file_name', () => {
|
||||
test('it should validate a regular string', () => {
|
||||
const payload = 'some string';
|
||||
const decoded = DefaultExportFileName.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate a number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultExportFileName.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultExportFileName"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default of "export.ndjson"', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultExportFileName.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual('export.ndjson');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
/**
|
||||
* Types the DefaultExportFileName as:
|
||||
* - If null or undefined, then a default of "export.ndjson" will be used
|
||||
*/
|
||||
export const DefaultExportFileName = new t.Type<string, string | undefined, unknown>(
|
||||
'DefaultExportFileName',
|
||||
t.string.is,
|
||||
(input, context): Either<t.Errors, string> =>
|
||||
input == null ? t.success('export.ndjson') : t.string.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultFromString } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_from_string', () => {
|
||||
test('it should validate a from string', () => {
|
||||
const payload = 'now-20m';
|
||||
const decoded = DefaultFromString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate a number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultFromString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultFromString"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default of "now-6m"', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultFromString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual('now-6m');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { from } from '../from';
|
||||
|
||||
/**
|
||||
* Types the DefaultFromString as:
|
||||
* - If null or undefined, then a default of the string "now-6m" will be used
|
||||
*/
|
||||
export const DefaultFromString = new t.Type<string, string | undefined, unknown>(
|
||||
'DefaultFromString',
|
||||
t.string.is,
|
||||
(input, context): Either<t.Errors, string> => {
|
||||
if (input == null) {
|
||||
return t.success('now-6m');
|
||||
}
|
||||
return from.validate(input, context);
|
||||
},
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultIntervalString } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_interval_string', () => {
|
||||
test('it should validate a interval string', () => {
|
||||
const payload = '20m';
|
||||
const decoded = DefaultIntervalString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate a number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultIntervalString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultIntervalString"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default of "5m"', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultIntervalString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual('5m');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
/**
|
||||
* Types the DefaultIntervalString as:
|
||||
* - If null or undefined, then a default of the string "5m" will be used
|
||||
*/
|
||||
export const DefaultIntervalString = new t.Type<string, string | undefined, unknown>(
|
||||
'DefaultIntervalString',
|
||||
t.string.is,
|
||||
(input, context): Either<t.Errors, string> =>
|
||||
input == null ? t.success('5m') : t.string.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { Language } from '../language';
|
||||
import { DefaultLanguageString } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_language_string', () => {
|
||||
test('it should validate a string', () => {
|
||||
const payload: Language = 'lucene';
|
||||
const decoded = DefaultLanguageString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate a number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultLanguageString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultLanguageString"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default of "kuery"', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultLanguageString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual('kuery');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { language } from '../language';
|
||||
|
||||
/**
|
||||
* Types the DefaultLanguageString as:
|
||||
* - If null or undefined, then a default of the string "kuery" will be used
|
||||
*/
|
||||
export const DefaultLanguageString = new t.Type<string, string | undefined, unknown>(
|
||||
'DefaultLanguageString',
|
||||
t.string.is,
|
||||
(input, context): Either<t.Errors, string> =>
|
||||
input == null ? t.success('kuery') : language.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultMaxSignalsNumber } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
import { DEFAULT_MAX_SIGNALS } from '../constants';
|
||||
|
||||
describe('default_from_string', () => {
|
||||
test('it should validate a max signal number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultMaxSignalsNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate a string', () => {
|
||||
const payload = '5';
|
||||
const decoded = DefaultMaxSignalsNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultMaxSignals"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate a zero', () => {
|
||||
const payload = 0;
|
||||
const decoded = DefaultMaxSignalsNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "0" supplied to "DefaultMaxSignals"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate a negative number', () => {
|
||||
const payload = -1;
|
||||
const decoded = DefaultMaxSignalsNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "-1" supplied to "DefaultMaxSignals"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default of DEFAULT_MAX_SIGNALS', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultMaxSignalsNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(DEFAULT_MAX_SIGNALS);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { max_signals } from '../max_signals';
|
||||
import { DEFAULT_MAX_SIGNALS } from '../constants';
|
||||
|
||||
/**
|
||||
* Types the default max signal:
|
||||
* - Natural Number (positive integer and not a float),
|
||||
* - greater than 1
|
||||
* - If undefined then it will use DEFAULT_MAX_SIGNALS (100) as the default
|
||||
*/
|
||||
export const DefaultMaxSignalsNumber = new t.Type<number, number | undefined, unknown>(
|
||||
'DefaultMaxSignals',
|
||||
t.number.is,
|
||||
(input, context): Either<t.Errors, number> => {
|
||||
return input == null ? t.success(DEFAULT_MAX_SIGNALS) : max_signals.validate(input, context);
|
||||
},
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultPage } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_page', () => {
|
||||
test('it should validate a regular number greater than zero', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a string of a number', () => {
|
||||
const payload = '5';
|
||||
const decoded = DefaultPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(5);
|
||||
});
|
||||
|
||||
test('it should not validate a junk string', () => {
|
||||
const payload = 'invalid-string';
|
||||
const decoded = DefaultPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "NaN" supplied to "DefaultPerPage"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate an empty string', () => {
|
||||
const payload = '';
|
||||
const decoded = DefaultPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "NaN" supplied to "DefaultPerPage"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate a zero', () => {
|
||||
const payload = 0;
|
||||
const decoded = DefaultPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "0" supplied to "DefaultPerPage"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate a negative number', () => {
|
||||
const payload = -1;
|
||||
const decoded = DefaultPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "-1" supplied to "DefaultPerPage"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default of 20', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { PositiveIntegerGreaterThanZero } from '../positive_integer_greater_than_zero';
|
||||
|
||||
/**
|
||||
* Types the DefaultPerPage as:
|
||||
* - If a string this will convert the string to a number
|
||||
* - If null or undefined, then a default of 1 will be used
|
||||
* - If the number is 0 or less this will not validate as it has to be a positive number greater than zero
|
||||
*/
|
||||
export const DefaultPage = new t.Type<number, number | undefined, unknown>(
|
||||
'DefaultPerPage',
|
||||
t.number.is,
|
||||
(input, context): Either<t.Errors, number> => {
|
||||
if (input == null) {
|
||||
return t.success(1);
|
||||
} else if (typeof input === 'string') {
|
||||
return PositiveIntegerGreaterThanZero.validate(parseInt(input, 10), context);
|
||||
} else {
|
||||
return PositiveIntegerGreaterThanZero.validate(input, context);
|
||||
}
|
||||
},
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultPerPage } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_per_page', () => {
|
||||
test('it should validate a regular number greater than zero', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultPerPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a string of a number', () => {
|
||||
const payload = '5';
|
||||
const decoded = DefaultPerPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(5);
|
||||
});
|
||||
|
||||
test('it should not validate a junk string', () => {
|
||||
const payload = 'invalid-string';
|
||||
const decoded = DefaultPerPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "NaN" supplied to "DefaultPerPage"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate an empty string', () => {
|
||||
const payload = '';
|
||||
const decoded = DefaultPerPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "NaN" supplied to "DefaultPerPage"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate a zero', () => {
|
||||
const payload = 0;
|
||||
const decoded = DefaultPerPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "0" supplied to "DefaultPerPage"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate a negative number', () => {
|
||||
const payload = -1;
|
||||
const decoded = DefaultPerPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "-1" supplied to "DefaultPerPage"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default of 20', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultPerPage.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(20);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { PositiveIntegerGreaterThanZero } from '../positive_integer_greater_than_zero';
|
||||
|
||||
/**
|
||||
* Types the DefaultPerPage as:
|
||||
* - If a string this will convert the string to a number
|
||||
* - If null or undefined, then a default of 20 will be used
|
||||
* - If the number is 0 or less this will not validate as it has to be a positive number greater than zero
|
||||
*/
|
||||
export const DefaultPerPage = new t.Type<number, number | undefined, unknown>(
|
||||
'DefaultPerPage',
|
||||
t.number.is,
|
||||
(input, context): Either<t.Errors, number> => {
|
||||
if (input == null) {
|
||||
return t.success(20);
|
||||
} else if (typeof input === 'string') {
|
||||
return PositiveIntegerGreaterThanZero.validate(parseInt(input, 10), context);
|
||||
} else {
|
||||
return PositiveIntegerGreaterThanZero.validate(input, context);
|
||||
}
|
||||
},
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { RiskScoreMapping, risk_score_mapping } from '../risk_score_mapping';
|
||||
|
||||
/**
|
||||
* Types the DefaultStringArray as:
|
||||
* - If null or undefined, then a default risk_score_mapping array will be set
|
||||
*/
|
||||
export const DefaultRiskScoreMappingArray = new t.Type<
|
||||
RiskScoreMapping,
|
||||
RiskScoreMapping | undefined,
|
||||
unknown
|
||||
>(
|
||||
'DefaultRiskScoreMappingArray',
|
||||
risk_score_mapping.is,
|
||||
(input, context): Either<t.Errors, RiskScoreMapping> =>
|
||||
input == null ? t.success([]) : risk_score_mapping.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { SeverityMapping, severity_mapping } from '../severity_mapping';
|
||||
|
||||
/**
|
||||
* Types the DefaultStringArray as:
|
||||
* - If null or undefined, then a default severity_mapping array will be set
|
||||
*/
|
||||
export const DefaultSeverityMappingArray = new t.Type<
|
||||
SeverityMapping,
|
||||
SeverityMapping | undefined,
|
||||
unknown
|
||||
>(
|
||||
'DefaultSeverityMappingArray',
|
||||
severity_mapping.is,
|
||||
(input, context): Either<t.Errors, SeverityMapping> =>
|
||||
input == null ? t.success([]) : severity_mapping.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultStringArray } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_string_array', () => {
|
||||
test('it should validate an empty array', () => {
|
||||
const payload: string[] = [];
|
||||
const decoded = DefaultStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of strings', () => {
|
||||
const payload = ['value 1', 'value 2'];
|
||||
const decoded = DefaultStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate an array with a number', () => {
|
||||
const payload = ['value 1', 5];
|
||||
const decoded = DefaultStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultStringArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default array entry', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
/**
|
||||
* Types the DefaultStringArray as:
|
||||
* - If undefined, then a default array will be set
|
||||
* - If an array is sent in, then the array will be validated to ensure all elements are a string
|
||||
*/
|
||||
export const DefaultStringArray = new t.Type<string[], string[] | undefined, unknown>(
|
||||
'DefaultStringArray',
|
||||
t.array(t.string).is,
|
||||
(input, context): Either<t.Errors, string[]> =>
|
||||
input == null ? t.success([]) : t.array(t.string).validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultStringBooleanFalse } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_string_boolean_false', () => {
|
||||
test('it should validate a boolean false', () => {
|
||||
const payload = false;
|
||||
const decoded = DefaultStringBooleanFalse.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a boolean true', () => {
|
||||
const payload = true;
|
||||
const decoded = DefaultStringBooleanFalse.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate a number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultStringBooleanFalse.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultStringBooleanFalse"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default false', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultStringBooleanFalse.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(false);
|
||||
});
|
||||
|
||||
test('it should return a default false when given a string of "false"', () => {
|
||||
const payload = 'false';
|
||||
const decoded = DefaultStringBooleanFalse.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(false);
|
||||
});
|
||||
|
||||
test('it should return a default true when given a string of "true"', () => {
|
||||
const payload = 'true';
|
||||
const decoded = DefaultStringBooleanFalse.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(true);
|
||||
});
|
||||
|
||||
test('it should return a default true when given a string of "TruE"', () => {
|
||||
const payload = 'TruE';
|
||||
const decoded = DefaultStringBooleanFalse.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(true);
|
||||
});
|
||||
|
||||
test('it should not work with a string of junk "junk"', () => {
|
||||
const payload = 'junk';
|
||||
const decoded = DefaultStringBooleanFalse.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "junk" supplied to "DefaultStringBooleanFalse"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not work with an empty string', () => {
|
||||
const payload = '';
|
||||
const decoded = DefaultStringBooleanFalse.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "" supplied to "DefaultStringBooleanFalse"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
/**
|
||||
* Types the DefaultStringBooleanFalse as:
|
||||
* - If a string this will convert the string to a boolean
|
||||
* - If null or undefined, then a default false will be set
|
||||
*/
|
||||
export const DefaultStringBooleanFalse = new t.Type<boolean, boolean | undefined | string, unknown>(
|
||||
'DefaultStringBooleanFalse',
|
||||
t.boolean.is,
|
||||
(input, context): Either<t.Errors, boolean> => {
|
||||
if (input == null) {
|
||||
return t.success(false);
|
||||
} else if (typeof input === 'string' && input.toLowerCase() === 'true') {
|
||||
return t.success(true);
|
||||
} else if (typeof input === 'string' && input.toLowerCase() === 'false') {
|
||||
return t.success(false);
|
||||
} else {
|
||||
return t.boolean.validate(input, context);
|
||||
}
|
||||
},
|
||||
t.identity
|
||||
);
|
||||
|
||||
export type DefaultStringBooleanFalseC = typeof DefaultStringBooleanFalse;
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { Threats } from '../threat';
|
||||
import { DefaultThreatArray } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_threat_null', () => {
|
||||
test('it should validate an empty array', () => {
|
||||
const payload: Threats = [];
|
||||
const decoded = DefaultThreatArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array of threats', () => {
|
||||
const payload: Threats = [
|
||||
{
|
||||
framework: 'MITRE ATTACK',
|
||||
technique: [{ reference: 'https://test.com', name: 'Audio Capture', id: 'T1123' }],
|
||||
tactic: { reference: 'https://test.com', name: 'Collection', id: 'TA000999' },
|
||||
},
|
||||
];
|
||||
const decoded = DefaultThreatArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate an array with a number', () => {
|
||||
const payload = [
|
||||
{
|
||||
framework: 'MITRE ATTACK',
|
||||
technique: [{ reference: 'https://test.com', name: 'Audio Capture', id: 'T1123' }],
|
||||
tactic: { reference: 'https://test.com', name: 'Collection', id: 'TA000999' },
|
||||
},
|
||||
5,
|
||||
];
|
||||
const decoded = DefaultThreatArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultThreatArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default empty array if not provided a value', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultThreatArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { threats, Threats } from '../threat';
|
||||
|
||||
/**
|
||||
* Types the DefaultThreatArray as:
|
||||
* - If null or undefined, then an empty array will be set
|
||||
*/
|
||||
export const DefaultThreatArray = new t.Type<Threats, Threats | undefined, unknown>(
|
||||
'DefaultThreatArray',
|
||||
threats.is,
|
||||
(input, context): Either<t.Errors, Threats> =>
|
||||
input == null ? t.success([]) : threats.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { Throttle } from '../throttle';
|
||||
import { DefaultThrottleNull } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_throttle_null', () => {
|
||||
test('it should validate a throttle string', () => {
|
||||
const payload: Throttle = 'some string';
|
||||
const decoded = DefaultThrottleNull.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate an array with a number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultThrottleNull.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultThreatNull"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default "null" if not provided a value', () => {
|
||||
const payload = undefined;
|
||||
const decoded = DefaultThrottleNull.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(null);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { throttle, ThrottleOrNull } from '../throttle';
|
||||
|
||||
/**
|
||||
* Types the DefaultThrottleNull as:
|
||||
* - If null or undefined, then a null will be set
|
||||
*/
|
||||
export const DefaultThrottleNull = new t.Type<ThrottleOrNull, ThrottleOrNull | undefined, unknown>(
|
||||
'DefaultThreatNull',
|
||||
throttle.is,
|
||||
(input, context): Either<t.Errors, ThrottleOrNull> =>
|
||||
input == null ? t.success(null) : throttle.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultToString } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_to_string', () => {
|
||||
test('it should validate a to string', () => {
|
||||
const payload = 'now-5m';
|
||||
const decoded = DefaultToString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate a number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultToString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultToString"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default of "now"', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultToString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual('now');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
/**
|
||||
* Types the DefaultToString as:
|
||||
* - If null or undefined, then a default of the string "now" will be used
|
||||
*/
|
||||
export const DefaultToString = new t.Type<string, string | undefined, unknown>(
|
||||
'DefaultToString',
|
||||
t.string.is,
|
||||
(input, context): Either<t.Errors, string> =>
|
||||
input == null ? t.success('now') : t.string.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultUuid } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_uuid', () => {
|
||||
test('it should validate a regular string', () => {
|
||||
const payload = '1';
|
||||
const decoded = DefaultUuid.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate a number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultUuid.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "DefaultUuid"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default of a uuid', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultUuid.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toMatch(
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import uuid from 'uuid';
|
||||
import { NonEmptyString } from '../non_empty_string';
|
||||
|
||||
/**
|
||||
* Types the DefaultUuid as:
|
||||
* - If null or undefined, then a default string uuid.v4() will be
|
||||
* created otherwise it will be checked just against an empty string
|
||||
*/
|
||||
export const DefaultUuid = new t.Type<string, string | undefined, unknown>(
|
||||
'DefaultUuid',
|
||||
t.string.is,
|
||||
(input, context): Either<t.Errors, string> =>
|
||||
input == null ? t.success(uuid.v4()) : NonEmptyString.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultVersionNumber } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('default_version_number', () => {
|
||||
test('it should validate a version number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultVersionNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate a 0', () => {
|
||||
const payload = 0;
|
||||
const decoded = DefaultVersionNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "0" supplied to "DefaultVersionNumber"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate a -1', () => {
|
||||
const payload = -1;
|
||||
const decoded = DefaultVersionNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "-1" supplied to "DefaultVersionNumber"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate a string', () => {
|
||||
const payload = '5';
|
||||
const decoded = DefaultVersionNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultVersionNumber"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default of 1', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultVersionNumber.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { version, Version } from '../version';
|
||||
|
||||
/**
|
||||
* Types the DefaultVersionNumber as:
|
||||
* - If null or undefined, then a default of the number 1 will be used
|
||||
*/
|
||||
export const DefaultVersionNumber = new t.Type<Version, Version | undefined, unknown>(
|
||||
'DefaultVersionNumber',
|
||||
version.is,
|
||||
(input, context): Either<t.Errors, Version> =>
|
||||
input == null ? t.success(1) : version.validate(input, context),
|
||||
t.identity
|
||||
);
|
||||
|
||||
export type DefaultVersionNumberDecoded = t.TypeOf<typeof DefaultVersionNumber>;
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
|
||||
export const description = t.string;
|
||||
export type Description = t.TypeOf<typeof description>;
|
||||
export const descriptionOrUndefined = t.union([description, t.undefined]);
|
||||
export type DescriptionOrUndefined = t.TypeOf<typeof descriptionOrUndefined>;
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { EmptyStringArray, EmptyStringArrayEncoded } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('empty_string_array', () => {
|
||||
test('it should validate "null" and create an empty array', () => {
|
||||
const payload: EmptyStringArrayEncoded = null;
|
||||
const decoded = EmptyStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([]);
|
||||
});
|
||||
|
||||
test('it should validate "undefined" and create an empty array', () => {
|
||||
const payload: EmptyStringArrayEncoded = undefined;
|
||||
const decoded = EmptyStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([]);
|
||||
});
|
||||
|
||||
test('it should validate a single value of "a" into an array of size 1 of ["a"]', () => {
|
||||
const payload: EmptyStringArrayEncoded = 'a';
|
||||
const decoded = EmptyStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(['a']);
|
||||
});
|
||||
|
||||
test('it should validate 2 values of "a,b" into an array of size 2 of ["a", "b"]', () => {
|
||||
const payload: EmptyStringArrayEncoded = 'a,b';
|
||||
const decoded = EmptyStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
test('it should validate 3 values of "a,b,c" into an array of size 3 of ["a", "b", "c"]', () => {
|
||||
const payload: EmptyStringArrayEncoded = 'a,b,c';
|
||||
const decoded = EmptyStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
test('it should FAIL validation of number', () => {
|
||||
const payload: number = 5;
|
||||
const decoded = EmptyStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "EmptyStringArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should validate 3 values of " a, b, c " into an array of size 3 of ["a", "b", "c"] even though they have spaces', () => {
|
||||
const payload: EmptyStringArrayEncoded = ' a, b, c ';
|
||||
const decoded = EmptyStringArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
/**
|
||||
* Types the EmptyStringArray as:
|
||||
* - A value that can be undefined, or null (which will be turned into an empty array)
|
||||
* - A comma separated string that can turn into an array by splitting on it
|
||||
* - Example input converted to output: undefined -> []
|
||||
* - Example input converted to output: null -> []
|
||||
* - Example input converted to output: "a,b,c" -> ["a", "b", "c"]
|
||||
*/
|
||||
export const EmptyStringArray = new t.Type<string[], string | undefined | null, unknown>(
|
||||
'EmptyStringArray',
|
||||
t.array(t.string).is,
|
||||
(input, context): Either<t.Errors, string[]> => {
|
||||
if (input == null) {
|
||||
return t.success([]);
|
||||
} else if (typeof input === 'string' && input.trim() !== '') {
|
||||
const arrayValues = input
|
||||
.trim()
|
||||
.split(',')
|
||||
.map((value) => value.trim());
|
||||
const emptyValueFound = arrayValues.some((value) => value === '');
|
||||
if (emptyValueFound) {
|
||||
return t.failure(input, context);
|
||||
} else {
|
||||
return t.success(arrayValues);
|
||||
}
|
||||
} else {
|
||||
return t.failure(input, context);
|
||||
}
|
||||
},
|
||||
String
|
||||
);
|
||||
|
||||
export type EmptyStringArrayEncoded = t.OutputOf<typeof EmptyStringArray>;
|
||||
export type EmptyStringArrayDecoded = t.TypeOf<typeof EmptyStringArray>;
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { left, right, Either } from 'fp-ts/lib/Either';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { exactCheck, findDifferencesRecursive } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('exact_check', () => {
|
||||
test('it returns an error if given extra object properties', () => {
|
||||
const someType = t.exact(
|
||||
t.type({
|
||||
a: t.string,
|
||||
})
|
||||
);
|
||||
const payload = { a: 'test', b: 'test' };
|
||||
const decoded = someType.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "b"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it returns an error if the data type is not as expected', () => {
|
||||
type UnsafeCastForTest = Either<
|
||||
t.Errors,
|
||||
{
|
||||
a: number;
|
||||
}
|
||||
>;
|
||||
|
||||
const someType = t.exact(
|
||||
t.type({
|
||||
a: t.string,
|
||||
})
|
||||
);
|
||||
|
||||
const payload = { a: 1 };
|
||||
const decoded = someType.decode(payload);
|
||||
const checked = exactCheck(payload, decoded as UnsafeCastForTest);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "1" supplied to "a"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it does NOT return an error if given normal object properties', () => {
|
||||
const someType = t.exact(
|
||||
t.type({
|
||||
a: t.string,
|
||||
})
|
||||
);
|
||||
const payload = { a: 'test' };
|
||||
const decoded = someType.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it will return an existing error and not validate', () => {
|
||||
const payload = { a: 'test' };
|
||||
const validationError: t.ValidationError = {
|
||||
value: 'Some existing error',
|
||||
context: [],
|
||||
message: 'some error',
|
||||
};
|
||||
const error: t.Errors = [validationError];
|
||||
const leftValue = left(error);
|
||||
const checked = exactCheck(payload, leftValue);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual(['some error']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it will work with a regular "right" payload without any decoding', () => {
|
||||
const payload = { a: 'test' };
|
||||
const rightValue = right(payload);
|
||||
const checked = exactCheck(payload, rightValue);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual({ a: 'test' });
|
||||
});
|
||||
|
||||
test('it will work with decoding a null payload when the schema expects a null', () => {
|
||||
const someType = t.union([
|
||||
t.exact(
|
||||
t.type({
|
||||
a: t.string,
|
||||
})
|
||||
),
|
||||
t.null,
|
||||
]);
|
||||
const payload = null;
|
||||
const decoded = someType.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = pipe(checked, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(null);
|
||||
});
|
||||
|
||||
test('it should find no differences recursively with two empty objects', () => {
|
||||
const difference = findDifferencesRecursive({}, {});
|
||||
expect(difference).toEqual([]);
|
||||
});
|
||||
|
||||
test('it should find a single difference with two objects with different keys', () => {
|
||||
const difference = findDifferencesRecursive({ a: 1 }, { b: 1 });
|
||||
expect(difference).toEqual(['a']);
|
||||
});
|
||||
|
||||
test('it should find a two differences with two objects with multiple different keys', () => {
|
||||
const difference = findDifferencesRecursive({ a: 1, c: 1 }, { b: 1 });
|
||||
expect(difference).toEqual(['a', 'c']);
|
||||
});
|
||||
|
||||
test('it should find no differences with two objects with the same keys', () => {
|
||||
const difference = findDifferencesRecursive({ a: 1, b: 1 }, { a: 1, b: 1 });
|
||||
expect(difference).toEqual([]);
|
||||
});
|
||||
|
||||
test('it should find a difference with two deep objects with different same keys', () => {
|
||||
const difference = findDifferencesRecursive({ a: 1, b: { c: 1 } }, { a: 1, b: { d: 1 } });
|
||||
expect(difference).toEqual(['c']);
|
||||
});
|
||||
|
||||
test('it should find a difference within an array', () => {
|
||||
const difference = findDifferencesRecursive({ a: 1, b: [{ c: 1 }] }, { a: 1, b: [{ a: 1 }] });
|
||||
expect(difference).toEqual(['c']);
|
||||
});
|
||||
|
||||
test('it should find a no difference when using arrays that are identical', () => {
|
||||
const difference = findDifferencesRecursive({ a: 1, b: [{ c: 1 }] }, { a: 1, b: [{ c: 1 }] });
|
||||
expect(difference).toEqual([]);
|
||||
});
|
||||
|
||||
test('it should find differences when one has an array and the other does not', () => {
|
||||
const difference = findDifferencesRecursive({ a: 1, b: [{ c: 1 }] }, { a: 1 });
|
||||
expect(difference).toEqual(['b', '[{"c":1}]']);
|
||||
});
|
||||
|
||||
test('it should find differences when one has an deep object and the other does not', () => {
|
||||
const difference = findDifferencesRecursive({ a: 1, b: { c: 1 } }, { a: 1 });
|
||||
expect(difference).toEqual(['b', '{"c":1}']);
|
||||
});
|
||||
|
||||
test('it should find differences when one has a deep object with multiple levels and the other does not', () => {
|
||||
const difference = findDifferencesRecursive({ a: 1, b: { c: { d: 1 } } }, { a: 1 });
|
||||
expect(difference).toEqual(['b', '{"c":{"d":1}}']);
|
||||
});
|
||||
|
||||
test('it tests two deep objects as the same with no key differences', () => {
|
||||
const difference = findDifferencesRecursive(
|
||||
{ a: 1, b: { c: { d: 1 } } },
|
||||
{ a: 1, b: { c: { d: 1 } } }
|
||||
);
|
||||
expect(difference).toEqual([]);
|
||||
});
|
||||
|
||||
test('it tests two deep objects with just one deep key difference', () => {
|
||||
const difference = findDifferencesRecursive(
|
||||
{ a: 1, b: { c: { d: 1 } } },
|
||||
{ a: 1, b: { c: { e: 1 } } }
|
||||
);
|
||||
expect(difference).toEqual(['d']);
|
||||
});
|
||||
|
||||
test('it should not find any differences when the original and decoded are both null', () => {
|
||||
const difference = findDifferencesRecursive(null, null);
|
||||
expect(difference).toEqual([]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { left, Either, fold, right } from 'fp-ts/lib/Either';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { isObject, get } from 'lodash/fp';
|
||||
|
||||
/**
|
||||
* Given an original object and a decoded object this will return an error
|
||||
* if and only if the original object has additional keys that the decoded
|
||||
* object does not have. If the original decoded already has an error, then
|
||||
* this will return the error as is and not continue.
|
||||
*
|
||||
* NOTE: You MUST use t.exact(...) for this to operate correctly as your schema
|
||||
* needs to remove additional keys before the compare
|
||||
*
|
||||
* You might not need this in the future if the below issue is solved:
|
||||
* https://github.com/gcanti/io-ts/issues/322
|
||||
*
|
||||
* @param original The original to check if it has additional keys
|
||||
* @param decoded The decoded either which has either an existing error or the
|
||||
* decoded object which could have additional keys stripped from it.
|
||||
*/
|
||||
export const exactCheck = <T>(
|
||||
original: unknown,
|
||||
decoded: Either<t.Errors, T>
|
||||
): Either<t.Errors, T> => {
|
||||
const onLeft = (errors: t.Errors): Either<t.Errors, T> => left(errors);
|
||||
const onRight = (decodedValue: T): Either<t.Errors, T> => {
|
||||
const differences = findDifferencesRecursive(original, decodedValue);
|
||||
if (differences.length !== 0) {
|
||||
const validationError: t.ValidationError = {
|
||||
value: differences,
|
||||
context: [],
|
||||
message: `invalid keys "${differences.join(',')}"`,
|
||||
};
|
||||
const error: t.Errors = [validationError];
|
||||
return left(error);
|
||||
} else {
|
||||
return right(decodedValue);
|
||||
}
|
||||
};
|
||||
return pipe(decoded, fold(onLeft, onRight));
|
||||
};
|
||||
|
||||
export const findDifferencesRecursive = <T>(original: unknown, decodedValue: T): string[] => {
|
||||
if (decodedValue === null && original === null) {
|
||||
// both the decodedValue and the original are null which indicates that they are equal
|
||||
// so do not report differences
|
||||
return [];
|
||||
} else if (decodedValue == null) {
|
||||
try {
|
||||
// It is null and painful when the original contains an object or an array
|
||||
// the the decoded value does not have.
|
||||
return [JSON.stringify(original)];
|
||||
} catch (err) {
|
||||
return ['circular reference'];
|
||||
}
|
||||
} else if (typeof original !== 'object' || original == null) {
|
||||
// We are not an object or null so do not report differences
|
||||
return [];
|
||||
} else {
|
||||
const decodedKeys = Object.keys(decodedValue);
|
||||
const differences = Object.keys(original).flatMap((originalKey) => {
|
||||
const foundKey = decodedKeys.some((key) => key === originalKey);
|
||||
const topLevelKey = foundKey ? [] : [originalKey];
|
||||
// I use lodash to cheat and get an any (not going to lie ;-))
|
||||
const valueObjectOrArrayOriginal = get(originalKey, original);
|
||||
const valueObjectOrArrayDecoded = get(originalKey, decodedValue);
|
||||
if (isObject(valueObjectOrArrayOriginal)) {
|
||||
return [
|
||||
...topLevelKey,
|
||||
...findDifferencesRecursive(valueObjectOrArrayOriginal, valueObjectOrArrayDecoded),
|
||||
];
|
||||
} else if (Array.isArray(valueObjectOrArrayOriginal)) {
|
||||
return [
|
||||
...topLevelKey,
|
||||
...valueObjectOrArrayOriginal.flatMap((arrayElement, index) =>
|
||||
findDifferencesRecursive(arrayElement, get(index, valueObjectOrArrayDecoded))
|
||||
),
|
||||
];
|
||||
} else {
|
||||
return topLevelKey;
|
||||
}
|
||||
});
|
||||
return differences;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { formatErrors } from '.';
|
||||
|
||||
describe('utils', () => {
|
||||
test('returns an empty error message string if there are no errors', () => {
|
||||
const errors: t.Errors = [];
|
||||
const output = formatErrors(errors);
|
||||
expect(output).toEqual([]);
|
||||
});
|
||||
|
||||
test('returns a single error message if given one', () => {
|
||||
const validationError: t.ValidationError = {
|
||||
value: 'Some existing error',
|
||||
context: [],
|
||||
message: 'some error',
|
||||
};
|
||||
const errors: t.Errors = [validationError];
|
||||
const output = formatErrors(errors);
|
||||
expect(output).toEqual(['some error']);
|
||||
});
|
||||
|
||||
test('returns a two error messages if given two', () => {
|
||||
const validationError1: t.ValidationError = {
|
||||
value: 'Some existing error 1',
|
||||
context: [],
|
||||
message: 'some error 1',
|
||||
};
|
||||
const validationError2: t.ValidationError = {
|
||||
value: 'Some existing error 2',
|
||||
context: [],
|
||||
message: 'some error 2',
|
||||
};
|
||||
const errors: t.Errors = [validationError1, validationError2];
|
||||
const output = formatErrors(errors);
|
||||
expect(output).toEqual(['some error 1', 'some error 2']);
|
||||
});
|
||||
|
||||
test('it filters out duplicate error messages', () => {
|
||||
const validationError1: t.ValidationError = {
|
||||
value: 'Some existing error 1',
|
||||
context: [],
|
||||
message: 'some error 1',
|
||||
};
|
||||
const validationError2: t.ValidationError = {
|
||||
value: 'Some existing error 1',
|
||||
context: [],
|
||||
message: 'some error 1',
|
||||
};
|
||||
const errors: t.Errors = [validationError1, validationError2];
|
||||
const output = formatErrors(errors);
|
||||
expect(output).toEqual(['some error 1']);
|
||||
});
|
||||
|
||||
test('will use message before context if it is set', () => {
|
||||
const context: t.Context = ([{ key: 'some string key' }] as unknown) as t.Context;
|
||||
const validationError1: t.ValidationError = {
|
||||
value: 'Some existing error 1',
|
||||
context,
|
||||
message: 'I should be used first',
|
||||
};
|
||||
const errors: t.Errors = [validationError1];
|
||||
const output = formatErrors(errors);
|
||||
expect(output).toEqual(['I should be used first']);
|
||||
});
|
||||
|
||||
test('will use context entry of a single string', () => {
|
||||
const context: t.Context = ([{ key: 'some string key' }] as unknown) as t.Context;
|
||||
const validationError1: t.ValidationError = {
|
||||
value: 'Some existing error 1',
|
||||
context,
|
||||
};
|
||||
const errors: t.Errors = [validationError1];
|
||||
const output = formatErrors(errors);
|
||||
expect(output).toEqual(['Invalid value "Some existing error 1" supplied to "some string key"']);
|
||||
});
|
||||
|
||||
test('will use two context entries of two strings', () => {
|
||||
const context: t.Context = ([
|
||||
{ key: 'some string key 1' },
|
||||
{ key: 'some string key 2' },
|
||||
] as unknown) as t.Context;
|
||||
const validationError1: t.ValidationError = {
|
||||
value: 'Some existing error 1',
|
||||
context,
|
||||
};
|
||||
const errors: t.Errors = [validationError1];
|
||||
const output = formatErrors(errors);
|
||||
expect(output).toEqual([
|
||||
'Invalid value "Some existing error 1" supplied to "some string key 1,some string key 2"',
|
||||
]);
|
||||
});
|
||||
|
||||
test('will filter out and not use any strings of numbers', () => {
|
||||
const context: t.Context = ([
|
||||
{ key: '5' },
|
||||
{ key: 'some string key 2' },
|
||||
] as unknown) as t.Context;
|
||||
const validationError1: t.ValidationError = {
|
||||
value: 'Some existing error 1',
|
||||
context,
|
||||
};
|
||||
const errors: t.Errors = [validationError1];
|
||||
const output = formatErrors(errors);
|
||||
expect(output).toEqual([
|
||||
'Invalid value "Some existing error 1" supplied to "some string key 2"',
|
||||
]);
|
||||
});
|
||||
|
||||
test('will filter out and not use null', () => {
|
||||
const context: t.Context = ([
|
||||
{ key: null },
|
||||
{ key: 'some string key 2' },
|
||||
] as unknown) as t.Context;
|
||||
const validationError1: t.ValidationError = {
|
||||
value: 'Some existing error 1',
|
||||
context,
|
||||
};
|
||||
const errors: t.Errors = [validationError1];
|
||||
const output = formatErrors(errors);
|
||||
expect(output).toEqual([
|
||||
'Invalid value "Some existing error 1" supplied to "some string key 2"',
|
||||
]);
|
||||
});
|
||||
|
||||
test('will filter out and not use empty strings', () => {
|
||||
const context: t.Context = ([
|
||||
{ key: '' },
|
||||
{ key: 'some string key 2' },
|
||||
] as unknown) as t.Context;
|
||||
const validationError1: t.ValidationError = {
|
||||
value: 'Some existing error 1',
|
||||
context,
|
||||
};
|
||||
const errors: t.Errors = [validationError1];
|
||||
const output = formatErrors(errors);
|
||||
expect(output).toEqual([
|
||||
'Invalid value "Some existing error 1" supplied to "some string key 2"',
|
||||
]);
|
||||
});
|
||||
|
||||
test('will use a name context if it cannot find a keyContext', () => {
|
||||
const context: t.Context = ([
|
||||
{ key: '' },
|
||||
{ key: '', type: { name: 'someName' } },
|
||||
] as unknown) as t.Context;
|
||||
const validationError1: t.ValidationError = {
|
||||
value: 'Some existing error 1',
|
||||
context,
|
||||
};
|
||||
const errors: t.Errors = [validationError1];
|
||||
const output = formatErrors(errors);
|
||||
expect(output).toEqual(['Invalid value "Some existing error 1" supplied to "someName"']);
|
||||
});
|
||||
|
||||
test('will return an empty string if name does not exist but type does', () => {
|
||||
const context: t.Context = ([{ key: '' }, { key: '', type: {} }] as unknown) as t.Context;
|
||||
const validationError1: t.ValidationError = {
|
||||
value: 'Some existing error 1',
|
||||
context,
|
||||
};
|
||||
const errors: t.Errors = [validationError1];
|
||||
const output = formatErrors(errors);
|
||||
expect(output).toEqual(['Invalid value "Some existing error 1" supplied to ""']);
|
||||
});
|
||||
|
||||
test('will stringify an error value', () => {
|
||||
const context: t.Context = ([
|
||||
{ key: '' },
|
||||
{ key: 'some string key 2' },
|
||||
] as unknown) as t.Context;
|
||||
const validationError1: t.ValidationError = {
|
||||
value: { foo: 'some error' },
|
||||
context,
|
||||
};
|
||||
const errors: t.Errors = [validationError1];
|
||||
const output = formatErrors(errors);
|
||||
expect(output).toEqual([
|
||||
'Invalid value "{"foo":"some error"}" supplied to "some string key 2"',
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { isObject } from 'lodash/fp';
|
||||
|
||||
export const formatErrors = (errors: t.Errors): string[] => {
|
||||
const err = errors.map((error) => {
|
||||
if (error.message != null) {
|
||||
return error.message;
|
||||
} else {
|
||||
const keyContext = error.context
|
||||
.filter(
|
||||
(entry) => entry.key != null && !Number.isInteger(+entry.key) && entry.key.trim() !== ''
|
||||
)
|
||||
.map((entry) => entry.key)
|
||||
.join(',');
|
||||
|
||||
const nameContext = error.context.find((entry) => entry.type?.name?.length > 0);
|
||||
const suppliedValue =
|
||||
keyContext !== '' ? keyContext : nameContext != null ? nameContext.type.name : '';
|
||||
const value = isObject(error.value) ? JSON.stringify(error.value) : error.value;
|
||||
return `Invalid value "${value}" supplied to "${suppliedValue}"`;
|
||||
}
|
||||
});
|
||||
|
||||
return [...new Set(err)];
|
||||
};
|
26
packages/kbn-securitysolution-io-ts-utils/src/from/index.ts
Normal file
26
packages/kbn-securitysolution-io-ts-utils/src/from/index.ts
Normal file
|
@ -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 { Either } from 'fp-ts/lib/Either';
|
||||
import * as t from 'io-ts';
|
||||
import { parseScheduleDates } from '../parse_schedule_dates';
|
||||
|
||||
const stringValidator = (input: unknown): input is string => typeof input === 'string';
|
||||
|
||||
export const from = new t.Type<string, string, unknown>(
|
||||
'From',
|
||||
t.string.is,
|
||||
(input, context): Either<t.Errors, string> => {
|
||||
if (stringValidator(input) && parseScheduleDates(input) == null) {
|
||||
return t.failure(input, context, 'Failed to parse "from" on rule param');
|
||||
}
|
||||
return t.string.validate(input, context);
|
||||
},
|
||||
t.identity
|
||||
);
|
||||
export type From = t.TypeOf<typeof from>;
|
15
packages/kbn-securitysolution-io-ts-utils/src/id/index.ts
Normal file
15
packages/kbn-securitysolution-io-ts-utils/src/id/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { NonEmptyString } from '../non_empty_string';
|
||||
|
||||
export const id = NonEmptyString;
|
||||
export type Id = t.TypeOf<typeof id>;
|
||||
export const idOrUndefined = t.union([id, t.undefined]);
|
||||
export type IdOrUndefined = t.TypeOf<typeof idOrUndefined>;
|
74
packages/kbn-securitysolution-io-ts-utils/src/index.ts
Normal file
74
packages/kbn-securitysolution-io-ts-utils/src/index.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './format_errors';
|
||||
export * from './actions';
|
||||
export * from './constants';
|
||||
export * from './created_at';
|
||||
export * from './created_by';
|
||||
export * from './default_version_number';
|
||||
export * from './default_actions_array';
|
||||
export * from './default_array';
|
||||
export * from './default_boolean_false';
|
||||
export * from './default_boolean_true';
|
||||
export * from './default_empty_string';
|
||||
export * from './default_export_file_name';
|
||||
export * from './default_from_string';
|
||||
export * from './default_interval_string';
|
||||
export * from './default_language_string';
|
||||
export * from './default_max_signals_number';
|
||||
export * from './default_page';
|
||||
export * from './default_per_page';
|
||||
export * from './default_risk_score_mapping_array';
|
||||
export * from './default_severity_mapping_array';
|
||||
export * from './default_string_array';
|
||||
export * from './default_string_boolean_false';
|
||||
export * from './default_threat_array';
|
||||
export * from './default_throttle_null';
|
||||
export * from './default_to_string';
|
||||
export * from './default_uuid';
|
||||
export * from './default_version_number';
|
||||
export * from './description';
|
||||
export * from './empty_string_array';
|
||||
export * from './exact_check';
|
||||
export * from './format_errors';
|
||||
export * from './from';
|
||||
export * from './id';
|
||||
export * from './iso_date_string';
|
||||
export * from './language';
|
||||
export * from './max_signals';
|
||||
export * from './meta';
|
||||
export * from './name';
|
||||
export * from './non_empty_array';
|
||||
export * from './non_empty_or_nullable_string_array';
|
||||
export * from './non_empty_string';
|
||||
export * from './normalized_ml_job_id';
|
||||
export * from './only_false_allowed';
|
||||
export * from './operator';
|
||||
export * from './parse_schedule_dates';
|
||||
export * from './positive_integer';
|
||||
export * from './positive_integer_greater_than_zero';
|
||||
export * from './references_default_array';
|
||||
export * from './risk_score';
|
||||
export * from './risk_score_mapping';
|
||||
export * from './saved_object_attributes';
|
||||
export * from './severity';
|
||||
export * from './severity_mapping';
|
||||
export * from './string_to_positive_number';
|
||||
export * from './tags';
|
||||
export * from './threat';
|
||||
export * from './threat_mapping';
|
||||
export * from './threat_subtechnique';
|
||||
export * from './threat_tactic';
|
||||
export * from './threat_technique';
|
||||
export * from './throttle';
|
||||
export * from './updated_at';
|
||||
export * from './updated_by';
|
||||
export * from './uuid';
|
||||
export * from './validate';
|
||||
export * from './version';
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { IsoDateString } from '.';
|
||||
import { foldLeftRight, getPaths } from '../test_utils';
|
||||
|
||||
describe('ios_date_string', () => {
|
||||
test('it should validate a iso string', () => {
|
||||
const payload = '2020-02-26T00:32:34.541Z';
|
||||
const decoded = IsoDateString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate an epoch number', () => {
|
||||
const payload = '1582677283067';
|
||||
const decoded = IsoDateString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1582677283067" supplied to "IsoDateString"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate a number such as 2000', () => {
|
||||
const payload = '2000';
|
||||
const decoded = IsoDateString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "2000" supplied to "IsoDateString"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should not validate a UTC', () => {
|
||||
const payload = 'Wed, 26 Feb 2020 00:36:20 GMT';
|
||||
const decoded = IsoDateString.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "Wed, 26 Feb 2020 00:36:20 GMT" supplied to "IsoDateString"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
/**
|
||||
* Types the IsoDateString as:
|
||||
* - A string that is an ISOString
|
||||
*/
|
||||
export const IsoDateString = new t.Type<string, string, unknown>(
|
||||
'IsoDateString',
|
||||
t.string.is,
|
||||
(input, context): Either<t.Errors, string> => {
|
||||
if (typeof input === 'string') {
|
||||
try {
|
||||
const parsed = new Date(input);
|
||||
if (parsed.toISOString() === input) {
|
||||
return t.success(input);
|
||||
} else {
|
||||
return t.failure(input, context);
|
||||
}
|
||||
} catch (err) {
|
||||
return t.failure(input, context);
|
||||
}
|
||||
} else {
|
||||
return t.failure(input, context);
|
||||
}
|
||||
},
|
||||
t.identity
|
||||
);
|
||||
|
||||
export type IsoDateStringC = typeof IsoDateString;
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
|
||||
export const language = t.keyof({ eql: null, kuery: null, lucene: null });
|
||||
export type Language = t.TypeOf<typeof language>;
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { Comment, CommentsArray } from '.';
|
||||
import { DATE_NOW, ID, USER } from '../../constants/index.mock';
|
||||
|
||||
export const getCommentsMock = (): Comment => ({
|
||||
comment: 'some old comment',
|
||||
created_at: DATE_NOW,
|
||||
created_by: USER,
|
||||
id: ID,
|
||||
});
|
||||
|
||||
export const getCommentsArrayMock = (): CommentsArray => [getCommentsMock(), getCommentsMock()];
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { getCommentsArrayMock, getCommentsMock } from './index.mock';
|
||||
import {
|
||||
Comment,
|
||||
comment,
|
||||
CommentsArray,
|
||||
commentsArray,
|
||||
CommentsArrayOrUndefined,
|
||||
commentsArrayOrUndefined,
|
||||
} from '.';
|
||||
import { foldLeftRight, getPaths } from '../../test_utils';
|
||||
import { DATE_NOW } from '../../constants/index.mock';
|
||||
|
||||
describe('Comment', () => {
|
||||
describe('comment', () => {
|
||||
test('it fails validation when "id" is undefined', () => {
|
||||
const payload = { ...getCommentsMock(), id: undefined };
|
||||
const decoded = comment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "id"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it passes validation with a typical comment', () => {
|
||||
const payload = getCommentsMock();
|
||||
const decoded = comment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it passes validation with "updated_at" and "updated_by" fields included', () => {
|
||||
const payload = getCommentsMock();
|
||||
payload.updated_at = DATE_NOW;
|
||||
payload.updated_by = 'someone';
|
||||
const decoded = comment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it fails validation when undefined', () => {
|
||||
const payload = undefined;
|
||||
const decoded = comment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it fails validation when "comment" is an empty string', () => {
|
||||
const payload: Omit<Comment, 'comment'> & { comment: string } = {
|
||||
...getCommentsMock(),
|
||||
comment: '',
|
||||
};
|
||||
const decoded = comment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "comment"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it fails validation when "comment" is not a string', () => {
|
||||
const payload: Omit<Comment, 'comment'> & { comment: string[] } = {
|
||||
...getCommentsMock(),
|
||||
comment: ['some value'],
|
||||
};
|
||||
const decoded = comment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "["some value"]" supplied to "comment"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it fails validation when "created_at" is not a string', () => {
|
||||
const payload: Omit<Comment, 'created_at'> & { created_at: number } = {
|
||||
...getCommentsMock(),
|
||||
created_at: 1,
|
||||
};
|
||||
const decoded = comment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "created_at"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it fails validation when "created_by" is not a string', () => {
|
||||
const payload: Omit<Comment, 'created_by'> & { created_by: number } = {
|
||||
...getCommentsMock(),
|
||||
created_by: 1,
|
||||
};
|
||||
const decoded = comment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "created_by"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it fails validation when "updated_at" is not a string', () => {
|
||||
const payload: Omit<Comment, 'updated_at'> & { updated_at: number } = {
|
||||
...getCommentsMock(),
|
||||
updated_at: 1,
|
||||
};
|
||||
const decoded = comment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "updated_at"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it fails validation when "updated_by" is not a string', () => {
|
||||
const payload: Omit<Comment, 'updated_by'> & { updated_by: number } = {
|
||||
...getCommentsMock(),
|
||||
updated_by: 1,
|
||||
};
|
||||
const decoded = comment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "updated_by"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: Comment & {
|
||||
extraKey?: string;
|
||||
} = getCommentsMock();
|
||||
payload.extraKey = 'some value';
|
||||
const decoded = comment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(getCommentsMock());
|
||||
});
|
||||
});
|
||||
|
||||
describe('commentsArray', () => {
|
||||
test('it passes validation an array of Comment', () => {
|
||||
const payload = getCommentsArrayMock();
|
||||
const decoded = commentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it passes validation when a Comment includes "updated_at" and "updated_by"', () => {
|
||||
const commentsPayload = getCommentsMock();
|
||||
commentsPayload.updated_at = DATE_NOW;
|
||||
commentsPayload.updated_by = 'someone';
|
||||
const payload = [{ ...commentsPayload }, ...getCommentsArrayMock()];
|
||||
const decoded = commentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it fails validation when undefined', () => {
|
||||
const payload = undefined;
|
||||
const decoded = commentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it fails validation when array includes non Comment types', () => {
|
||||
const payload = ([1] as unknown) as CommentsArray;
|
||||
const decoded = commentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('commentsArrayOrUndefined', () => {
|
||||
test('it passes validation an array of Comment', () => {
|
||||
const payload = getCommentsArrayMock();
|
||||
const decoded = commentsArrayOrUndefined.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it passes validation when undefined', () => {
|
||||
const payload = undefined;
|
||||
const decoded = commentsArrayOrUndefined.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it fails validation when array includes non Comment types', () => {
|
||||
const payload = ([1] as unknown) as CommentsArrayOrUndefined;
|
||||
const decoded = commentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
|
||||
import { NonEmptyString } from '../../non_empty_string';
|
||||
import { created_at } from '../../created_at';
|
||||
import { created_by } from '../../created_by';
|
||||
import { id } from '../../id';
|
||||
import { updated_at } from '../../updated_at';
|
||||
import { updated_by } from '../../updated_by';
|
||||
|
||||
export const comment = t.intersection([
|
||||
t.exact(
|
||||
t.type({
|
||||
comment: NonEmptyString,
|
||||
created_at,
|
||||
created_by,
|
||||
id,
|
||||
})
|
||||
),
|
||||
t.exact(
|
||||
t.partial({
|
||||
updated_at,
|
||||
updated_by,
|
||||
})
|
||||
),
|
||||
]);
|
||||
|
||||
export const commentsArray = t.array(comment);
|
||||
export type CommentsArray = t.TypeOf<typeof commentsArray>;
|
||||
export type Comment = t.TypeOf<typeof comment>;
|
||||
export const commentsArrayOrUndefined = t.union([commentsArray, t.undefined]);
|
||||
export type CommentsArrayOrUndefined = t.TypeOf<typeof commentsArrayOrUndefined>;
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 { CreateComment, CreateCommentsArray } from '.';
|
||||
|
||||
export const getCreateCommentsMock = (): CreateComment => ({
|
||||
comment: 'some comments',
|
||||
});
|
||||
|
||||
export const getCreateCommentsArrayMock = (): CreateCommentsArray => [getCreateCommentsMock()];
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { getCreateCommentsArrayMock, getCreateCommentsMock } from './index.mock';
|
||||
import {
|
||||
CreateComment,
|
||||
createComment,
|
||||
CreateCommentsArray,
|
||||
createCommentsArray,
|
||||
CreateCommentsArrayOrUndefined,
|
||||
createCommentsArrayOrUndefined,
|
||||
} from '.';
|
||||
import { foldLeftRight, getPaths } from '../../test_utils';
|
||||
|
||||
describe('CreateComment', () => {
|
||||
describe('createComment', () => {
|
||||
test('it passes validation with a default comment', () => {
|
||||
const payload = getCreateCommentsMock();
|
||||
const decoded = createComment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it fails validation when undefined', () => {
|
||||
const payload = undefined;
|
||||
const decoded = createComment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "{| comment: NonEmptyString |}"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it fails validation when "comment" is not a string', () => {
|
||||
const payload: Omit<CreateComment, 'comment'> & { comment: string[] } = {
|
||||
...getCreateCommentsMock(),
|
||||
comment: ['some value'],
|
||||
};
|
||||
const decoded = createComment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "["some value"]" supplied to "comment"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: CreateComment & {
|
||||
extraKey?: string;
|
||||
} = getCreateCommentsMock();
|
||||
payload.extraKey = 'some value';
|
||||
const decoded = createComment.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(getCreateCommentsMock());
|
||||
});
|
||||
});
|
||||
|
||||
describe('createCommentsArray', () => {
|
||||
test('it passes validation an array of comments', () => {
|
||||
const payload = getCreateCommentsArrayMock();
|
||||
const decoded = createCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it fails validation when undefined', () => {
|
||||
const payload = undefined;
|
||||
const decoded = createCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "Array<{| comment: NonEmptyString |}>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it fails validation when array includes non comments types', () => {
|
||||
const payload = ([1] as unknown) as CreateCommentsArray;
|
||||
const decoded = createCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "Array<{| comment: NonEmptyString |}>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createCommentsArrayOrUndefined', () => {
|
||||
test('it passes validation an array of comments', () => {
|
||||
const payload = getCreateCommentsArrayMock();
|
||||
const decoded = createCommentsArrayOrUndefined.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it passes validation when undefined', () => {
|
||||
const payload = undefined;
|
||||
const decoded = createCommentsArrayOrUndefined.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it fails validation when array includes non comments types', () => {
|
||||
const payload = ([1] as unknown) as CreateCommentsArrayOrUndefined;
|
||||
const decoded = createCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "Array<{| comment: NonEmptyString |}>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { NonEmptyString } from '../../non_empty_string';
|
||||
|
||||
export const createComment = t.exact(
|
||||
t.type({
|
||||
comment: NonEmptyString,
|
||||
})
|
||||
);
|
||||
|
||||
export type CreateComment = t.TypeOf<typeof createComment>;
|
||||
export const createCommentsArray = t.array(createComment);
|
||||
export type CreateCommentsArray = t.TypeOf<typeof createCommentsArray>;
|
||||
export type CreateComments = t.TypeOf<typeof createComment>;
|
||||
export const createCommentsArrayOrUndefined = t.union([createCommentsArray, t.undefined]);
|
||||
export type CreateCommentsArrayOrUndefined = t.TypeOf<typeof createCommentsArrayOrUndefined>;
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { CommentsArray } from '../comment';
|
||||
import { DefaultCommentsArray } from '.';
|
||||
import { foldLeftRight, getPaths } from '../../test_utils';
|
||||
import { getCommentsArrayMock } from '../comment/index.mock';
|
||||
|
||||
describe('default_comments_array', () => {
|
||||
test('it should pass validation when supplied an empty array', () => {
|
||||
const payload: CommentsArray = [];
|
||||
const decoded = DefaultCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should pass validation when supplied an array of comments', () => {
|
||||
const payload: CommentsArray = getCommentsArrayMock();
|
||||
const decoded = DefaultCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should fail validation when supplied an array of numbers', () => {
|
||||
const payload = [1];
|
||||
const decoded = DefaultCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should fail validation when supplied an array of strings', () => {
|
||||
const payload = ['some string'];
|
||||
const decoded = DefaultCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default array entry', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { comment, CommentsArray } from '../comment';
|
||||
|
||||
/**
|
||||
* Types the DefaultCommentsArray as:
|
||||
* - If null or undefined, then a default array of type entry will be set
|
||||
*/
|
||||
export const DefaultCommentsArray = new t.Type<CommentsArray, CommentsArray, unknown>(
|
||||
'DefaultCommentsArray',
|
||||
t.array(comment).is,
|
||||
(input): Either<t.Errors, CommentsArray> =>
|
||||
input == null ? t.success([]) : t.array(comment).decode(input),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { foldLeftRight, getPaths } from '../../test_utils';
|
||||
import { CommentsArray } from '../comment';
|
||||
import { DefaultCommentsArray } from '../default_comments_array';
|
||||
import { getCommentsArrayMock } from '../comment/index.mock';
|
||||
|
||||
describe('default_comments_array', () => {
|
||||
test('it should pass validation when supplied an empty array', () => {
|
||||
const payload: CommentsArray = [];
|
||||
const decoded = DefaultCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should pass validation when supplied an array of comments', () => {
|
||||
const payload: CommentsArray = getCommentsArrayMock();
|
||||
const decoded = DefaultCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should fail validation when supplied an array of numbers', () => {
|
||||
const payload = [1];
|
||||
const decoded = DefaultCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should fail validation when supplied an array of strings', () => {
|
||||
const payload = ['some string'];
|
||||
const decoded = DefaultCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString, created_at: string, created_by: string, id: NonEmptyString |} & Partial<{| updated_at: string, updated_by: string |}>)>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default array entry', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { createComment, CreateCommentsArray } from '../create_comment';
|
||||
|
||||
/**
|
||||
* Types the DefaultCreateComments as:
|
||||
* - If null or undefined, then a default array of type entry will be set
|
||||
*/
|
||||
export const DefaultCreateCommentsArray = new t.Type<
|
||||
CreateCommentsArray,
|
||||
CreateCommentsArray,
|
||||
unknown
|
||||
>(
|
||||
'DefaultCreateComments',
|
||||
t.array(createComment).is,
|
||||
(input): Either<t.Errors, CreateCommentsArray> =>
|
||||
input == null ? t.success([]) : t.array(createComment).decode(input),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultNamespace } from '.';
|
||||
import { foldLeftRight, getPaths } from '../../test_utils';
|
||||
|
||||
describe('default_namespace', () => {
|
||||
test('it should validate "single"', () => {
|
||||
const payload = 'single';
|
||||
const decoded = DefaultNamespace.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate "agnostic"', () => {
|
||||
const payload = 'agnostic';
|
||||
const decoded = DefaultNamespace.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it defaults to "single" if "undefined"', () => {
|
||||
const payload = undefined;
|
||||
const decoded = DefaultNamespace.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual('single');
|
||||
});
|
||||
|
||||
test('it defaults to "single" if "null"', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultNamespace.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual('single');
|
||||
});
|
||||
|
||||
test('it should FAIL validation if not "single" or "agnostic"', () => {
|
||||
const payload = 'something else';
|
||||
const decoded = DefaultNamespace.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
`Invalid value "something else" supplied to "DefaultNamespace"`,
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
|
||||
export const namespaceType = t.keyof({ agnostic: null, single: null });
|
||||
export type NamespaceType = t.TypeOf<typeof namespaceType>;
|
||||
|
||||
/**
|
||||
* Types the DefaultNamespace as:
|
||||
* - If null or undefined, then a default string/enumeration of "single" will be used.
|
||||
*/
|
||||
export const DefaultNamespace = new t.Type<NamespaceType, NamespaceType | undefined, unknown>(
|
||||
'DefaultNamespace',
|
||||
namespaceType.is,
|
||||
(input, context): Either<t.Errors, NamespaceType> =>
|
||||
input == null ? t.success('single') : namespaceType.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { DefaultNamespaceArray, DefaultNamespaceArrayType } from '.';
|
||||
import { foldLeftRight, getPaths } from '../../test_utils';
|
||||
|
||||
describe('default_namespace_array', () => {
|
||||
test('it should validate "null" single item as an array with a "single" value', () => {
|
||||
const payload: DefaultNamespaceArrayType = null;
|
||||
const decoded = DefaultNamespaceArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(['single']);
|
||||
});
|
||||
|
||||
test('it should FAIL validation of numeric value', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultNamespaceArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultNamespaceArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should validate "undefined" item as an array with a "single" value', () => {
|
||||
const payload: DefaultNamespaceArrayType = undefined;
|
||||
const decoded = DefaultNamespaceArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(['single']);
|
||||
});
|
||||
|
||||
test('it should validate "single" as an array of a "single" value', () => {
|
||||
const payload: DefaultNamespaceArrayType = 'single';
|
||||
const decoded = DefaultNamespaceArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([payload]);
|
||||
});
|
||||
|
||||
test('it should validate "agnostic" as an array of a "agnostic" value', () => {
|
||||
const payload: DefaultNamespaceArrayType = 'agnostic';
|
||||
const decoded = DefaultNamespaceArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([payload]);
|
||||
});
|
||||
|
||||
test('it should validate "single,agnostic" as an array of 2 values of ["single", "agnostic"] values', () => {
|
||||
const payload: DefaultNamespaceArrayType = 'agnostic,single';
|
||||
const decoded = DefaultNamespaceArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(['agnostic', 'single']);
|
||||
});
|
||||
|
||||
test('it should validate 3 elements of "single,agnostic,single" as an array of 3 values of ["single", "agnostic", "single"] values', () => {
|
||||
const payload: DefaultNamespaceArrayType = 'single,agnostic,single';
|
||||
const decoded = DefaultNamespaceArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(['single', 'agnostic', 'single']);
|
||||
});
|
||||
|
||||
test('it should validate 3 elements of "single,agnostic, single" as an array of 3 values of ["single", "agnostic", "single"] values when there are spaces', () => {
|
||||
const payload: DefaultNamespaceArrayType = ' single, agnostic, single ';
|
||||
const decoded = DefaultNamespaceArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(['single', 'agnostic', 'single']);
|
||||
});
|
||||
|
||||
test('it should FAIL validation when given 3 elements of "single,agnostic,junk" since the 3rd value is junk', () => {
|
||||
const payload: DefaultNamespaceArrayType = 'single,agnostic,junk';
|
||||
const decoded = DefaultNamespaceArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "junk" supplied to "DefaultNamespaceArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { namespaceType } from '../default_namespace';
|
||||
|
||||
export const namespaceTypeArray = t.array(namespaceType);
|
||||
export type NamespaceTypeArray = t.TypeOf<typeof namespaceTypeArray>;
|
||||
|
||||
/**
|
||||
* Types the DefaultNamespaceArray as:
|
||||
* - If null or undefined, then a default string array of "single" will be used.
|
||||
* - If it contains a string, then it is split along the commas and puts them into an array and validates it
|
||||
*/
|
||||
export const DefaultNamespaceArray = new t.Type<
|
||||
NamespaceTypeArray,
|
||||
string | undefined | null,
|
||||
unknown
|
||||
>(
|
||||
'DefaultNamespaceArray',
|
||||
namespaceTypeArray.is,
|
||||
(input, context): Either<t.Errors, NamespaceTypeArray> => {
|
||||
if (input == null) {
|
||||
return t.success(['single']);
|
||||
} else if (typeof input === 'string') {
|
||||
const commaSeparatedValues = input
|
||||
.trim()
|
||||
.split(',')
|
||||
.map((value) => value.trim());
|
||||
return namespaceTypeArray.validate(commaSeparatedValues, context);
|
||||
}
|
||||
return t.failure(input, context);
|
||||
},
|
||||
String
|
||||
);
|
||||
|
||||
export type DefaultNamespaceArrayType = t.OutputOf<typeof DefaultNamespaceArray>;
|
||||
export type DefaultNamespaceArrayTypeDecoded = t.TypeOf<typeof DefaultNamespaceArray>;
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { UpdateCommentsArray } from '../update_comment';
|
||||
import { DefaultUpdateCommentsArray } from '.';
|
||||
import { foldLeftRight, getPaths } from '../../test_utils';
|
||||
import { getUpdateCommentsArrayMock } from '../update_comment/index.mock';
|
||||
|
||||
describe('default_update_comments_array', () => {
|
||||
test('it should pass validation when supplied an empty array', () => {
|
||||
const payload: UpdateCommentsArray = [];
|
||||
const decoded = DefaultUpdateCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should pass validation when supplied an array of comments', () => {
|
||||
const payload: UpdateCommentsArray = getUpdateCommentsArrayMock();
|
||||
const decoded = DefaultUpdateCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should fail validation when supplied an array of numbers', () => {
|
||||
const payload = [1];
|
||||
const decoded = DefaultUpdateCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should fail validation when supplied an array of strings', () => {
|
||||
const payload = ['some string'];
|
||||
const decoded = DefaultUpdateCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "some string" supplied to "Array<({| comment: NonEmptyString |} & Partial<{| id: NonEmptyString |}>)>"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default array entry', () => {
|
||||
const payload = null;
|
||||
const decoded = DefaultUpdateCommentsArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual([]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { updateCommentsArray, UpdateCommentsArray } from '../update_comment';
|
||||
|
||||
/**
|
||||
* Types the DefaultCommentsUpdate as:
|
||||
* - If null or undefined, then a default array of type entry will be set
|
||||
*/
|
||||
export const DefaultUpdateCommentsArray = new t.Type<
|
||||
UpdateCommentsArray,
|
||||
UpdateCommentsArray,
|
||||
unknown
|
||||
>(
|
||||
'DefaultCreateComments',
|
||||
updateCommentsArray.is,
|
||||
(input): Either<t.Errors, UpdateCommentsArray> =>
|
||||
input == null ? t.success([]) : updateCommentsArray.decode(input),
|
||||
t.identity
|
||||
);
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { EndpointEntriesArray } from '.';
|
||||
import { getEndpointEntryMatchMock } from '../entry_match/index.mock';
|
||||
import { getEndpointEntryMatchAnyMock } from '../entry_match_any/index.mock';
|
||||
import { getEndpointEntryNestedMock } from '../entry_nested/index.mock';
|
||||
|
||||
export const getEndpointEntriesArrayMock = (): EndpointEntriesArray => [
|
||||
getEndpointEntryMatchMock(),
|
||||
getEndpointEntryMatchAnyMock(),
|
||||
getEndpointEntryNestedMock(),
|
||||
];
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { getEndpointEntryMatchMock } from '../entry_match/index.mock';
|
||||
import {
|
||||
endpointEntriesArray,
|
||||
nonEmptyEndpointEntriesArray,
|
||||
NonEmptyEndpointEntriesArray,
|
||||
} from '.';
|
||||
import { foldLeftRight, getPaths } from '../../../test_utils';
|
||||
import { getEndpointEntryMatchAnyMock } from '../entry_match_any/index.mock';
|
||||
import { getEndpointEntryNestedMock } from '../entry_nested/index.mock';
|
||||
import { getEndpointEntriesArrayMock } from './index.mock';
|
||||
import { getEntryListMock } from '../../entries_list/index.mock';
|
||||
import { getEntryExistsMock } from '../../entries_exist/index.mock';
|
||||
|
||||
describe('Endpoint', () => {
|
||||
describe('entriesArray', () => {
|
||||
test('it should validate an array with match entry', () => {
|
||||
const payload = [getEndpointEntryMatchMock()];
|
||||
const decoded = endpointEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array with match_any entry', () => {
|
||||
const payload = [getEndpointEntryMatchAnyMock()];
|
||||
const decoded = endpointEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate an empty array', () => {
|
||||
const payload: NonEmptyEndpointEntriesArray = [];
|
||||
const decoded = nonEmptyEndpointEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "[]" supplied to "NonEmptyEndpointEntriesArray"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('type guard for nonEmptyEndpointNestedEntries should allow array of endpoint entries', () => {
|
||||
const payload: NonEmptyEndpointEntriesArray = [getEndpointEntryMatchAnyMock()];
|
||||
const guarded = nonEmptyEndpointEntriesArray.is(payload);
|
||||
expect(guarded).toBeTruthy();
|
||||
});
|
||||
|
||||
test('type guard for nonEmptyEndpointNestedEntries should disallow empty arrays', () => {
|
||||
const payload: NonEmptyEndpointEntriesArray = [];
|
||||
const guarded = nonEmptyEndpointEntriesArray.is(payload);
|
||||
expect(guarded).toBeFalsy();
|
||||
});
|
||||
|
||||
test('it should NOT validate an array with exists entry', () => {
|
||||
const payload = [getEntryExistsMock()];
|
||||
const decoded = endpointEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "exists" supplied to "type"',
|
||||
'Invalid value "undefined" supplied to "value"',
|
||||
'Invalid value "undefined" supplied to "entries"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate an array with list entry', () => {
|
||||
const payload = [getEntryListMock()];
|
||||
const decoded = endpointEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "list" supplied to "type"',
|
||||
'Invalid value "undefined" supplied to "value"',
|
||||
'Invalid value "undefined" supplied to "entries"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should validate an array with nested entry', () => {
|
||||
const payload = [getEndpointEntryNestedMock()];
|
||||
const decoded = endpointEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array with all types of entries', () => {
|
||||
const payload = getEndpointEntriesArrayMock();
|
||||
const decoded = endpointEntriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { endpointEntryMatch } from '../entry_match';
|
||||
import { endpointEntryMatchAny } from '../entry_match_any';
|
||||
import { endpointEntryNested } from '../entry_nested';
|
||||
|
||||
export const endpointEntriesArray = t.array(
|
||||
t.union([endpointEntryMatch, endpointEntryMatchAny, endpointEntryNested])
|
||||
);
|
||||
export type EndpointEntriesArray = t.TypeOf<typeof endpointEntriesArray>;
|
||||
|
||||
/**
|
||||
* Types the nonEmptyEndpointEntriesArray as:
|
||||
* - An array of entries of length 1 or greater
|
||||
*
|
||||
*/
|
||||
export const nonEmptyEndpointEntriesArray = new t.Type<
|
||||
EndpointEntriesArray,
|
||||
EndpointEntriesArray,
|
||||
unknown
|
||||
>(
|
||||
'NonEmptyEndpointEntriesArray',
|
||||
(u: unknown): u is EndpointEntriesArray => endpointEntriesArray.is(u) && u.length > 0,
|
||||
(input, context): Either<t.Errors, EndpointEntriesArray> => {
|
||||
if (Array.isArray(input) && input.length === 0) {
|
||||
return t.failure(input, context);
|
||||
} else {
|
||||
return endpointEntriesArray.validate(input, context);
|
||||
}
|
||||
},
|
||||
t.identity
|
||||
);
|
||||
|
||||
export type NonEmptyEndpointEntriesArray = t.OutputOf<typeof nonEmptyEndpointEntriesArray>;
|
||||
export type NonEmptyEndpointEntriesArrayDecoded = t.TypeOf<typeof nonEmptyEndpointEntriesArray>;
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { EndpointEntryMatch } from '.';
|
||||
import { ENTRY_VALUE, FIELD, MATCH, OPERATOR } from '../../../constants/index.mock';
|
||||
|
||||
export const getEndpointEntryMatchMock = (): EndpointEntryMatch => ({
|
||||
field: FIELD,
|
||||
operator: OPERATOR,
|
||||
type: MATCH,
|
||||
value: ENTRY_VALUE,
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { getEndpointEntryMatchMock } from './index.mock';
|
||||
import { EndpointEntryMatch, endpointEntryMatch } from '.';
|
||||
import { foldLeftRight, getPaths } from '../../../test_utils';
|
||||
import { getEntryMatchMock } from '../../entry_match/index.mock';
|
||||
|
||||
describe('endpointEntryMatch', () => {
|
||||
test('it should validate an entry', () => {
|
||||
const payload = getEndpointEntryMatchMock();
|
||||
const decoded = endpointEntryMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate when "operator" is "excluded"', () => {
|
||||
// Use the generic entry mock so we can test operator: excluded
|
||||
const payload = getEntryMatchMock();
|
||||
payload.operator = 'excluded';
|
||||
const decoded = endpointEntryMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "excluded" supplied to "operator"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "field" is empty string', () => {
|
||||
const payload: Omit<EndpointEntryMatch, 'field'> & { field: string } = {
|
||||
...getEndpointEntryMatchMock(),
|
||||
field: '',
|
||||
};
|
||||
const decoded = endpointEntryMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "value" is not string', () => {
|
||||
const payload: Omit<EndpointEntryMatch, 'value'> & { value: string[] } = {
|
||||
...getEndpointEntryMatchMock(),
|
||||
value: ['some value'],
|
||||
};
|
||||
const decoded = endpointEntryMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "["some value"]" supplied to "value"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "value" is empty string', () => {
|
||||
const payload: Omit<EndpointEntryMatch, 'value'> & { value: string } = {
|
||||
...getEndpointEntryMatchMock(),
|
||||
value: '',
|
||||
};
|
||||
const decoded = endpointEntryMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "value"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "type" is not "match"', () => {
|
||||
const payload: Omit<EndpointEntryMatch, 'type'> & { type: string } = {
|
||||
...getEndpointEntryMatchMock(),
|
||||
type: 'match_any',
|
||||
};
|
||||
const decoded = endpointEntryMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "match_any" supplied to "type"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EndpointEntryMatch & {
|
||||
extraKey?: string;
|
||||
} = getEndpointEntryMatchMock();
|
||||
payload.extraKey = 'some value';
|
||||
const decoded = endpointEntryMatch.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(getEntryMatchMock());
|
||||
});
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { operatorIncluded } from '../../../operator';
|
||||
import { NonEmptyString } from '../../../non_empty_string';
|
||||
|
||||
export const endpointEntryMatch = t.exact(
|
||||
t.type({
|
||||
field: NonEmptyString,
|
||||
operator: operatorIncluded,
|
||||
type: t.keyof({ match: null }),
|
||||
value: NonEmptyString,
|
||||
})
|
||||
);
|
||||
export type EndpointEntryMatch = t.TypeOf<typeof endpointEntryMatch>;
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { ENTRY_VALUE, FIELD, MATCH_ANY, OPERATOR } from '../../../constants/index.mock';
|
||||
import { EndpointEntryMatchAny } from '.';
|
||||
|
||||
export const getEndpointEntryMatchAnyMock = (): EndpointEntryMatchAny => ({
|
||||
field: FIELD,
|
||||
operator: OPERATOR,
|
||||
type: MATCH_ANY,
|
||||
value: [ENTRY_VALUE],
|
||||
});
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { getEndpointEntryMatchAnyMock } from './index.mock';
|
||||
import { EndpointEntryMatchAny, endpointEntryMatchAny } from '.';
|
||||
import { foldLeftRight, getPaths } from '../../../test_utils';
|
||||
import { getEntryMatchAnyMock } from '../../entry_match_any/index.mock';
|
||||
|
||||
describe('endpointEntryMatchAny', () => {
|
||||
test('it should validate an entry', () => {
|
||||
const payload = getEndpointEntryMatchAnyMock();
|
||||
const decoded = endpointEntryMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate when operator is "excluded"', () => {
|
||||
// Use the generic entry mock so we can test operator: excluded
|
||||
const payload = getEntryMatchAnyMock();
|
||||
payload.operator = 'excluded';
|
||||
const decoded = endpointEntryMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "excluded" supplied to "operator"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when field is empty string', () => {
|
||||
const payload: Omit<EndpointEntryMatchAny, 'field'> & { field: string } = {
|
||||
...getEndpointEntryMatchAnyMock(),
|
||||
field: '',
|
||||
};
|
||||
const decoded = endpointEntryMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when value is empty array', () => {
|
||||
const payload: Omit<EndpointEntryMatchAny, 'value'> & { value: string[] } = {
|
||||
...getEndpointEntryMatchAnyMock(),
|
||||
value: [],
|
||||
};
|
||||
const decoded = endpointEntryMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "[]" supplied to "value"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when value is not string array', () => {
|
||||
const payload: Omit<EndpointEntryMatchAny, 'value'> & { value: string } = {
|
||||
...getEndpointEntryMatchAnyMock(),
|
||||
value: 'some string',
|
||||
};
|
||||
const decoded = endpointEntryMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "some string" supplied to "value"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "type" is not "match_any"', () => {
|
||||
const payload: Omit<EndpointEntryMatchAny, 'type'> & { type: string } = {
|
||||
...getEndpointEntryMatchAnyMock(),
|
||||
type: 'match',
|
||||
};
|
||||
const decoded = endpointEntryMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EndpointEntryMatchAny & {
|
||||
extraKey?: string;
|
||||
} = getEndpointEntryMatchAnyMock();
|
||||
payload.extraKey = 'some extra key';
|
||||
const decoded = endpointEntryMatchAny.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(getEntryMatchAnyMock());
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { nonEmptyOrNullableStringArray } from '../../../non_empty_or_nullable_string_array';
|
||||
import { operatorIncluded } from '../../../operator';
|
||||
import { NonEmptyString } from '../../../non_empty_string';
|
||||
|
||||
export const endpointEntryMatchAny = t.exact(
|
||||
t.type({
|
||||
field: NonEmptyString,
|
||||
operator: operatorIncluded,
|
||||
type: t.keyof({ match_any: null }),
|
||||
value: nonEmptyOrNullableStringArray,
|
||||
})
|
||||
);
|
||||
export type EndpointEntryMatchAny = t.TypeOf<typeof endpointEntryMatchAny>;
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { operatorIncluded } from '../../../operator';
|
||||
import { NonEmptyString } from '../../../non_empty_string';
|
||||
|
||||
export const endpointEntryMatchWildcard = t.exact(
|
||||
t.type({
|
||||
field: NonEmptyString,
|
||||
operator: operatorIncluded,
|
||||
type: t.keyof({ wildcard: null }),
|
||||
value: NonEmptyString,
|
||||
})
|
||||
);
|
||||
export type EndpointEntryMatchWildcard = t.TypeOf<typeof endpointEntryMatchWildcard>;
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { EndpointEntryNested } from '.';
|
||||
import { FIELD, NESTED } from '../../../constants/index.mock';
|
||||
import { getEndpointEntryMatchMock } from '../entry_match/index.mock';
|
||||
import { getEndpointEntryMatchAnyMock } from '../entry_match_any/index.mock';
|
||||
|
||||
export const getEndpointEntryNestedMock = (): EndpointEntryNested => ({
|
||||
entries: [getEndpointEntryMatchMock(), getEndpointEntryMatchAnyMock()],
|
||||
field: FIELD,
|
||||
type: NESTED,
|
||||
});
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { EndpointEntryNested, endpointEntryNested } from '.';
|
||||
import { foldLeftRight, getPaths } from '../../../test_utils';
|
||||
import { getEndpointEntryNestedMock } from './index.mock';
|
||||
import { getEndpointEntryMatchAnyMock } from '../entry_match_any/index.mock';
|
||||
import {
|
||||
nonEmptyEndpointNestedEntriesArray,
|
||||
NonEmptyEndpointNestedEntriesArray,
|
||||
} from '../non_empty_nested_entries_array';
|
||||
import { getEndpointEntryMatchMock } from '../entry_match/index.mock';
|
||||
import { getEntryExistsMock } from '../../entries_exist/index.mock';
|
||||
|
||||
describe('endpointEntryNested', () => {
|
||||
test('it should validate a nested entry', () => {
|
||||
const payload = getEndpointEntryNestedMock();
|
||||
const decoded = endpointEntryNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "type" is not "nested"', () => {
|
||||
const payload: Omit<EndpointEntryNested, 'type'> & { type: 'match' } = {
|
||||
...getEndpointEntryNestedMock(),
|
||||
type: 'match',
|
||||
};
|
||||
const decoded = endpointEntryNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "field" is empty string', () => {
|
||||
const payload: Omit<EndpointEntryNested, 'field'> & {
|
||||
field: string;
|
||||
} = { ...getEndpointEntryNestedMock(), field: '' };
|
||||
const decoded = endpointEntryNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "field" is not a string', () => {
|
||||
const payload: Omit<EndpointEntryNested, 'field'> & {
|
||||
field: number;
|
||||
} = { ...getEndpointEntryNestedMock(), field: 1 };
|
||||
const decoded = endpointEntryNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "1" supplied to "field"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "entries" is not an array', () => {
|
||||
const payload: Omit<EndpointEntryNested, 'entries'> & {
|
||||
entries: string;
|
||||
} = { ...getEndpointEntryNestedMock(), entries: 'im a string' };
|
||||
const decoded = endpointEntryNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "im a string" supplied to "entries"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should validate when "entries" contains an entry item that is type "match"', () => {
|
||||
const payload = { ...getEndpointEntryNestedMock(), entries: [getEndpointEntryMatchAnyMock()] };
|
||||
const decoded = endpointEntryNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual({
|
||||
entries: [
|
||||
{
|
||||
field: 'host.name',
|
||||
operator: 'included',
|
||||
type: 'match_any',
|
||||
value: ['some host name'],
|
||||
},
|
||||
],
|
||||
field: 'host.name',
|
||||
type: 'nested',
|
||||
});
|
||||
});
|
||||
|
||||
test('it should NOT validate when "entries" contains an entry item that is type "exists"', () => {
|
||||
const payload = { ...getEndpointEntryNestedMock(), entries: [getEntryExistsMock()] };
|
||||
const decoded = endpointEntryNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "exists" supplied to "entries,type"',
|
||||
'Invalid value "undefined" supplied to "entries,value"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EndpointEntryNested & {
|
||||
extraKey?: string;
|
||||
} = getEndpointEntryNestedMock();
|
||||
payload.extraKey = 'some extra key';
|
||||
const decoded = endpointEntryNested.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(getEndpointEntryNestedMock());
|
||||
});
|
||||
|
||||
test('type guard for nonEmptyEndpointNestedEntries should allow array of endpoint entries', () => {
|
||||
const payload: NonEmptyEndpointNestedEntriesArray = [
|
||||
getEndpointEntryMatchMock(),
|
||||
getEndpointEntryMatchAnyMock(),
|
||||
];
|
||||
const guarded = nonEmptyEndpointNestedEntriesArray.is(payload);
|
||||
expect(guarded).toBeTruthy();
|
||||
});
|
||||
|
||||
test('type guard for nonEmptyEndpointNestedEntries should disallow empty arrays', () => {
|
||||
const payload: NonEmptyEndpointNestedEntriesArray = [];
|
||||
const guarded = nonEmptyEndpointNestedEntriesArray.is(payload);
|
||||
expect(guarded).toBeFalsy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { NonEmptyString } from '../../../non_empty_string';
|
||||
import { nonEmptyEndpointNestedEntriesArray } from '../non_empty_nested_entries_array';
|
||||
|
||||
export const endpointEntryNested = t.exact(
|
||||
t.type({
|
||||
entries: nonEmptyEndpointNestedEntriesArray,
|
||||
field: NonEmptyString,
|
||||
type: t.keyof({ nested: null }),
|
||||
})
|
||||
);
|
||||
export type EndpointEntryNested = t.TypeOf<typeof endpointEntryNested>;
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
export * from './entries';
|
||||
export * from './entry_match';
|
||||
export * from './entry_match_any';
|
||||
export * from './entry_match_wildcard';
|
||||
export * from './entry_nested';
|
||||
export * from './non_empty_nested_entries_array';
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { endpointEntryMatch } from '../entry_match';
|
||||
import { endpointEntryMatchAny } from '../entry_match_any';
|
||||
|
||||
export const endpointNestedEntriesArray = t.array(
|
||||
t.union([endpointEntryMatch, endpointEntryMatchAny])
|
||||
);
|
||||
export type EndpointNestedEntriesArray = t.TypeOf<typeof endpointNestedEntriesArray>;
|
||||
|
||||
/**
|
||||
* Types the nonEmptyNestedEntriesArray as:
|
||||
* - An array of entries of length 1 or greater
|
||||
*
|
||||
*/
|
||||
export const nonEmptyEndpointNestedEntriesArray = new t.Type<
|
||||
EndpointNestedEntriesArray,
|
||||
EndpointNestedEntriesArray,
|
||||
unknown
|
||||
>(
|
||||
'NonEmptyEndpointNestedEntriesArray',
|
||||
(u: unknown): u is EndpointNestedEntriesArray => endpointNestedEntriesArray.is(u) && u.length > 0,
|
||||
(input, context): Either<t.Errors, EndpointNestedEntriesArray> => {
|
||||
if (Array.isArray(input) && input.length === 0) {
|
||||
return t.failure(input, context);
|
||||
} else {
|
||||
return endpointNestedEntriesArray.validate(input, context);
|
||||
}
|
||||
},
|
||||
t.identity
|
||||
);
|
||||
|
||||
export type NonEmptyEndpointNestedEntriesArray = t.OutputOf<
|
||||
typeof nonEmptyEndpointNestedEntriesArray
|
||||
>;
|
||||
export type NonEmptyEndpointNestedEntriesArrayDecoded = t.TypeOf<
|
||||
typeof nonEmptyEndpointNestedEntriesArray
|
||||
>;
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { EntriesArray } from '.';
|
||||
import { getEntryExistsMock } from '../entries_exist/index.mock';
|
||||
import { getEntryListMock } from '../entries_list/index.mock';
|
||||
import { getEntryMatchMock } from '../entry_match/index.mock';
|
||||
import { getEntryMatchAnyMock } from '../entry_match_any/index.mock';
|
||||
import { getEntryNestedMock } from '../entry_nested/index.mock';
|
||||
|
||||
export const getListAndNonListEntriesArrayMock = (): EntriesArray => [
|
||||
getEntryMatchMock(),
|
||||
getEntryMatchAnyMock(),
|
||||
getEntryListMock(),
|
||||
getEntryExistsMock(),
|
||||
getEntryNestedMock(),
|
||||
];
|
||||
|
||||
export const getListEntriesArrayMock = (): EntriesArray => [getEntryListMock(), getEntryListMock()];
|
||||
|
||||
export const getEntriesArrayMock = (): EntriesArray => [
|
||||
getEntryMatchMock(),
|
||||
getEntryMatchAnyMock(),
|
||||
getEntryExistsMock(),
|
||||
getEntryNestedMock(),
|
||||
];
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { getEntryMatchMock } from '../entry_match/index.mock';
|
||||
import { entriesArray, entriesArrayOrUndefined, entry } from '.';
|
||||
import { foldLeftRight, getPaths } from '../../test_utils';
|
||||
import { getEntryMatchAnyMock } from '../entry_match_any/index.mock';
|
||||
import { getEntryExistsMock } from '../entries_exist/index.mock';
|
||||
import { getEntryListMock } from '../entries_list/index.mock';
|
||||
import { getEntryNestedMock } from '../entry_nested/index.mock';
|
||||
import { getEntriesArrayMock } from './index.mock';
|
||||
|
||||
describe('Entries', () => {
|
||||
describe('entry', () => {
|
||||
test('it should validate a match entry', () => {
|
||||
const payload = getEntryMatchMock();
|
||||
const decoded = entry.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a match_any entry', () => {
|
||||
const payload = getEntryMatchAnyMock();
|
||||
const decoded = entry.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a exists entry', () => {
|
||||
const payload = getEntryExistsMock();
|
||||
const decoded = entry.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a list entry', () => {
|
||||
const payload = getEntryListMock();
|
||||
const decoded = entry.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should FAIL validation of nested entry', () => {
|
||||
const payload = getEntryNestedMock();
|
||||
const decoded = entry.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "operator"',
|
||||
'Invalid value "nested" supplied to "type"',
|
||||
'Invalid value "undefined" supplied to "value"',
|
||||
'Invalid value "undefined" supplied to "list"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('entriesArray', () => {
|
||||
test('it should validate an array with match entry', () => {
|
||||
const payload = [getEntryMatchMock()];
|
||||
const decoded = entriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array with match_any entry', () => {
|
||||
const payload = [getEntryMatchAnyMock()];
|
||||
const decoded = entriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array with exists entry', () => {
|
||||
const payload = [getEntryExistsMock()];
|
||||
const decoded = entriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array with list entry', () => {
|
||||
const payload = [getEntryListMock()];
|
||||
const decoded = entriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array with nested entry', () => {
|
||||
const payload = [getEntryNestedMock()];
|
||||
const decoded = entriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array with all types of entries', () => {
|
||||
const payload = [...getEntriesArrayMock()];
|
||||
const decoded = entriesArray.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
});
|
||||
|
||||
describe('entriesArrayOrUndefined', () => {
|
||||
test('it should validate undefined', () => {
|
||||
const payload = undefined;
|
||||
const decoded = entriesArrayOrUndefined.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an array with nested entry', () => {
|
||||
const payload = [getEntryNestedMock()];
|
||||
const decoded = entriesArrayOrUndefined.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { entriesExists } from '../entries_exist';
|
||||
import { entriesList } from '../entries_list';
|
||||
import { entriesMatch } from '../entry_match';
|
||||
import { entriesMatchAny } from '../entry_match_any';
|
||||
import { entriesMatchWildcard } from '../entry_match_wildcard';
|
||||
import { entriesNested } from '../entry_nested';
|
||||
|
||||
// NOTE: Type nested is not included here to denote it's non-recursive nature.
|
||||
// So a nested entry is really just a collection of `Entry` types.
|
||||
export const entry = t.union([
|
||||
entriesMatch,
|
||||
entriesMatchAny,
|
||||
entriesList,
|
||||
entriesExists,
|
||||
entriesMatchWildcard,
|
||||
]);
|
||||
export type Entry = t.TypeOf<typeof entry>;
|
||||
|
||||
export const entriesArray = t.array(
|
||||
t.union([
|
||||
entriesMatch,
|
||||
entriesMatchAny,
|
||||
entriesList,
|
||||
entriesExists,
|
||||
entriesNested,
|
||||
entriesMatchWildcard,
|
||||
])
|
||||
);
|
||||
export type EntriesArray = t.TypeOf<typeof entriesArray>;
|
||||
|
||||
export const entriesArrayOrUndefined = t.union([entriesArray, t.undefined]);
|
||||
export type EntriesArrayOrUndefined = t.TypeOf<typeof entriesArrayOrUndefined>;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue