Skip to content

Commit f68cd8e

Browse files
Fixed evaluation of audiences with leaf roots. (#366)
1 parent cbe3f1c commit f68cd8e

File tree

7 files changed

+66
-36
lines changed

7 files changed

+66
-36
lines changed

OptimizelySDKCore/OptimizelySDKCore/OPTLYAudience.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,6 @@
3737
/// Override OPTLYJSONModel set conditions
3838
- (void)setConditionsWithNSString:(NSString *)string;
3939
- (void)setConditionsWithNSArray:(NSArray *)array;
40+
- (void)setConditionsWithNSDictionary:(NSDictionary *)dictionary;
4041

4142
@end

OptimizelySDKCore/OptimizelySDKCore/OPTLYAudience.m

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,16 @@ - (void)setConditionsWithNSString:(NSString *)string {
3434

3535
- (void)setConditionsWithNSArray:(NSArray *)array {
3636
NSError *err = nil;
37-
self.conditions = [OPTLYCondition deserializeJSONArray:array error:nil];
37+
self.conditions = [OPTLYCondition deserializeJSON:array error:nil];
38+
if (err != nil) {
39+
NSException *exception = [[NSException alloc] initWithName:err.domain reason:err.localizedFailureReason userInfo:@{@"Error" : err}];
40+
@throw exception;
41+
}
42+
}
43+
44+
- (void)setConditionsWithNSDictionary:(NSDictionary *)dictionary {
45+
NSError *err = nil;
46+
self.conditions = [OPTLYCondition deserializeJSON:dictionary error:nil];
3847
if (err != nil) {
3948
NSException *exception = [[NSException alloc] initWithName:err.domain reason:err.localizedFailureReason userInfo:@{@"Error" : err}];
4049
@throw exception;

OptimizelySDKCore/OptimizelySDKCore/OPTLYCondition.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828

2929
@interface OPTLYCondition : NSObject
3030

31-
+ (NSArray<OPTLYCondition *><OPTLYCondition> *)deserializeJSONArray:(NSArray *)jsonArray
32-
error:(NSError * __autoreleasing *)error;
33-
+ (NSArray<OPTLYCondition> *)deserializeJSONArray:(NSArray *)jsonArray;
34-
+ (NSArray<OPTLYCondition> *)deserializeAudienceConditionsJSONArray:(NSArray *)jsonArray
35-
error:(NSError * __autoreleasing *)error;
36-
+ (NSArray<OPTLYCondition> *)deserializeAudienceConditionsJSONArray:(NSArray *)jsonArray;
31+
+ (NSArray<OPTLYCondition *><OPTLYCondition> *)deserializeJSON:(id)json
32+
error:(NSError * __autoreleasing *)error;
33+
+ (NSArray<OPTLYCondition *><OPTLYCondition> *)deserializeJSON:(id)json;
34+
+ (NSArray<OPTLYCondition *><OPTLYCondition> *)deserializeAudienceConditionsJSONArray:(NSArray *)jsonArray
35+
error:(NSError * __autoreleasing *)error;
36+
+ (NSArray<OPTLYCondition *><OPTLYCondition> *)deserializeAudienceConditionsJSONArray:(NSArray *)jsonArray;
3737

3838
@end
3939

OptimizelySDKCore/OptimizelySDKCore/OPTLYCondition.m

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,25 @@
2222

2323
@implementation OPTLYCondition
2424

25-
+ (NSArray<OPTLYCondition *><OPTLYCondition> *)deserializeJSONArray:(NSArray *)jsonArray {
26-
return [OPTLYCondition deserializeJSONArray:jsonArray error:nil];
25+
+ (NSArray<OPTLYCondition *><OPTLYCondition> *)deserializeJSON:(id)json {
26+
return [OPTLYCondition deserializeJSON:json error:nil];
2727
}
2828

29-
+ (NSArray<OPTLYCondition> *)deserializeAudienceConditionsJSONArray:(NSArray *)jsonArray {
29+
+ (NSArray<OPTLYCondition *><OPTLYCondition> *)deserializeAudienceConditionsJSONArray:(NSArray *)jsonArray {
3030
return [OPTLYCondition deserializeAudienceConditionsJSONArray:jsonArray error:nil];
3131
}
3232

3333
// example jsonArray:
3434
// [“and", [“or", [“or", {"name": "sample_attribute_key", "type": "custom_attribute", "value": “a”}], [“or", {"name": "sample_attribute_key", "type": "custom_attribute", "value": "b"}], [“or", {"name": "sample_attribute_key", "type": "custom_attribute", "value": "c"}]
35-
+ (NSArray<OPTLYCondition *><OPTLYCondition> *)deserializeJSONArray:(NSArray *)jsonArray
36-
error:(NSError * __autoreleasing *)error {
35+
+ (NSArray<OPTLYCondition *><OPTLYCondition> *)deserializeJSON:(id)json
36+
error:(NSError * __autoreleasing *)error {
3737

3838
NSMutableArray *mutableJsonArray = [NSMutableArray new];
3939

4040
// need to check if the jsonArray is actually an array, otherwise, something is wrong with the audience condition
41-
if (![jsonArray isKindOfClass:[NSArray class]]) {
42-
if ([OPTLYBaseCondition isBaseConditionJSON:((NSData *)jsonArray)]) {
43-
mutableJsonArray = [[NSMutableArray alloc] initWithArray:@[OPTLYDatafileKeysOrCondition,jsonArray]];
41+
if (![json isKindOfClass:[NSArray class]]) {
42+
if ([OPTLYBaseCondition isBaseConditionJSON:((NSData *)json)]) {
43+
mutableJsonArray = [[NSMutableArray alloc] initWithArray:@[OPTLYDatafileKeysOrCondition,json]];
4444
}
4545
else {
4646
NSError *err = [NSError errorWithDomain:OPTLYErrorHandlerMessagesDomain
@@ -53,7 +53,7 @@ @implementation OPTLYCondition
5353
}
5454
}
5555
else {
56-
mutableJsonArray = [jsonArray mutableCopy];
56+
mutableJsonArray = [(NSArray *)json mutableCopy];
5757
}
5858

5959
if (mutableJsonArray.count < 2) {
@@ -82,7 +82,7 @@ @implementation OPTLYCondition
8282

8383
// further condition arrays to deserialize
8484
NSError *err = nil;
85-
NSArray *deserializedJsonObject = [OPTLYCondition deserializeJSONArray:mutableJsonArray[i] error:&err];
85+
NSArray *deserializedJsonObject = [OPTLYCondition deserializeJSON:mutableJsonArray[i] error:&err];
8686
if (err) {
8787
*error = err;
8888
return nil;
@@ -100,8 +100,8 @@ @implementation OPTLYCondition
100100

101101
// example jsonArray:
102102
// "[\"and\", [\"or\", \"3468206642\", \"3988293898\"], [\"or\", \"3988293899\", \"3468206646\", \"3468206647\", \"3468206644\", \"3468206643\"]]"
103-
+ (NSArray<OPTLYCondition> *)deserializeAudienceConditionsJSONArray:(NSArray *)jsonArray
104-
error:(NSError * __autoreleasing *)error {
103+
+ (NSArray<OPTLYCondition *><OPTLYCondition> *)deserializeAudienceConditionsJSONArray:(NSArray *)jsonArray
104+
error:(NSError * __autoreleasing *)error {
105105

106106
NSMutableArray *mutableJsonArray = [NSMutableArray new];
107107
// need to check if the jsonArray is actually an array, otherwise, something is wrong with the audience condition

OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYConditionTest.m

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ - (void)testConditionBaseCaseDeserializationWithAndContainer {
470470
@"value": @"attributeValue",
471471
@"type": @"custom_attribute"};
472472
NSArray *andConditionArray = @[@"and", conditionInfo];
473-
NSArray *conditions = [OPTLYCondition deserializeJSONArray:andConditionArray];
473+
NSArray *conditions = [OPTLYCondition deserializeJSON:andConditionArray];
474474
XCTAssertNotNil(conditions);
475475
XCTAssertTrue(conditions.count == 1);
476476
XCTAssertTrue([conditions[0] isKindOfClass:[OPTLYAndCondition class]]);
@@ -488,7 +488,7 @@ - (void)testConditionBaseCaseDeserializationWithOrContainer {
488488
@"value": @"attributeValue",
489489
@"type": @"custom_attribute"};
490490
NSArray *andConditionArray = @[@"or", conditionInfo];
491-
NSArray *conditions = [OPTLYCondition deserializeJSONArray:andConditionArray];
491+
NSArray *conditions = [OPTLYCondition deserializeJSON:andConditionArray];
492492
XCTAssertNotNil(conditions);
493493
XCTAssertTrue(conditions.count == 1);
494494
XCTAssertTrue([conditions[0] isKindOfClass:[OPTLYOrCondition class]]);
@@ -501,11 +501,27 @@ - (void)testConditionBaseCaseDeserializationWithOrContainer {
501501
XCTAssertEqualObjects(condition.type, @"custom_attribute");
502502
}
503503

504+
- (void)testConditionBaseCaseDeserializationWithLeafCondition {
505+
NSDictionary *conditionInfo = @{@"name": @"someAttributeKey",
506+
@"value": @"attributeValue",
507+
@"type": @"custom_attribute"};
508+
NSArray *conditions = [OPTLYCondition deserializeJSON:conditionInfo];
509+
XCTAssertNotNil(conditions);
510+
XCTAssertTrue(conditions.count == 1);
511+
XCTAssertTrue([conditions[0] isKindOfClass:[OPTLYOrCondition class]]);
512+
OPTLYAndCondition *andCondition = conditions[0];
513+
XCTAssertTrue(andCondition.subConditions.count == 1);
514+
XCTAssertTrue([andCondition.subConditions[0] isKindOfClass:[OPTLYBaseCondition class]]);
515+
OPTLYBaseCondition *condition = (OPTLYBaseCondition *)andCondition.subConditions[0];
516+
XCTAssertEqualObjects(condition.name, @"someAttributeKey");
517+
XCTAssertEqualObjects(condition.value, @"attributeValue");
518+
XCTAssertEqualObjects(condition.type, @"custom_attribute");
519+
}
504520

505521
- (void)testDeserializeConditions {
506522
NSString *conditionString = @"[\"and\", [\"or\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_attribute\", \"value\": \"chrome\"}]]]";
507523
NSArray *conditionStringJSONArray = [conditionString getValidConditionsArray];
508-
NSArray *conditionsArray = [OPTLYCondition deserializeJSONArray:conditionStringJSONArray];
524+
NSArray *conditionsArray = [OPTLYCondition deserializeJSON:conditionStringJSONArray];
509525
XCTAssertNotNil(conditionsArray);
510526
XCTAssertTrue([conditionsArray[0] isKindOfClass:[OPTLYAndCondition class]]);
511527
OPTLYAndCondition *andCondition = conditionsArray[0];
@@ -527,21 +543,21 @@ - (void)testDeserializeConditionsNoValue {
527543
NSString *conditionString = @"[\"and\", [\"or\", [\"or\", {\"name\": \"browser_type\", \"invalid\": \"custom_attribute\"}]]]";
528544
NSArray *conditionStringJSONArray = [conditionString getValidConditionsArray];
529545
NSError *error = nil;
530-
NSArray *conditionsArray = [OPTLYCondition deserializeJSONArray:conditionStringJSONArray error:&error];
546+
NSArray *conditionsArray = [OPTLYCondition deserializeJSON:conditionStringJSONArray error:&error];
531547
XCTAssertTrue(conditionsArray);
532548
}
533549

534550
- (void)testDeserializeConditionsEmptyConditions {
535551
NSString *conditionString = @"";
536552
NSArray *conditionStringJSONArray = [conditionString getValidConditionsArray];
537553
NSError *error = nil;
538-
NSArray *conditionsArray = [OPTLYCondition deserializeJSONArray:conditionStringJSONArray error:&error];
554+
NSArray *conditionsArray = [OPTLYCondition deserializeJSON:conditionStringJSONArray error:&error];
539555
XCTAssertNil(conditionsArray);
540556
}
541557

542558
- (void)testDeserializeConditionsNilConditions {
543559
NSError *error = nil;
544-
NSArray *conditionsArray = [OPTLYCondition deserializeJSONArray:nil error:&error];
560+
NSArray *conditionsArray = [OPTLYCondition deserializeJSON:nil error:&error];
545561
XCTAssertNil(conditionsArray);
546562
}
547563

@@ -674,7 +690,7 @@ - (void)testShouldReturnOrOperatorWhenNoOperatorIsProvided {
674690
NSString *noOperatorConditionString = @"[{\"name\": \"browser_type\", \"type\": \"custom_attribute\", \"value\": \"android\"}]";
675691
NSArray *conditionStringJSONArray = [noOperatorConditionString getValidConditionsArray];
676692
NSError *error = nil;
677-
NSArray *conditionsArray = [OPTLYCondition deserializeJSONArray:conditionStringJSONArray error:&error];
693+
NSArray *conditionsArray = [OPTLYCondition deserializeJSON:conditionStringJSONArray error:&error];
678694
XCTAssertNotNil(conditionsArray);
679695
XCTAssertTrue([conditionsArray[0] isKindOfClass:[OPTLYOrCondition class]]);
680696

@@ -718,7 +734,7 @@ - (OPTLYBaseCondition *)mockBaseConditionAlwaysNull {
718734
///MARK:- Helper Methods
719735

720736
- (OPTLYCondition *)getFirstConditionFromArray:(NSArray *)array {
721-
NSArray *conditionArray = [OPTLYCondition deserializeJSONArray:array];
737+
NSArray *conditionArray = [OPTLYCondition deserializeJSON:array];
722738
return conditionArray[0];
723739
}
724740

OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYTypedAudienceTest.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ - (void)testLTMatcherReturnsTrueWhenAttributeValueIsLessThanConditionValue {
472472
///MARK:- Helper Methods
473473

474474
- (OPTLYCondition *)getFirstConditionFromArray:(NSArray *)array {
475-
NSArray *conditionArray = [OPTLYCondition deserializeJSONArray:array];
475+
NSArray *conditionArray = [OPTLYCondition deserializeJSON:array];
476476
return conditionArray[0];
477477
}
478478

OptimizelySDKCore/OptimizelySDKCoreTests/TestData/audience_targeting.json

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"endOfRange": 10000
1717
}
1818
],
19-
"audienceIds": ["10413101794"],
19+
"audienceIds": ["10413101794", "10413101795"],
2020
"variations": [
2121
{
2222
"variables": [],
@@ -58,7 +58,7 @@
5858
"endOfRange": 10000
5959
}
6060
],
61-
"audienceIds": ["20413101795", "20413101796", "20413101797" ,"20413101798"],
61+
"audienceIds": ["20413101794", "20413101795", "20413101796", "20413101797" ,"20413101798"],
6262
"variations": [
6363
{
6464
"variables": [],
@@ -1294,9 +1294,9 @@
12941294
},
12951295
{
12961296
"id": "10413101795",
1297-
"_comment_about_condition_format": "Note: pre-3.0 Java SDKs cannot parse leaves at the root of this condition tree. App backend accommodates those SDKs by wrapping the leaves in parents. (OASIS-3718)",
1298-
"conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_atribute\", \"match\": \"exact\", \"value\": \"$opt_dummy_value\"}",
1299-
"name": "untyped_single_dummy_condition"
1297+
"_comment_about_condition_format": "Note: pre-3.0 Java SDKs cannot parse and evaluate leaves at the root of this condition tree. App backend accommodates those SDKs by wrapping the leaves in parents. (OASIS-3718)",
1298+
"conditions": "{\"type\": \"custom_attribute\", \"name\": \"string_attribute\", \"match\": \"exact\", \"value\": \"leaf_root\"}",
1299+
"name": "untyped_single_condition_leaf_root"
13001300
},
13011301
{
13021302
"id": "10413101796",
@@ -1311,18 +1311,22 @@
13111311
{
13121312
"_comment": "Dummy audience that is overridden by a typedAudience with the same id",
13131313
"id": "20413101798",
1314-
"_comment_about_condition_format": "Note: pre-3.0 Java SDKs cannot parse leaves at the root of this condition tree. App backend accommodates those SDKs by wrapping the leaves in parents. (OASIS-3718)",
1315-
"conditions": "{\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_atribute\", \"match\": \"exact\", \"value\": \"$opt_dummy_value\"}",
1314+
"conditions": "[\"and\", [\"or\", [\"or\", {\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"match\": \"exact\", \"value\": \"$opt_dummy_value\"}]]]",
13161315
"name": "dummy_audience_overridden_by_typed_audience"
13171316
},
13181317
{
13191318
"_comment": "Dummy audience that is targeted by audienceIds for experiments with backwards-incompatible audienceConditions",
13201319
"id": "$opt_dummy_audience",
1321-
"conditions": "[\"and\", [\"or\", [\"or\", {\"type\": \"custom_attribute\", \"name\": \"string_attribute\", \"value\": \"exact_match\"}]]]",
1320+
"conditions": "[\"and\", [\"or\", [\"or\", {\"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"match\": \"exact\", \"value\": \"$opt_dummy_value\"}]]]",
13221321
"name": "dummy_audience"
13231322
}
13241323
],
13251324
"typedAudiences": [
1325+
{
1326+
"id": "20413101794",
1327+
"conditions": { "type": "custom_attribute", "name": "s_foo", "match": "exact", "value": "leaf_root" },
1328+
"name": "single_condition_leaf_root"
1329+
},
13261330
{
13271331
"id": "20413101795",
13281332
"conditions": [ "and", [ "or", [ "or", { "type": "custom_attribute", "name": "s_foo", "match": "exact", "value": "foo" } ] ] ],

0 commit comments

Comments
 (0)