1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-11-01 19:07:38 +01:00
unleash.unleash/src/lib/schema/feature-schema.ts
Gastón Fournier 29be130757
fix: enforce weight precision to 1 decimal (#2749)
## About the changes
According to our docs, we only support up to 1 decimal place for
weights. This is to use integers to represent the percentages (we divide
them by 10) and supporting more decimals results in bad maths due to
floating point arithmetics.

This PRs adds Frontend and Backend validations to enforce this
restriction

Closes #2222

## Discussion points
Should we reconsider supporting more decimal places, that door remains
open, but for now we'll just adhere to our documentation because that
change would require some development.
2023-01-05 12:39:18 +01:00

163 lines
4.8 KiB
TypeScript

import joi from 'joi';
import { ALL_OPERATORS } from '../util/constants';
import { nameType } from '../routes/util';
import { validateJsonString } from '../util/validateJsonString';
export const nameSchema = joi
.object()
.keys({ name: nameType })
.options({ stripUnknown: true, allowUnknown: false, abortEarly: false });
export const constraintSchema = joi.object().keys({
contextName: joi.string().required(),
operator: joi
.string()
.valid(...ALL_OPERATORS)
.required(),
// Constraints must have a values array to support legacy SDKs.
values: joi.array().items(joi.string().min(1).max(100)).default([]),
value: joi.optional(),
caseInsensitive: joi.boolean().optional(),
inverted: joi.boolean().optional(),
});
export const strategiesSchema = joi.object().keys({
id: joi.string().optional(),
name: nameType,
constraints: joi.array().allow(null).items(constraintSchema),
parameters: joi.object(),
});
export const variantValueSchema = joi
.string()
.required()
// perform additional validation
// when provided 'type' is 'json'
.when('type', {
is: 'json',
then: joi.custom((val, helper) => {
const isValidJsonString = validateJsonString(val);
if (isValidJsonString === false) {
return helper.error('invalidJsonString');
}
return val;
}),
})
.messages({
invalidJsonString:
"'value' must be a valid json string when 'type' is json",
});
export const variantsSchema = joi.object().keys({
name: nameType,
weight: joi
.number()
.integer()
.message('Weight only supports 1 decimal')
.min(0)
.max(1000)
.required(),
weightType: joi.string().valid('variable', 'fix').default('variable'),
payload: joi
.object()
.keys({
type: joi.string().required(),
value: variantValueSchema,
})
.optional(),
stickiness: joi.string().default('default'),
overrides: joi.array().items(
joi
.object()
.keys({
contextName: joi.string().required(),
values: joi.array().items(joi.string()),
})
.optional(),
),
});
export const variantsArraySchema = joi
.array()
.min(0)
.items(variantsSchema)
.unique((a, b) => a.name === b.name);
export const featureMetadataSchema = joi
.object()
.keys({
name: nameType,
stale: joi.boolean().default(false),
archived: joi.boolean().default(false),
type: joi.string().default('release'),
description: joi.string().allow('').allow(null).optional(),
impressionData: joi
.boolean()
.allow(true)
.allow(false)
.default(false)
.optional(),
createdAt: joi.date().optional().allow(null),
variants: joi
.array()
.allow(null)
.unique((a, b) => a.name === b.name)
.optional()
.items(variantsSchema),
})
.options({ allowUnknown: false, stripUnknown: true, abortEarly: false });
export const featureSchema = joi
.object()
.keys({
name: nameType,
enabled: joi.boolean().default(false),
stale: joi.boolean().default(false),
archived: joi.boolean().default(false),
type: joi.string().default('release'),
project: joi.string().default('default'),
description: joi.string().allow('').allow(null).optional(),
impressionData: joi
.boolean()
.allow(true)
.allow(false)
.default(false)
.optional(),
strategies: joi
.array()
.min(0)
.allow(null)
.optional()
.items(strategiesSchema),
variants: joi
.array()
.allow(null)
.unique((a, b) => a.name === b.name)
.optional()
.items(variantsSchema),
})
.options({ allowUnknown: false, stripUnknown: true, abortEarly: false });
export const querySchema = joi
.object()
.keys({
tag: joi
.array()
.allow(null)
.items(joi.string().pattern(/\w+:.+/, { name: 'tag' }))
.optional(),
project: joi.array().allow(null).items(nameType).optional(),
namePrefix: joi.string().allow(null).optional(),
environment: joi.string().allow(null).optional(),
inlineSegmentConstraints: joi.boolean().optional(),
})
.options({ allowUnknown: false, stripUnknown: true, abortEarly: false });
export const featureTagSchema = joi.object().keys({
featureName: nameType,
tagType: nameType.optional(),
tagValue: joi.string(),
type: nameType.optional(),
value: joi.string().optional(),
});