Skip to content

Commit

Permalink
Allow passing NaN values to number/datetime comparisons
Browse files Browse the repository at this point in the history
  • Loading branch information
adams85 committed Nov 20, 2023
1 parent 911f8ea commit 60e3704
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 14 deletions.
19 changes: 9 additions & 10 deletions src/RolloutEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -751,11 +751,12 @@ function getUserAttributeValueAsSemVer(attributeName: string, attributeValue: Us
}

function getUserAttributeValueAsNumber(attributeName: string, attributeValue: UserAttributeValue, condition: UserConditionUnion, key: string, logger: LoggerWrapper): number | string {
const number =
typeof attributeValue === "number" ? attributeValue :
typeof attributeValue === "string" ? parseFloatStrict(attributeValue.replace(",", ".")) :
NaN;
if (!isNaN(number)) {
if (typeof attributeValue === "number") {
return attributeValue;
}
let number: number;
if (typeof attributeValue === "string"
&& (!isNaN(number = parseFloatStrict(attributeValue.replace(",", "."))) || attributeValue === "NaN")) {
return number;
}
return handleInvalidUserAttribute(logger, condition, key, attributeName, `'${attributeValue}' is not a valid decimal number`);
Expand All @@ -765,11 +766,9 @@ function getUserAttributeValueAsUnixTimeSeconds(attributeName: string, attribute
if (attributeValue instanceof Date) {
return attributeValue.getTime() / 1000;
}
const number =
typeof attributeValue === "number" ? attributeValue :
typeof attributeValue === "string" ? parseFloatStrict(attributeValue.replace(",", ".")) :
NaN;
if (!isNaN(number)) {
let number: number;
if (typeof attributeValue === "string"
&& (!isNaN(number = parseFloatStrict(attributeValue.replace(",", "."))) || attributeValue === "NaN")) {
return number;
}
return handleInvalidUserAttribute(logger, condition, key, attributeName, `'${attributeValue}' is not a valid Unix timestamp (number of seconds elapsed since Unix epoch)`);
Expand Down
42 changes: 40 additions & 2 deletions src/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ export type WellKnownUserObjectAttribute = "Identifier" | "Email" | "Country";

export type UserAttributeValue = string | number | Date | ReadonlyArray<string>;

/** User Object. Contains user attributes which are used for evaluating targeting rules and percentage options. */
/**
* User Object. Contains user attributes which are used for evaluating targeting rules and percentage options.
* @remarks
* Please note that the `User` class is not designed to be used as a DTO (data transfer object).
* (Since the type of the `custom` property is polymorphic, it's not guaranteed that deserializing a serialized instance produces an instance with an identical or even valid data content.)
**/
export class User {
constructor(
/** The unique identifier of the user or session (e.g. email address, primary key, session ID, etc.) */
Expand All @@ -11,7 +16,40 @@ export class User {
public email?: string,
/** Country of the user. */
public country?: string,
/** Custom attributes of the user for advanced targeting rule definitions (e.g. user role, subscription type, etc.) */
/**
* Custom attributes of the user for advanced targeting rule definitions (e.g. user role, subscription type, etc.)
* @remarks
* The set of allowed attribute values depends on the comparison type of the condition which references the User Object attribute.
* `string` values are supported by all comparison types (in some cases they need to be provided in a specific format though).
* Some of the comparison types work with other types of values, as descibed below.
*
* Text-based comparisons (EQUALS, IS ONE OF, etc.)<br/>
* * accept `string` values,<br/>
* * all other values are automatically converted to string (a warning will be logged but evaluation will continue as normal).
*
* SemVer-based comparisons (IS ONE OF, &lt;, &gt;=, etc.)<br/>
* * accept `string` values containing a properly formatted, valid semver value,<br/>
* * all other values are considered invalid (a warning will be logged and the currently evaluated targeting rule will be skipped).
*
* Number-based comparisons (=, &lt;, &gt;=, etc.)<br/>
* * accept `number` values,<br/>
* * accept `string` values containing a properly formatted, valid `number` value,<br/>
* * all other values are considered invalid (a warning will be logged and the currently evaluated targeting rule will be skipped).
*
* Date time-based comparisons (BEFORE / AFTER)<br/>
* * accept `Date` values, which are automatically converted to a second-based Unix timestamp,<br/>
* * accept `number` values representing a second-based Unix timestamp,<br/>
* * accept `string` values containing a properly formatted, valid `number` value,<br/>
* * all other values are considered invalid (a warning will be logged and the currently evaluated targeting rule will be skipped).
*
* String array-based comparisons (ARRAY CONTAINS ANY OF / ARRAY NOT CONTAINS ANY OF)<br/>
* * accept arrays of `string`,<br/>
* * accept `string` values containing a valid JSON string which can be deserialized to an array of `string`,<br/>
* * all other values are considered invalid (a warning will be logged and the currently evaluated targeting rule will be skipped).
*
* In case a non-string attribute value needs to be converted to `string` during evaluation, it will always be done using the same format
* which is accepted by the comparisons.
**/
public custom: { [key: string]: UserAttributeValue } = {}
) {
}
Expand Down
5 changes: 3 additions & 2 deletions test/ConfigV2EvaluationTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ describe("Setting evaluation (config v2)", () => {
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", 3, "<>4.2"],
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", 5, ">=5"],
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", Infinity, ">5"],
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", NaN, "80%"],
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", NaN, "<>4.2"],
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "-Infinity", "<2.1"],
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "-1", "<2.1"],
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "2", "<2.1"],
Expand All @@ -241,7 +241,8 @@ describe("Setting evaluation (config v2)", () => {
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "3", "<>4.2"],
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "5", ">=5"],
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "Infinity", ">5"],
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "NaN", "80%"],
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "NaN", "<>4.2"],
["configcat-sdk-1/PKDVCLf-Hq-h-kCzMp-L7Q/FCWN-k1dV0iBf8QZrDgjdw", "numberWithPercentage", "12345", "Custom1", "NaNa", "80%"],
// Date time-based comparisons
["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", new Date("2023-03-31T23:59:59.9990000Z"), false],
["configcat-sdk-1/JcPbCGl_1E-K9M-fJOyKyQ/OfQqcTjfFUGBwMKqtyEOrQ", "boolTrueIn202304", "12345", "Custom1", new Date("2023-04-01T01:59:59.9990000+02:00"), false],
Expand Down

0 comments on commit 60e3704

Please sign in to comment.