diff --git a/README.md b/README.md index b8b83a8..340154b 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ const client = createFeatureFlagClient({ ### Add properties to client -Secondly we want add some attributes we can use in the condition in each of the feature flags we are creating later. You are allowed to use `string`, `number` or `boolean` types as a property +Secondly we want add some attributes we can use in the condition(s) in each of the feature flags we are creating later. You are allowed to use `string`, `number` or `boolean` types as a property ```ts import { createFeatureFlagClient } from "toggle-kit"; @@ -82,7 +82,7 @@ const client = createFeatureFlagClient({ ### Create feature flag -Last but not least, we want to create our first flag. Here we specify a name for the feature flag and select the type of condition we want to evaluate upon. Then we select the property we want to evaluate, and an expected value. +Last but not least, we want to create our first flag. Here we specify a name for the feature flag and select the type of condition(s) we want to evaluate upon. Then we select the property we want to evaluate, and an expected value. ```ts import { createFeatureFlagClient } from "toggle-kit"; @@ -97,11 +97,13 @@ const client = createFeatureFlagClient({ flags: [ { name: "secret-page", - condition: { - type: "equal", - attribute: "email", - expectedValue: "test@example.com", - }, + conditions: [ + { + type: "equal", + attribute: "email", + expectedValue: "test@example.com", + }, + ], }, ], }); @@ -124,11 +126,13 @@ const client = createFeatureFlagClient({ flags: [ { name: "secret-page", - condition: { - type: "equal", - attribute: "email", - expectedValue: "test@example.com", - }, + conditions: [ + { + type: "equal", + attribute: "email", + expectedValue: "test@example.com", + }, + ], }, ], }); diff --git a/docs/conditions/contains.md b/docs/conditions/contains.md index 3f9553f..0d9d788 100644 --- a/docs/conditions/contains.md +++ b/docs/conditions/contains.md @@ -13,19 +13,23 @@ const client = createFeatureFlagClient({ flags: [ { name: "is-admin", - condition: { - type: "contains", - attribute: "roles", - expectedValue: "admin", - }, + conditions: [ + { + type: "contains", + attribute: "roles", + expectedValue: "admin", + }, + ], }, { name: "is-gmail", - condition: { - type: "contains", - attribute: "email", - expectedValue: "@gmail.com", - }, + conditions: [ + { + type: "contains", + attribute: "email", + expectedValue: "@gmail.com", + }, + ], }, ], }); diff --git a/docs/conditions/endsWith.md b/docs/conditions/endsWith.md index c2d1065..1635792 100644 --- a/docs/conditions/endsWith.md +++ b/docs/conditions/endsWith.md @@ -12,19 +12,23 @@ const client = createFeatureFlagClient({ flags: [ { name: "is-example-mail", - condition: { - type: "endsWith", - attribute: "email", - expectedValue: "@example.com", - }, + conditions: [ + { + type: "endsWith", + attribute: "email", + expectedValue: "@example.com", + }, + ], }, { name: "is-google-mail", - condition: { - type: "endsWith", - attribute: "email", - expectedValue: "@gmail.com", - }, + conditions: [ + { + type: "endsWith", + attribute: "email", + expectedValue: "@gmail.com", + }, + ], }, ], }); diff --git a/docs/conditions/equal.md b/docs/conditions/equal.md index f90f7e9..e98750b 100644 --- a/docs/conditions/equal.md +++ b/docs/conditions/equal.md @@ -12,19 +12,23 @@ const client = createFeatureFlagClient({ flags: [ { name: "is-john", - condition: { - type: "equal", - attribute: "username", - expectedValue: "JohnDoe", - }, + conditions: [ + { + type: "equal", + attribute: "username", + expectedValue: "JohnDoe", + }, + ], }, { name: "is-jane", - condition: { - type: "equal", - attribute: "username", - expectedValue: "JaneDoe", - }, + conditions: [ + { + type: "equal", + attribute: "username", + expectedValue: "JaneDoe", + }, + ], }, ], }); diff --git a/docs/conditions/greaterThan.md b/docs/conditions/greaterThan.md index eca1f3f..3eb28fd 100644 --- a/docs/conditions/greaterThan.md +++ b/docs/conditions/greaterThan.md @@ -12,11 +12,13 @@ const client = createFeatureFlagClient({ flags: [ { name: "is-adult", - condition: { - type: "greaterThan", - attribute: "age", - expectedValue: 20, - }, + conditions: [ + { + type: "greaterThan", + attribute: "age", + expectedValue: 20, + }, + ], }, ], }); diff --git a/docs/conditions/lessThan.md b/docs/conditions/lessThan.md index 9e09c65..d7c7304 100644 --- a/docs/conditions/lessThan.md +++ b/docs/conditions/lessThan.md @@ -12,11 +12,13 @@ const client = createFeatureFlagClient({ flags: [ { name: "is-child", - condition: { - type: "lessThan", - attribute: "age", - expectedValue: 21, - }, + conditions: [ + { + type: "lessThan", + attribute: "age", + expectedValue: 21, + }, + ], }, ], }); diff --git a/docs/conditions/percentage.md b/docs/conditions/percentage.md index 739719d..d083499 100644 --- a/docs/conditions/percentage.md +++ b/docs/conditions/percentage.md @@ -12,11 +12,13 @@ const client = createFeatureFlagClient({ flags: [ { name: "is-lucky", - condition: { - type: "percentage", - attribute: "userId", - expectedValue: 50, - }, + conditions: [ + { + type: "percentage", + attribute: "userId", + expectedValue: 50, + }, + ], }, ], }); diff --git a/docs/conditions/regex.md b/docs/conditions/regex.md index 3a6944b..152f8c7 100644 --- a/docs/conditions/regex.md +++ b/docs/conditions/regex.md @@ -12,11 +12,13 @@ const client = createFeatureFlagClient({ flags: [ { name: "is-gmail", - condition: { - type: "regex", - attribute: "email", - expectedValue: /.+@gmail\.com/, - }, + conditions: [ + { + type: "regex", + attribute: "email", + expectedValue: /.+@gmail\.com/, + }, + ], }, ], }); diff --git a/docs/conditions/startsWith.md b/docs/conditions/startsWith.md index 5a4f925..734fe05 100644 --- a/docs/conditions/startsWith.md +++ b/docs/conditions/startsWith.md @@ -12,11 +12,13 @@ const client = createFeatureFlagClient({ flags: [ { name: "is-john", - condition: { - type: "startsWith", - attribute: "username", - expectedValue: "john", - }, + conditions: [ + { + type: "startsWith", + attribute: "username", + expectedValue: "john", + }, + ], }, ], }); diff --git a/docs/usages/simple.md b/docs/usages/simple.md index 7b48bf6..fb8bd3b 100644 --- a/docs/usages/simple.md +++ b/docs/usages/simple.md @@ -15,11 +15,13 @@ const client = createFeatureFlagClient({ flags: [ { name: "secret-page", // No autocompletion - condition: { - type: "equal", - attribute: "email", // No autocompletion - expectedValue: "test@example.com", - }, + conditions: [ + { + type: "equal", + attribute: "email", // No autocompletion + expectedValue: "test@example.com", + }, + ], }, ], }); diff --git a/docs/usages/structured.md b/docs/usages/structured.md index 40ce93b..6859382 100644 --- a/docs/usages/structured.md +++ b/docs/usages/structured.md @@ -39,11 +39,13 @@ type FlagNames = "secret-page"; const flags: FeatureFlag[] = [ { name: "secret-page", // <--- Autocompletion - condition: { - type: "equal", - attribute: "email", // <--- Autocompletion - expectedValue: "test@example.com", - }, + conditions: [ + { + type: "equal", + attribute: "email", // <--- Autocompletion + expectedValue: "test@example.com", + }, + ], }, ]; diff --git a/docs/usages/typed.md b/docs/usages/typed.md index 2edb1a9..3e6b110 100644 --- a/docs/usages/typed.md +++ b/docs/usages/typed.md @@ -28,7 +28,7 @@ const client = createFeatureFlagClient({ flags: [ { name: "secret-page", // <--- Autocompletion - condition: { + conditions: { type: "equal", attribute: "email", // <--- Autocompletion expectedValue: "test@example.com", @@ -62,7 +62,7 @@ const client = createFeatureFlagClient({ flags: [ { name: "secret-page", // <--- Autocompletion - condition: { + conditions: { type: "equal", attribute: "email", // <--- No Autocompletion expectedValue: "test@example.com", @@ -94,7 +94,7 @@ const client = createFeatureFlagClient({ flags: [ { name: "secret-page", // <--- No Autocompletion - condition: { + conditions: { type: "equal", attribute: "email", // <--- No Autocompletion expectedValue: "test@example.com", diff --git a/src/conditions/contains/contains.test.ts b/src/conditions/contains/contains.test.ts index d354f0b..18a4295 100644 --- a/src/conditions/contains/contains.test.ts +++ b/src/conditions/contains/contains.test.ts @@ -28,11 +28,13 @@ describe("Condition - Contains", () => { flags: [ { name: "admin-dashboard", - condition: { - type: "contains", - attribute: "roles", - expectedValue: "admin", - }, + conditions: [ + { + type: "contains", + attribute: "roles", + expectedValue: "admin", + }, + ], }, ], }); diff --git a/src/conditions/endsWith/endsWith.test.ts b/src/conditions/endsWith/endsWith.test.ts index db6d015..4905076 100644 --- a/src/conditions/endsWith/endsWith.test.ts +++ b/src/conditions/endsWith/endsWith.test.ts @@ -28,11 +28,13 @@ describe("Condition - Ends With", () => { flags: [ { name: "example-emails", - condition: { - type: "endsWith", - attribute: "email", - expectedValue: "example.com", - }, + conditions: [ + { + type: "endsWith", + attribute: "email", + expectedValue: "example.com", + }, + ], }, ], }); diff --git a/src/conditions/equal/equal.test.ts b/src/conditions/equal/equal.test.ts index 46930f4..1a95a7f 100644 --- a/src/conditions/equal/equal.test.ts +++ b/src/conditions/equal/equal.test.ts @@ -28,11 +28,13 @@ describe("Condition - Equal", () => { flags: [ { name: "is-admin", - condition: { - type: "equal", - attribute: "isAdmin", - expectedValue: true, - }, + conditions: [ + { + type: "equal", + attribute: "isAdmin", + expectedValue: true, + }, + ], }, ], }); diff --git a/src/conditions/greaterThan/greaterThan.test.ts b/src/conditions/greaterThan/greaterThan.test.ts index bcf696a..7a956d1 100644 --- a/src/conditions/greaterThan/greaterThan.test.ts +++ b/src/conditions/greaterThan/greaterThan.test.ts @@ -36,11 +36,13 @@ describe("Condition - Greater Than", () => { flags: [ { name: "is-adult", - condition: { - type: "greaterThan", - attribute: "age", - expectedValue: 21, - }, + conditions: [ + { + type: "greaterThan", + attribute: "age", + expectedValue: 21, + }, + ], }, ], }); diff --git a/src/conditions/lessThan/lessThan.test.ts b/src/conditions/lessThan/lessThan.test.ts index c099582..84e05ec 100644 --- a/src/conditions/lessThan/lessThan.test.ts +++ b/src/conditions/lessThan/lessThan.test.ts @@ -36,11 +36,13 @@ describe("Condition - Less Than", () => { flags: [ { name: "is-teenager", - condition: { - type: "lessThan", - attribute: "age", - expectedValue: 21, - }, + conditions: [ + { + type: "lessThan", + attribute: "age", + expectedValue: 21, + }, + ], }, ], }); diff --git a/src/conditions/percentage/percentage.test.ts b/src/conditions/percentage/percentage.test.ts index 1544f63..6935502 100644 --- a/src/conditions/percentage/percentage.test.ts +++ b/src/conditions/percentage/percentage.test.ts @@ -57,11 +57,13 @@ describe("Condition - Percentage", () => { flags: [ { name: "new-feature", - condition: { - type: "percentage", - attribute: "userId", - expectedValue: 50, - }, + conditions: [ + { + type: "percentage", + attribute: "userId", + expectedValue: 50, + }, + ], }, ], }); diff --git a/src/conditions/regex/regex.test.ts b/src/conditions/regex/regex.test.ts index fb41b9d..0e8606e 100644 --- a/src/conditions/regex/regex.test.ts +++ b/src/conditions/regex/regex.test.ts @@ -36,11 +36,13 @@ describe("Condition - Regex", () => { flags: [ { name: "example-page", - condition: { - type: "regex", - attribute: "email", - expectedValue: /.+\@example.com/, - }, + conditions: [ + { + type: "regex", + attribute: "email", + expectedValue: /.+\@example.com/, + }, + ], }, ], }); diff --git a/src/conditions/startsWith/startsWith.test.ts b/src/conditions/startsWith/startsWith.test.ts index 02c74ad..28540c2 100644 --- a/src/conditions/startsWith/startsWith.test.ts +++ b/src/conditions/startsWith/startsWith.test.ts @@ -28,11 +28,13 @@ describe("Condition - Starts With", () => { flags: [ { name: "is-john", - condition: { - type: "startsWith", - attribute: "username", - expectedValue: "John", - }, + conditions: [ + { + type: "startsWith", + attribute: "username", + expectedValue: "John", + }, + ], }, ], }); diff --git a/src/helpers/isEnabled.ts b/src/helpers/isEnabled.ts index 38d8fbc..92f407a 100644 --- a/src/helpers/isEnabled.ts +++ b/src/helpers/isEnabled.ts @@ -25,37 +25,52 @@ export const isEnabled = ({ return false; } - const { attribute, expectedValue, type: conditionType } = flag.condition; + let isEnabled: boolean = false; + for (const condition of flag.conditions) { + if (isEnabled) return true; // Return fast - if (!(attribute in property)) { - return false; - } + const { attribute, expectedValue, type: conditionType } = condition; - const value = property[attribute]; - - switch (conditionType) { - case "equal": - return equalCondition({ value, expectedValue }); - case "contains": - return containsCondition({ value, expectedValue }); - case "startsWith": - return startsWithCondition({ value, expectedValue }); - case "endsWith": - return endsWithCondition({ value, expectedValue }); - case "percentage": - return percentageCondition({ - featureName, - value, - expectedValue, - }); - case "greaterThan": - return greaterThanCondition({ value, expectedValue }); - case "lessThan": - return lessThanCondition({ value, expectedValue }); - case "regex": - return regexCondition({ value, expectedValue }); - default: - conditionType satisfies never; + if (!(attribute in property)) { return false; + } + + const value = property[attribute]; + + switch (conditionType) { + case "equal": + isEnabled = equalCondition({ value, expectedValue }); + continue; + case "contains": + isEnabled = containsCondition({ value, expectedValue }); + continue; + case "startsWith": + isEnabled = startsWithCondition({ value, expectedValue }); + continue; + case "endsWith": + isEnabled = endsWithCondition({ value, expectedValue }); + continue; + case "percentage": + isEnabled = percentageCondition({ + featureName, + value, + expectedValue, + }); + continue; + case "greaterThan": + isEnabled = greaterThanCondition({ value, expectedValue }); + continue; + case "lessThan": + isEnabled = lessThanCondition({ value, expectedValue }); + continue; + case "regex": + isEnabled = regexCondition({ value, expectedValue }); + continue; + default: + conditionType satisfies never; + return false; + } } + + return isEnabled; }; diff --git a/src/index.test.ts b/src/index.test.ts index e3d0b55..b5dfa30 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -9,11 +9,13 @@ describe("End-To-End Tests - Library Tests", () => { flags: [ { name: "secret-page", - condition: { - type: "equal", - attribute: "email", - expectedValue: "test@example.com", - }, + conditions: [ + { + type: "equal", + attribute: "email", + expectedValue: "test@example.com", + }, + ], }, ], }); @@ -28,11 +30,13 @@ describe("End-To-End Tests - Library Tests", () => { flags: [ { name: "discount-price", - condition: { - type: "equal", - attribute: "price", - expectedValue: 100, - }, + conditions: [ + { + type: "equal", + attribute: "price", + expectedValue: 100, + }, + ], }, ], }); @@ -47,11 +51,40 @@ describe("End-To-End Tests - Library Tests", () => { flags: [ { name: "admin-dashboard", - condition: { - type: "equal", - attribute: "isAdmin", - expectedValue: true, - }, + conditions: [ + { + type: "equal", + attribute: "isAdmin", + expectedValue: true, + }, + ], + }, + ], + }); + expect(client.isEnabled("admin-dashboard")).toBe(true); + }); + + it("should return true when feature flag contains multiple conditions", () => { + const client = createFeatureFlagClient({ + property: { + isAdmin: false, + roles: "moderator,admin", + }, + flags: [ + { + name: "admin-dashboard", + conditions: [ + { + type: "equal", + attribute: "isAdmin", + expectedValue: true, + }, + { + type: "contains", + attribute: "roles", + expectedValue: "admin", + }, + ], }, ], }); diff --git a/src/types/FeatureFlag.ts b/src/types/FeatureFlag.ts index 17c4e7c..db90a4d 100644 --- a/src/types/FeatureFlag.ts +++ b/src/types/FeatureFlag.ts @@ -2,5 +2,5 @@ import { Condition } from "./Condition"; export type FeatureFlag = { name: FlagNames; - condition: Condition; + conditions: Condition[]; };