Skip to content

Commit 965d0b8

Browse files
yasirfolio3mikeproeng37
authored andcommitted
feat(Audience Evaluation): Audience Logging Fixes (#372)
1 parent a9b78c1 commit 965d0b8

File tree

8 files changed

+194
-66
lines changed

8 files changed

+194
-66
lines changed

OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.m

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ - (nullable NSNumber *)evaluateMatchTypeExact:(NSDictionary<NSString *, NSObject
5757

5858
// check if condition value is invalid
5959
if (![self.value isValidExactMatchTypeValue]) {
60+
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorUnsupportedValueType, self.stringRepresentation];
61+
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelWarning];
6062
return NULL;
6163
}
6264
// check if attributes exists
@@ -65,28 +67,36 @@ - (nullable NSNumber *)evaluateMatchTypeExact:(NSDictionary<NSString *, NSObject
6567
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelDebug];
6668
return NULL;
6769
}
68-
// check if attribute value is invalid
69-
NSObject *userAttribute = [attributes objectForKey:self.name];
70-
if (![userAttribute isValidExactMatchTypeValue]) {
71-
// Log Invalid Attribute Value Type
72-
NSString *userAttributeClassName = NSStringFromClass([userAttribute class]) ?: @"null";
73-
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedType, self.stringRepresentation, userAttributeClassName, self.name];
74-
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelWarning];
75-
return NULL;
76-
}
7770

71+
NSObject *userAttribute = [attributes objectForKey:self.name];
7872
if ([self.value isValidStringType] && [userAttribute isValidStringType]) {
7973
return [NSNumber numberWithBool:[self.value isEqual:userAttribute]];
8074
}
81-
else if ([self.value isValidNumericAttributeValue] && [userAttribute isValidNumericAttributeValue]) {
82-
return [NSNumber numberWithBool:[self.value isEqual:userAttribute]];
75+
else if ([self.value isNumericAttributeValue] && [userAttribute isNumericAttributeValue]) {
76+
if ([userAttribute isFiniteNumber]) {
77+
return [NSNumber numberWithBool:[self.value isEqual:userAttribute]];
78+
}
79+
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedTypeNanInfinity, self.stringRepresentation, self.name];
80+
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelDebug];
81+
return NULL;
8382
}
8483
else if ([self.value isKindOfClass:[NSNull class]] && [userAttribute isKindOfClass:[NSNull class]]) {
8584
return [NSNumber numberWithBool:[self.value isEqual:userAttribute]];
8685
}
8786
else if ([self.value isValidBooleanAttributeValue] && [userAttribute isValidBooleanAttributeValue]) {
8887
return [NSNumber numberWithBool:[self.value isEqual:userAttribute]];
8988
}
89+
90+
// Log Invalid Attribute Value Type
91+
if ([userAttribute class] != nil) {
92+
NSString *userAttributeClassName = NSStringFromClass([userAttribute class]);
93+
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedType, self.stringRepresentation, userAttributeClassName, self.name];
94+
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelWarning];
95+
}
96+
else {
97+
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedTypeNull, self.stringRepresentation, self.name];
98+
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelDebug];
99+
}
90100
return NULL;
91101
}
92102

@@ -99,7 +109,9 @@ - (nullable NSNumber *)evaluateMatchTypeSubstring:(NSDictionary<NSString *, NSOb
99109
// check if user attributes contain our value as substring
100110

101111
// check if condition value is invalid
102-
if (self.value == nil || [self.value isKindOfClass:[NSNull class]] || ![self.value isKindOfClass: [NSString class]]) {
112+
if (![self.value isValidStringType]) {
113+
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorUnsupportedValueType, self.stringRepresentation];
114+
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelWarning];
103115
return NULL;
104116
}
105117
// check if attributes exists
@@ -114,7 +126,7 @@ - (nullable NSNumber *)evaluateMatchTypeSubstring:(NSDictionary<NSString *, NSOb
114126
// Log Invalid Attribute Value Type
115127
if (!userAttribute || [userAttribute isKindOfClass:[NSNull class]]) {
116128
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedTypeNull, self.stringRepresentation, self.name];
117-
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelWarning];
129+
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelDebug];
118130
}
119131
else {
120132
NSString *userAttributeClassName = NSStringFromClass([userAttribute class]);
@@ -132,7 +144,9 @@ - (nullable NSNumber *)evaluateMatchTypeGreaterThan:(NSDictionary<NSString *, NS
132144
// check if user attributes contain a value greater than our value
133145

134146
// check if condition value is invalid
135-
if (self.value == nil || [self.value isKindOfClass:[NSNull class]] || ![self.value isValidNumericAttributeValue]) {
147+
if (![self.value isValidGTLTMatchTypeValue]) {
148+
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorUnsupportedValueType, self.stringRepresentation];
149+
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelWarning];
136150
return NULL;
137151
}
138152
// check if attributes exists
@@ -143,11 +157,11 @@ - (nullable NSNumber *)evaluateMatchTypeGreaterThan:(NSDictionary<NSString *, NS
143157
}
144158
// check if user attributes are invalid
145159
NSObject *userAttribute = [attributes objectForKey:self.name];
146-
if (![userAttribute isValidNumericAttributeValue]) {
160+
if (![userAttribute isNumericAttributeValue]) {
147161
// Log Invalid Attribute Value Type
148162
if (!userAttribute || [userAttribute isKindOfClass:[NSNull class]]) {
149163
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedTypeNull, self.stringRepresentation, self.name];
150-
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelWarning];
164+
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelDebug];
151165
}
152166
else {
153167
NSString *userAttributeClassName = NSStringFromClass([userAttribute class]);
@@ -156,6 +170,11 @@ - (nullable NSNumber *)evaluateMatchTypeGreaterThan:(NSDictionary<NSString *, NS
156170
}
157171
return NULL;
158172
}
173+
if (![userAttribute isFiniteNumber]) {
174+
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedTypeNanInfinity, self.stringRepresentation, self.name];
175+
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelDebug];
176+
return NULL;
177+
}
159178

160179
NSNumber *ourValue = (NSNumber *)self.value;
161180
NSNumber *userValue = (NSNumber *)userAttribute;
@@ -167,6 +186,8 @@ - (nullable NSNumber *)evaluateMatchTypeLessThan:(NSDictionary<NSString *, NSObj
167186

168187
// check if condition value is invalid
169188
if (![self.value isValidGTLTMatchTypeValue]) {
189+
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorUnsupportedValueType, self.stringRepresentation];
190+
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelWarning];
170191
return NULL;
171192
}
172193
// check if attributes exists
@@ -177,11 +198,11 @@ - (nullable NSNumber *)evaluateMatchTypeLessThan:(NSDictionary<NSString *, NSObj
177198
}
178199
// check if user attributes are invalid
179200
NSObject *userAttribute = [attributes objectForKey:self.name];
180-
if (![userAttribute isValidNumericAttributeValue]) {
201+
if (![userAttribute isNumericAttributeValue]) {
181202
// Log Invalid Attribute Value Type
182203
if (!userAttribute || [userAttribute isKindOfClass:[NSNull class]]) {
183204
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedTypeNull, self.stringRepresentation, self.name];
184-
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelWarning];
205+
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelDebug];
185206
}
186207
else {
187208
NSString *userAttributeClassName = NSStringFromClass([userAttribute class]);
@@ -190,6 +211,11 @@ - (nullable NSNumber *)evaluateMatchTypeLessThan:(NSDictionary<NSString *, NSObj
190211
}
191212
return NULL;
192213
}
214+
if (![userAttribute isFiniteNumber]) {
215+
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedTypeNanInfinity, self.stringRepresentation, self.name];
216+
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelDebug];
217+
return NULL;
218+
}
193219

194220
NSNumber *ourValue = (NSNumber *)self.value;
195221
NSNumber *userValue = (NSNumber *)userAttribute;
@@ -206,6 +232,8 @@ - (nullable NSNumber *)evaluateCustomMatchType:(NSDictionary<NSString *, NSObjec
206232
}
207233
else if (self.value == NULL && ![self.match isEqualToString:OPTLYDatafileKeysMatchTypeExists]){
208234
//Check if given value is null, which is only acceptable if match type is Exists
235+
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorUnsupportedValueType, self.stringRepresentation];
236+
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelWarning];
209237
return NULL;
210238
}
211239
if (!self.match || [self.match isEqualToString:@""]){

OptimizelySDKCore/OptimizelySDKCore/OPTLYDecisionService.m

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -487,14 +487,15 @@ - (BOOL)evaluateAudienceConditionsForExperiment:(OPTLYExperiment *)experiment
487487
// Log Experiment Evaluation Started
488488
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorEvaluationStartedForExperiment, experiment.experimentKey, [experiment getAudienceConditionsString]];
489489
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelDebug];
490-
BOOL areAttributesValid = [[experiment evaluateConditionsWithAttributes:attributes projectConfig:config] boolValue];
490+
NSNumber *result = [experiment evaluateConditionsWithAttributes:attributes projectConfig:config];
491+
491492
// Log Evaluation Result
492-
logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorExperimentEvaluationCompletedWithResult, experiment.experimentKey, areAttributesValid ? @"TRUE" : @"FALSE"];
493+
logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorExperimentEvaluationCompletedWithResult, experiment.experimentKey, ([result boolValue] ? @"TRUE" : @"FALSE")];
493494
[config.logger logMessage:logMessage withLevel:OptimizelyLogLevelInfo];
494-
if (areAttributesValid) {
495-
return areAttributesValid;
495+
if (result == nil) {
496+
return false;
496497
}
497-
return false;
498+
return [result boolValue];
498499
}
499500

500501
// Returns true if evaluation should be done using audience conditions, else returns false

OptimizelySDKCore/OptimizelySDKCore/OPTLYLoggerMessages.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,15 @@ extern NSString *const OPTLYLoggerMessagesAudienceEvaluatorEvaluationCompletedWi
263263
extern NSString *const OPTLYLoggerMessagesAudienceEvaluatorExperimentEvaluationCompletedWithResult;
264264
// warning
265265
extern NSString *const OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedType;
266-
extern NSString *const OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedTypeNull;
267266
extern NSString *const OPTLYLoggerMessagesAudienceEvaluatorUnknownMatchType;
268267
extern NSString *const OPTLYLoggerMessagesAudienceEvaluatorUnknownConditionType;
268+
extern NSString *const OPTLYLoggerMessagesAudienceEvaluatorUnsupportedValueType;
269269
// debug
270270
extern NSString *const OPTLYLoggerMessagesAudienceEvaluatorEvaluationStartedWithConditions;
271271
extern NSString *const OPTLYLoggerMessagesAudienceEvaluatorEvaluationStartedForExperiment;
272272
extern NSString *const OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForMissingAttribute;
273+
extern NSString *const OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedTypeNull;
274+
extern NSString *const OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedTypeNanInfinity;
273275
// error
274276

275277
@interface OPTLYLoggerMessages : NSObject

OptimizelySDKCore/OptimizelySDKCore/OPTLYLoggerMessages.m

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,13 +257,15 @@
257257
NSString *const OPTLYLoggerMessagesAudienceEvaluatorExperimentEvaluationCompletedWithResult = @"[AUDIENCE EVALUATOR] Audiences for experiment %@ collectively evaluated to %@.";
258258
// warning
259259
NSString *const OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedType = @"[AUDIENCE EVALUATOR] Audience condition %@ evaluated to UNKNOWN because a value of type %@ was passed for user attribute %@.";
260-
NSString *const OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedTypeNull = @"[AUDIENCE EVALUATOR] Audience condition %@ evaluated to UNKNOWN because a null value was passed for user attribute %@.";
261260
NSString *const OPTLYLoggerMessagesAudienceEvaluatorUnknownMatchType = @"[AUDIENCE EVALUATOR] Audience condition %@ uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK.";
262261
NSString *const OPTLYLoggerMessagesAudienceEvaluatorUnknownConditionType = @"[AUDIENCE EVALUATOR] Audience condition %@ has an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK.";
262+
NSString *const OPTLYLoggerMessagesAudienceEvaluatorUnsupportedValueType = @"[AUDIENCE EVALUATOR] Audience condition %@ has an unsupported condition value. You may need to upgrade to a newer release of the Optimizely SDK.";
263263
// debug
264264
NSString *const OPTLYLoggerMessagesAudienceEvaluatorEvaluationStartedWithConditions = @"[AUDIENCE EVALUATOR] Starting to evaluate audience %@ with conditions: %@.";
265265
NSString *const OPTLYLoggerMessagesAudienceEvaluatorEvaluationStartedForExperiment = @"[AUDIENCE EVALUATOR] Evaluating audiences for experiment %@: %@.";
266266
NSString *const OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForMissingAttribute = @"[AUDIENCE EVALUATOR] Audience condition %@ evaluated to UNKNOWN because no value was passed for user attribute %@.";
267+
NSString *const OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedTypeNull = @"[AUDIENCE EVALUATOR] Audience condition %@ evaluated to UNKNOWN because a null value was passed for user attribute %@.";
268+
NSString *const OPTLYLoggerMessagesAudienceEvaluatorConditionEvaluatedAsUnknownForUnexpectedTypeNanInfinity = @"[AUDIENCE EVALUATOR] Audience condition %@ evaluated to UNKNOWN because the number value for user attribute %@ is not in the range [-2^53, +2^53].";
267269
// error
268270

269271
@implementation OPTLYLoggerMessages

OptimizelySDKCore/OptimizelySDKCore/OPTLYNSObject+Validation.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,18 @@ NS_ASSUME_NONNULL_BEGIN
105105
- (BOOL)isValidBooleanAttributeValue;
106106

107107
/**
108-
* Returns if object is a valid numeric attribute
108+
* Returns if object is a numeric attribute
109109
*
110-
* @returns A Bool whether object is a valid numeric attribute.
110+
* @returns A Bool whether object is a numeric attribute.
111111
**/
112-
- (BOOL)isValidNumericAttributeValue;
112+
- (BOOL)isNumericAttributeValue;
113+
114+
/**
115+
* Returns if object is a finite number
116+
*
117+
* @returns A Bool whether object is a finite number.
118+
**/
119+
- (BOOL)isFiniteNumber;
113120

114121
@end
115122

OptimizelySDKCore/OptimizelySDKCore/OPTLYNSObject+Validation.m

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,15 @@ - (NSString *)getJSONArrayStringOrEmpty {
8181
- (BOOL)isValidExactMatchTypeValue {
8282
//Check if given object is acceptable exact match type value
8383
if (self) {
84-
return ([self isKindOfClass:[NSString class]] || [self isValidNumericAttributeValue] || [self isKindOfClass:[NSNull class]] || [self isValidBooleanAttributeValue]);
84+
return ([self isKindOfClass:[NSString class]] || ([self isNumericAttributeValue] && [self isFiniteNumber]) || [self isKindOfClass:[NSNull class]] || [self isValidBooleanAttributeValue]);
8585
}
8686
return false;
8787
}
8888

8989
- (BOOL)isValidGTLTMatchTypeValue {
9090
//Check if given object is acceptable GT or LT match type value
9191
if (self) {
92-
return (self != nil && ![self isKindOfClass:[NSNull class]] && [self isValidNumericAttributeValue]);
92+
return (self != nil && ![self isKindOfClass:[NSNull class]] && [self isNumericAttributeValue] && [self isFiniteNumber]);
9393
}
9494
return false;
9595
}
@@ -146,7 +146,7 @@ - (BOOL)isValidAttributeValue {
146146
return true;
147147
}
148148
//check value is valid numeric attribute
149-
if ([self isValidNumericAttributeValue]) {
149+
if ([self isNumericAttributeValue] && [self isFiniteNumber]) {
150150
return true;
151151
}
152152
}
@@ -164,27 +164,19 @@ - (BOOL)isValidBooleanAttributeValue {
164164
return false;
165165
}
166166

167-
- (BOOL)isValidNumericAttributeValue {
167+
- (BOOL)isNumericAttributeValue {
168168
if (self) {
169169
NSNumber *number = (NSNumber *)self;
170170
// check value is NSNumber
171171
if (number && [number isKindOfClass:[NSNumber class]]) {
172172
const char *objCType = [number objCType];
173-
174-
// check for Nan
175-
if (isnan([number doubleValue])) {
176-
return false;
177-
}
173+
178174
// check NSNumber is bool
179175
CFTypeID boolID = CFBooleanGetTypeID(); // the type ID of CFBoolean
180176
CFTypeID numID = CFGetTypeID((__bridge CFTypeRef)(number)); // the type ID of num
181177
if (boolID == numID) {
182178
return false;
183179
}
184-
// check for infinity
185-
if (isinf([number doubleValue])) {
186-
return false;
187-
}
188180
// check NSNumber is of type int, double
189181
Boolean isNumeric = (strcmp(objCType, @encode(short)) == 0)
190182
|| (strcmp(objCType, @encode(unsigned short)) == 0)
@@ -199,7 +191,19 @@ - (BOOL)isValidNumericAttributeValue {
199191
|| (strcmp(objCType, @encode(char)) == 0)
200192
|| (strcmp(objCType, @encode(unsigned char)) == 0);
201193

202-
if (isNumeric) {
194+
return isNumeric;
195+
}
196+
}
197+
return false;
198+
}
199+
200+
- (BOOL)isFiniteNumber {
201+
if (self) {
202+
NSNumber *number = (NSNumber *)self;
203+
// check value is NSNumber
204+
if (number && [number isKindOfClass:[NSNumber class]]) {
205+
// check for Nan and infinity
206+
if (!isnan([number doubleValue]) && !isinf([number doubleValue])) {
203207
NSNumber *maxValue = [NSNumber numberWithDouble:pow(2,53)];
204208
return (fabs([number doubleValue]) <= [maxValue doubleValue]);
205209
}

OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYDecisionServiceTest.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,15 @@ - (void)testIsUserInExperimentUsesNonNullAudienceConditionsWhenAudienceIdsAlsoAv
282282
logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorExperimentEvaluationCompletedWithResult, experiment.experimentKey, @"TRUE"];
283283
OCMVerify([loggerMock logMessage:logMessage withLevel:OptimizelyLogLevelInfo]);
284284

285+
tmpAttributes = @{@"favorite_ice_cream1":@"pineapple", @"house":@"test"};
286+
[self.typedAudienceDecisionService isUserInExperiment:self.typedAudienceConfig experiment:experiment attributes:tmpAttributes];
287+
logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorExperimentEvaluationCompletedWithResult, experiment.experimentKey, @"FALSE"];
288+
OCMVerify([loggerMock logMessage:logMessage withLevel:OptimizelyLogLevelInfo]);
289+
285290
[self.typedAudienceDecisionService isUserInExperiment:self.typedAudienceConfig experiment:experiment attributes:self.attributes];
286291
logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesAudienceEvaluatorExperimentEvaluationCompletedWithResult, experiment.experimentKey, @"FALSE"];
287292
OCMVerify([loggerMock logMessage:logMessage withLevel:OptimizelyLogLevelInfo]);
293+
288294

289295
[loggerMock stopMocking];
290296
}

0 commit comments

Comments
 (0)