mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[config-schema] Add min
and max
options for Duration
(#173494)
## Summary Fix https://github.com/elastic/kibana/issues/97513 Add a `min` and `max` option for the `Duration` schema type. ```ts schema.object({ duration: schema.duration({ min: '5m', max: '1d' }) }); ```
This commit is contained in:
parent
c01b3a450a
commit
96cba63172
4 changed files with 93 additions and 5 deletions
|
@ -153,6 +153,46 @@ export const internals: JoiRoot = Joi.extend(
|
|||
}
|
||||
return { value };
|
||||
},
|
||||
rules: {
|
||||
min: {
|
||||
args: [
|
||||
{
|
||||
name: 'limit',
|
||||
assert: Joi.alternatives([Joi.number(), Joi.string()]).required(),
|
||||
},
|
||||
],
|
||||
method(limit) {
|
||||
return this.$_addRule({ name: 'min', args: { limit } });
|
||||
},
|
||||
validate(value, { error }, args) {
|
||||
const limit = ensureDuration(args.limit);
|
||||
if (value.asMilliseconds() < limit.asMilliseconds()) {
|
||||
return error('duration.min', { value, limit });
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
},
|
||||
max: {
|
||||
args: [
|
||||
{
|
||||
name: 'limit',
|
||||
assert: Joi.alternatives([Joi.number(), Joi.string()]).required(),
|
||||
},
|
||||
],
|
||||
method(limit) {
|
||||
return this.$_addRule({ name: 'max', args: { limit } });
|
||||
},
|
||||
validate(value, { error }, args) {
|
||||
const limit = ensureDuration(args.limit);
|
||||
if (value.asMilliseconds() > limit.asMilliseconds()) {
|
||||
return error('duration.max', { value, limit });
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { duration as momentDuration } from 'moment';
|
||||
import { schema } from '../..';
|
||||
import { ensureDuration } from '../duration';
|
||||
|
||||
const { duration, object, contextRef, siblingRef } = schema;
|
||||
|
||||
|
@ -135,6 +136,28 @@ describe('#defaultValue', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#min', () => {
|
||||
it('returns the value when larger', () => {
|
||||
expect(duration({ min: '5m' }).validate('7m')).toEqual(ensureDuration('7m'));
|
||||
});
|
||||
it('throws error when value is smaller', () => {
|
||||
expect(() => duration({ min: '5m' }).validate('3m')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Value must be equal to or greater than [PT5M]"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#max', () => {
|
||||
it('returns the value when smaller', () => {
|
||||
expect(duration({ max: '10d' }).validate('7d')).toEqual(ensureDuration('7d'));
|
||||
});
|
||||
it('throws error when value is greater', () => {
|
||||
expect(() => duration({ max: '10h' }).validate('17h')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Value must be equal to or less than [PT10H]"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('returns error when not valid string or non-safe positive integer', () => {
|
||||
expect(() => duration().validate(-123)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Value in milliseconds is expected to be a safe positive integer."`
|
||||
|
|
|
@ -13,12 +13,14 @@ import { internals } from '../internals';
|
|||
import { Reference } from '../references';
|
||||
import { Type } from './type';
|
||||
|
||||
type DurationValueType = Duration | string | number;
|
||||
export type DurationValueType = Duration | string | number;
|
||||
|
||||
export interface DurationOptions {
|
||||
// we need to special-case defaultValue as we want to handle string inputs too
|
||||
defaultValue?: DurationValueType | Reference<DurationValueType> | (() => DurationValueType);
|
||||
validate?: (value: Duration) => string | void;
|
||||
min?: DurationValueType;
|
||||
max?: DurationValueType;
|
||||
}
|
||||
|
||||
export class DurationType extends Type<Duration> {
|
||||
|
@ -36,16 +38,32 @@ export class DurationType extends Type<Duration> {
|
|||
defaultValue = options.defaultValue;
|
||||
}
|
||||
|
||||
super(internals.duration(), { ...options, defaultValue });
|
||||
let schema = internals.duration();
|
||||
if (options.min) {
|
||||
schema = schema.min(options.min);
|
||||
}
|
||||
if (options.max) {
|
||||
schema = schema.max(options.max);
|
||||
}
|
||||
|
||||
super(schema, { validate: options.validate, defaultValue });
|
||||
}
|
||||
|
||||
protected handleError(type: string, { message, value }: Record<string, any>, path: string[]) {
|
||||
protected handleError(
|
||||
type: string,
|
||||
{ message, value, limit }: Record<string, any>,
|
||||
path: string[]
|
||||
) {
|
||||
switch (type) {
|
||||
case 'any.required':
|
||||
case 'duration.base':
|
||||
return `expected value of type [moment.Duration] but got [${typeDetect(value)}]`;
|
||||
case 'duration.parse':
|
||||
return new SchemaTypeError(message, path);
|
||||
case 'duration.min':
|
||||
return `Value must be equal to or greater than [${limit.toString()}]`;
|
||||
case 'duration.max':
|
||||
return `Value must be equal to or less than [${limit.toString()}]`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
11
packages/kbn-config-schema/types/joi.d.ts
vendored
11
packages/kbn-config-schema/types/joi.d.ts
vendored
|
@ -7,7 +7,8 @@
|
|||
*/
|
||||
|
||||
import * as Joi from 'joi';
|
||||
import { ByteSizeValue } from '../src/byte_size_value';
|
||||
import type { ByteSizeValue } from '../src/byte_size_value';
|
||||
import type { DurationValueType } from '../src/types/duration_type';
|
||||
|
||||
declare module 'joi' {
|
||||
interface BytesSchema extends AnySchema {
|
||||
|
@ -16,6 +17,12 @@ declare module 'joi' {
|
|||
max(limit: number | string | ByteSizeValue): this;
|
||||
}
|
||||
|
||||
interface DurationSchema extends AnySchema {
|
||||
min(limit: DurationValueType): this;
|
||||
|
||||
max(limit: DurationValueType): this;
|
||||
}
|
||||
|
||||
interface MapSchema extends AnySchema {
|
||||
entries(key: AnySchema, value: AnySchema): this;
|
||||
}
|
||||
|
@ -34,7 +41,7 @@ declare module 'joi' {
|
|||
|
||||
export type JoiRoot = Joi.Root & {
|
||||
bytes: () => BytesSchema;
|
||||
duration: () => AnySchema;
|
||||
duration: () => DurationSchema;
|
||||
map: () => MapSchema;
|
||||
record: () => RecordSchema;
|
||||
stream: () => AnySchema;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue