From c3f1203a1f8f800e0fc4a52050a3f58d643b81fc Mon Sep 17 00:00:00 2001 From: Fredrik Oseberg Date: Thu, 24 Feb 2022 15:29:45 +0100 Subject: [PATCH] feat: add validation fields for constraint --- src/lib/schema/constraint-value-types.test.ts | 23 +++++++++++++++- src/lib/schema/constraint-value-types.ts | 2 ++ src/lib/schema/feature-schema.ts | 3 +++ src/lib/services/feature-toggle-service.ts | 26 ++++++++++++++----- src/lib/util/validators/constraint-types.ts | 9 ++++++- 5 files changed, 54 insertions(+), 9 deletions(-) diff --git a/src/lib/schema/constraint-value-types.test.ts b/src/lib/schema/constraint-value-types.test.ts index ba27913abf..7da57bd5d9 100644 --- a/src/lib/schema/constraint-value-types.test.ts +++ b/src/lib/schema/constraint-value-types.test.ts @@ -1,5 +1,9 @@ -import { constraintNumberTypeSchema } from './constraint-value-types'; +import { + constraintNumberTypeSchema, + constraintStringTypeSchema, +} from './constraint-value-types'; +/* Number type */ test('should require number', async () => { try { await constraintNumberTypeSchema.validateAsync('test'); @@ -23,3 +27,20 @@ test('should allow numbers', async () => { test('should allow negative numbers', async () => { await constraintNumberTypeSchema.validateAsync(-5); }); + +/* String types */ +test('should require a list of strings', async () => { + try { + await constraintStringTypeSchema.validateAsync(['test', 1]); + } catch (error) { + expect(error.details[0].message).toEqual('"[1]" must be a string'); + } +}); + +test('should succeed with a list of strings', async () => { + await constraintStringTypeSchema.validateAsync([ + 'test', + 'another-test', + 'supervalue', + ]); +}); diff --git a/src/lib/schema/constraint-value-types.ts b/src/lib/schema/constraint-value-types.ts index 8470f331a0..506c78f587 100644 --- a/src/lib/schema/constraint-value-types.ts +++ b/src/lib/schema/constraint-value-types.ts @@ -1,3 +1,5 @@ import joi from 'joi'; export const constraintNumberTypeSchema = joi.number(); + +export const constraintStringTypeSchema = joi.array().items(joi.string()); diff --git a/src/lib/schema/feature-schema.ts b/src/lib/schema/feature-schema.ts index 395d2c8e97..b04b071151 100644 --- a/src/lib/schema/feature-schema.ts +++ b/src/lib/schema/feature-schema.ts @@ -10,6 +10,9 @@ export const constraintSchema = joi.object().keys({ contextName: joi.string(), operator: joi.string(), values: joi.array().items(joi.string().min(1).max(100)).min(1).optional(), + value: joi.optional(), + caseInsensitive: joi.boolean().optional(), + inverted: joi.boolean().optional(), }); export const strategiesSchema = joi.object().keys({ diff --git a/src/lib/services/feature-toggle-service.ts b/src/lib/services/feature-toggle-service.ts index 44b15d0d40..0c52278f4a 100644 --- a/src/lib/services/feature-toggle-service.ts +++ b/src/lib/services/feature-toggle-service.ts @@ -6,6 +6,7 @@ import NameExistsError from '../error/name-exists-error'; import InvalidOperationError from '../error/invalid-operation-error'; import { FOREIGN_KEY_VIOLATION } from '../error/db-error'; import { + constraintSchema, featureMetadataSchema, nameSchema, variantsArraySchema, @@ -60,6 +61,7 @@ import { } from '../util/constants'; import { applyPatch, deepClone, Operation } from 'fast-json-patch'; import { OperationDeniedError } from '../error/operation-denied-error'; +import { validateNumber } from 'lib/util/validators/constraint-types'; interface IFeatureContext { featureName: string; @@ -151,23 +153,33 @@ class FeatureToggleService { } } - validateConstraint(constraint: IConstraint): void { + async validateConstraint(constraint: IConstraint): Promise { const { operator } = constraint; + await constraintSchema.validateAsync(constraint); + if (oneOf(NUM_OPERATORS, operator)) { // Validate number value + await validateNumber(constraint.value); + // 1. Retrieve context defintion based on constraint contextName + // 2. Check if value is a valid number / value type + // 3. Check the value against predefined legalValues if specified on the + // context definition } if (oneOf(STRING_OPERATORS, operator)) { // validate string values array + await validateString(constraint.values); } - if (oneOf(SEMVER_OPERATORS, operator)) { - // validate semver - } + // if (oneOf(SEMVER_OPERATORS, operator)) { + // // validate semver + // validateSemver(constraint.value) + // } - if (oneOf(DATE_OPERATORS, operator)) { - // validate dates - } + // if (oneOf(DATE_OPERATORS, operator)) { + // // validate dates + // validateDate(constraint.value); + // } } async patchFeature( diff --git a/src/lib/util/validators/constraint-types.ts b/src/lib/util/validators/constraint-types.ts index ebeccc13de..0d024715cc 100644 --- a/src/lib/util/validators/constraint-types.ts +++ b/src/lib/util/validators/constraint-types.ts @@ -1,5 +1,12 @@ -import { constraintNumberTypeSchema } from 'lib/schema/constraint-value-types'; +import { + constraintNumberTypeSchema, + constraintStringTypeSchema, +} from 'lib/schema/constraint-value-types'; export const validateNumber = async (value: unknown): Promise => { await constraintNumberTypeSchema.validateAsync(value); }; + +export const validateString = async (value: unknown): Promise => { + await constraintStringTypeSchema.validateAsync(value); +};