Skip to content

Commit 80b0f43

Browse files
[iOS] Fix for: "unrecognized selector sent to instance" for datafile with audience having no specified value (#178)
* [OASIS-1423] Fixed crash caused by missing value in attributes. This just prevents a crash, but the datafile generation should not allow the missing value. * Changed dictionary index back to literal as this is the preferred Apple way. * Added a few more tests for the condition deserializer.
1 parent 8135f46 commit 80b0f43

File tree

7 files changed

+86
-13
lines changed

7 files changed

+86
-13
lines changed

OptimizelySDKCore/OptimizelySDKCore/OPTLYAudience.m

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ @implementation OPTLYAudience
2222
+ (OPTLYJSONKeyMapper*)keyMapper
2323
{
2424
return [[OPTLYJSONKeyMapper alloc] initWithDictionary:@{ OPTLYDatafileKeysAudienceId : @"audienceId",
25-
OPTLYDatafileKeysAudienceName : @"audienceName"
25+
OPTLYDatafileKeysAudienceName : @"audienceName"
2626
}];
2727
}
2828

@@ -38,7 +38,12 @@ - (void)setConditionsWithNSString:(NSString *)string {
3838
@throw exception;
3939
}
4040

41-
self.conditions = [OPTLYCondition deserializeJSONArray:array];
41+
self.conditions = [OPTLYCondition deserializeJSONArray:array error:&err];
42+
43+
if (err != nil) {
44+
NSException *exception = [[NSException alloc] initWithName:err.domain reason:err.localizedFailureReason userInfo:@{@"Error" : err}];
45+
@throw exception;
46+
}
4247
}
4348

4449
- (BOOL)evaluateConditionsWithAttributes:(NSDictionary<NSString *,NSString *> *)attributes {

OptimizelySDKCore/OptimizelySDKCore/OPTLYBaseCondition.m

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ + (BOOL) isBaseConditionJSON:(NSData *)jsonData {
2525
}
2626
else {
2727
NSDictionary *dict = (NSDictionary *)jsonData;
28-
if (dict[OPTLYDatafileKeysConditionName] != nil
29-
&& dict[OPTLYDatafileKeysConditionType] != nil
30-
&& dict[OPTLYDatafileKeysConditionValue] != nil) {
28+
29+
if (dict[OPTLYDatafileKeysConditionName] != nil &&
30+
dict[OPTLYDatafileKeysConditionType] != nil &&
31+
dict[OPTLYDatafileKeysConditionValue] != nil) {
3132
return true;
3233
}
3334
return false;

OptimizelySDKCore/OptimizelySDKCore/OPTLYCondition.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
@interface OPTLYCondition : NSObject
2929

30+
+ (NSArray<OPTLYCondition> *)deserializeJSONArray:(NSArray *)jsonArray
31+
error:(NSError **)error;
3032
+ (NSArray<OPTLYCondition> *)deserializeJSONArray:(NSArray *)jsonArray;
3133

3234
@end

OptimizelySDKCore/OptimizelySDKCore/OPTLYCondition.m

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,25 @@
2222
@implementation OPTLYCondition
2323

2424
+ (NSArray<OPTLYCondition> *)deserializeJSONArray:(NSArray *)jsonArray {
25+
return [OPTLYCondition deserializeJSONArray:jsonArray error:nil];
26+
}
27+
28+
// example jsonArray:
29+
// [“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"}]
30+
+ (NSArray<OPTLYCondition> *)deserializeJSONArray:(NSArray *)jsonArray
31+
error:(NSError **)error {
32+
33+
// need to check if the jsonArray is actually an array, otherwise, something is wrong with the audience condition
34+
if (![jsonArray isKindOfClass:[NSArray class]]) {
35+
NSError *err = [NSError errorWithDomain:OPTLYErrorHandlerMessagesDomain
36+
code:OPTLYErrorTypesDatafileInvalid
37+
userInfo:@{NSLocalizedDescriptionKey : OPTLYErrorHandlerMessagesProjectConfigInvalidAudienceCondition}];
38+
if (error && err) {
39+
*error = err;
40+
}
41+
return nil;
42+
}
43+
2544
if (jsonArray.count > 1 && [OPTLYBaseCondition isBaseConditionJSON:jsonArray[1]]) { //base case condition
2645

2746
// generate all base conditions
@@ -31,24 +50,38 @@ @implementation OPTLYCondition
3150
NSError *err = nil;
3251
OPTLYBaseCondition *condition = [[OPTLYBaseCondition alloc] initWithDictionary:info
3352
error:&err];
34-
if (err != nil) {
35-
NSException *exception = [[NSException alloc] initWithName:err.domain reason:err.localizedFailureReason userInfo:@{@"Error" : err}];
36-
@throw exception;
53+
if (error && err) {
54+
*error = err;
3755
} else {
38-
[conditions addObject:condition];
56+
if (condition != nil) {
57+
[conditions addObject:condition];
58+
}
3959
}
4060
}
4161

4262
// return an (And/Or/Not) Condition handling the base conditions
43-
return (NSArray<OPTLYCondition> *)@[[OPTLYCondition createConditionInstanceOfClass:jsonArray[0] withConditions:conditions]];
63+
NSObject<OPTLYCondition> *condition = [OPTLYCondition createConditionInstanceOfClass:jsonArray[0]
64+
withConditions:conditions];
65+
return (NSArray<OPTLYCondition> *)@[condition];
4466
}
4567
else { // further condition arrays to deserialize
4668
NSMutableArray<OPTLYCondition> *subConditions = (NSMutableArray<OPTLYCondition> *)[[NSMutableArray alloc] initWithCapacity:(jsonArray.count - 1)];
4769
for (int i = 1; i < jsonArray.count; i++) {
48-
[subConditions addObjectsFromArray:[OPTLYCondition deserializeJSONArray:jsonArray[i]]];
70+
NSError *err = nil;
71+
NSArray *deserializedJsonObject = [OPTLYCondition deserializeJSONArray:jsonArray[i] error:&err];
72+
73+
if (err) {
74+
*error = err;
75+
return nil;
76+
}
77+
78+
if (deserializedJsonObject != nil) {
79+
[subConditions addObjectsFromArray:deserializedJsonObject];
80+
}
4981
}
50-
return (NSArray<OPTLYCondition> *)@[[OPTLYCondition createConditionInstanceOfClass:jsonArray[0]
51-
withConditions:subConditions]];
82+
NSObject<OPTLYCondition> *condition = [OPTLYCondition createConditionInstanceOfClass:jsonArray[0]
83+
withConditions:subConditions];
84+
return (NSArray<OPTLYCondition> *)@[condition];
5285
}
5386
}
5487

OptimizelySDKCore/OptimizelySDKCore/OPTLYErrorHandlerMessages.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ extern NSString *const OPTLYErrorHandlerMessagesDataStoreInvalidDataStoreEntityV
5454
extern NSString *const OPTLYErrorHandlerMessagesHTTPRequestManagerPOSTRetryFailure;
5555
extern NSString *const OPTLYErrorHandlerMessagesHTTPRequestManagerGETRetryFailure;
5656
extern NSString *const OPTLYErrorHandlerMessagesHTTPRequestManagerGETIfModifiedFailure;
57+
extern NSString *const OPTLYErrorHandlerMessagesProjectConfigInvalidAudienceCondition;
5758

5859

5960
typedef NS_ENUM(NSUInteger, OPTLYErrorTypes) {

OptimizelySDKCore/OptimizelySDKCore/OPTLYErrorHandlerMessages.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,8 @@
5959
NSString *const OPTLYErrorHandlerMessagesHTTPRequestManagerGETRetryFailure = @"[HTTP] The max backoff retry has been exceeded. GET failed with error: %@.";
6060
NSString *const OPTLYErrorHandlerMessagesHTTPRequestManagerGETIfModifiedFailure = @"[HTTP] The max backoff retry has been exceeded. GET if modified failed with error: %@.";
6161

62+
// ---- Project Config ----
63+
NSString *const OPTLYErrorHandlerMessagesProjectConfigInvalidAudienceCondition = @"[CONFIG] Invalid audience condition.";
64+
6265
@implementation OPTLYErrorHandlerMessages
6366
@end

OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYConditionTest.m

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,34 @@ - (void)testDeserializeConditions {
179179
XCTAssertTrue([conditionsArray[0] evaluateConditionsWithAttributes:self.testUserAttributes]);
180180
}
181181

182+
- (void)testDeserializeConditionsNoValue {
183+
NSString *conditionString = @"[\"and\", [\"or\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_dimension\"}]]]";
184+
NSData *conditionData = [conditionString dataUsingEncoding:NSUTF8StringEncoding];
185+
NSArray *conditionStringJSONArray = [NSJSONSerialization JSONObjectWithData:conditionData
186+
options:NSJSONReadingAllowFragments
187+
error:nil];
188+
NSError *error = nil;
189+
NSArray *conditionsArray = [OPTLYCondition deserializeJSONArray:conditionStringJSONArray error:&error];
190+
XCTAssertNil(conditionsArray);
191+
}
192+
193+
- (void)testDeserializeConditionsEmptyConditions {
194+
NSString *conditionString = @"";
195+
NSData *conditionData = [conditionString dataUsingEncoding:NSUTF8StringEncoding];
196+
NSArray *conditionStringJSONArray = [NSJSONSerialization JSONObjectWithData:conditionData
197+
options:NSJSONReadingAllowFragments
198+
error:nil];
199+
NSError *error = nil;
200+
NSArray *conditionsArray = [OPTLYCondition deserializeJSONArray:conditionStringJSONArray error:&error];
201+
XCTAssertNil(conditionsArray);
202+
}
203+
204+
- (void)testDeserializeConditionsNilConditions {
205+
NSError *error = nil;
206+
NSArray *conditionsArray = [OPTLYCondition deserializeJSONArray:nil error:&error];
207+
XCTAssertNil(conditionsArray);
208+
}
209+
182210
- (OPTLYBaseCondition *)mockBaseConditionAlwaysFalse {
183211
id falseBaseCondition = OCMClassMock([OPTLYBaseCondition class]);
184212
OCMStub([falseBaseCondition evaluateConditionsWithAttributes:[OCMArg isKindOfClass:[NSDictionary class]]]).andReturn(false);

0 commit comments

Comments
 (0)