Skip to content

Commit 76fd593

Browse files
committed
Add handling http JSON error field as string.
1 parent 4e23a51 commit 76fd593

File tree

5 files changed

+135
-29
lines changed

5 files changed

+135
-29
lines changed

Source/ARTJsonLikeEncoder.m

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,12 +1116,23 @@ - (ARTStatsResourceCount *)statsResourceCountFromDictionary:(NSDictionary *)inpu
11161116
refused:refused.doubleValue];
11171117
}
11181118

1119-
- (ARTErrorInfo *)decodeErrorInfo:(NSData *)artError error:(NSError **)error {
1120-
NSDictionary *decodedError = [[self decodeDictionary:artError error:error] valueForKey:@"error"];
1121-
if (!decodedError) {
1119+
- (ARTErrorInfo *)decodeErrorInfo:(NSData *)artError statusCode:(NSInteger)statusCode error:(NSError **)error {
1120+
id decodedError = [[self decodeDictionary:artError error:error] valueForKey:@"error"];
1121+
if ([decodedError isKindOfClass:NSString.class]) {
1122+
// Return error with HTTP StatusCode if ARTErrorStatusCode does not exist
1123+
return [ARTErrorInfo createWithCode:statusCode * 100
1124+
status:statusCode
1125+
message:decodedError];
1126+
}
1127+
else if ([decodedError isKindOfClass:NSDictionary.class]) {
1128+
return [ARTErrorInfo createWithCode:[(decodedError[@"code"] ?: @(statusCode * 100)) intValue]
1129+
status:[(decodedError[@"statusCode"] ?: @(statusCode)) intValue]
1130+
message:decodedError[@"message"] ?: @"<Error message was not provided>"];
1131+
}
1132+
else {
1133+
ARTLogDebug(_logger, @"RS:%p ARTJsonLikeEncoder<%@>: `decodeErrorInfo:statusCode:error:` was unable to extract error from data: %@", _rest, [_delegate formatAsString], decodedError);
11221134
return nil;
11231135
}
1124-
return [ARTErrorInfo createWithCode:[decodedError[@"code"] intValue] status:[decodedError[@"statusCode"] intValue] message:decodedError[@"message"]];
11251136
}
11261137

11271138
- (ARTStatsRequestCount *)statsRequestCountFromDictionary:(NSDictionary *)input {

Source/ARTRest.m

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,9 @@ - (NSString *)agentIdentifierWithWrapperSDKAgents:(nullable NSDictionary<NSStrin
402402
if (!validContentType) {
403403
NSString *plain = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
404404
// Construct artificial error
405-
error = [ARTErrorInfo createWithCode:response.statusCode * 100 status:response.statusCode message:[plain art_shortString] requestId:requestId];
405+
error = [ARTErrorInfo createWithCode:response.statusCode * 100
406+
status:response.statusCode
407+
message:[plain art_shortString] requestId:requestId];
406408
data = nil; // Discard data; format is unreliable.
407409
ARTLogError(self.logger, @"Request %@ failed with %@", request, error);
408410
}
@@ -411,7 +413,9 @@ - (NSString *)agentIdentifierWithWrapperSDKAgents:(nullable NSDictionary<NSStrin
411413
if (response.statusCode >= 400) {
412414
if (data) {
413415
NSError *decodeError = nil;
414-
ARTErrorInfo *dataError = [self->_encoders[response.MIMEType] decodeErrorInfo:data error:&decodeError];
416+
ARTErrorInfo *dataError = [self->_encoders[response.MIMEType] decodeErrorInfo:data
417+
statusCode:response.statusCode
418+
error:&decodeError];
415419
if ([self shouldRenewToken:&dataError] && [request isKindOfClass:[NSMutableURLRequest class]]) {
416420
ARTLogDebug(self.logger, @"RS:%p retry request %@", self, request);
417421
// Make a single attempt to reissue the token and resend the request
@@ -429,11 +433,10 @@ - (NSString *)agentIdentifierWithWrapperSDKAgents:(nullable NSDictionary<NSStrin
429433
}
430434
if (!error) {
431435
// Return error with HTTP StatusCode if ARTErrorStatusCode does not exist
432-
error = [ARTErrorInfo
433-
createWithCode:response.statusCode*100
434-
status:response.statusCode
435-
message:[[NSString alloc] initWithData:data ?: [NSData data] encoding:NSUTF8StringEncoding]
436-
requestId:requestId];
436+
error = [ARTErrorInfo createWithCode:response.statusCode * 100
437+
status:response.statusCode
438+
message:[[NSString alloc] initWithData:data ?: [NSData data] encoding:NSUTF8StringEncoding]
439+
requestId:requestId];
437440
}
438441

439442
} else {
@@ -561,7 +564,9 @@ - (void)timeWithWrapperSDKAgents:(nullable NSStringDictionary *)wrapperSDKAgents
561564
}
562565
NSError *decodeError = nil;
563566
if (response.statusCode >= 400) {
564-
ARTErrorInfo *dataError = [self->_encoders[response.MIMEType] decodeErrorInfo:data error:&decodeError];
567+
ARTErrorInfo *dataError = [self->_encoders[response.MIMEType] decodeErrorInfo:data
568+
statusCode:response.statusCode
569+
error:&decodeError];
565570
callback(nil, dataError ? dataError : decodeError);
566571
} else {
567572
NSDate *time = [self->_encoders[response.MIMEType] decodeTime:data error:&decodeError];

Source/PrivateHeaders/Ably/ARTEncoder.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ NS_ASSUME_NONNULL_BEGIN
9797

9898
// Others
9999
- (nullable NSDate *)decodeTime:(NSData *)data error:(NSError *_Nullable *_Nullable)error;
100-
- (nullable ARTErrorInfo *)decodeErrorInfo:(NSData *)error error:(NSError *_Nullable *_Nullable)error;
100+
- (nullable ARTErrorInfo *)decodeErrorInfo:(NSData *)error statusCode:(NSInteger)statusCode error:(NSError *_Nullable *_Nullable)error;
101101
- (nullable NSArray *)decodeStats:(NSData *)data error:(NSError *_Nullable *_Nullable)error;
102102

103103
@end

Test/AblyTests/Test Utilities/TestUtilities.swift

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -886,8 +886,37 @@ struct ErrorSimulator {
886886
let serverId = "server-test-suite"
887887
var statusCode: Int = 401
888888
var shouldPerformRequest: Bool = false
889-
890-
mutating func stubResponse(_ url: URL) -> HTTPURLResponse? {
889+
let stubData: Data?
890+
891+
init(value: Int, description: String, statusCode: Int, shouldPerformRequest: Bool, stubData: Data?) {
892+
self.value = value
893+
self.description = description
894+
self.statusCode = statusCode
895+
self.shouldPerformRequest = shouldPerformRequest
896+
self.stubData = stubData
897+
}
898+
899+
init(value: Int, description: String, statusCode: Int, shouldPerformRequest: Bool, stubDataDict: [String: Any]? = nil) {
900+
self.value = value
901+
self.description = description
902+
self.statusCode = statusCode
903+
self.shouldPerformRequest = shouldPerformRequest
904+
if let stubDataDict {
905+
self.stubData = stubDataDict.data()
906+
} else {
907+
let jsonObject: [String: Any] = [
908+
"error": [
909+
"statusCode": self.statusCode,
910+
"code": self.value,
911+
"message": self.description,
912+
"serverId": self.serverId,
913+
]
914+
]
915+
self.stubData = jsonObject.data()
916+
}
917+
}
918+
919+
func stubResponse(_ url: URL) -> HTTPURLResponse? {
891920
return HTTPURLResponse(url: url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: [
892921
"Content-Length": String(stubData?.count ?? 0),
893922
"Content-Type": "application/json",
@@ -897,17 +926,12 @@ struct ErrorSimulator {
897926
]
898927
)
899928
}
929+
}
900930

901-
lazy var stubData: Data? = {
902-
let jsonObject: [String: Any] = ["error": [
903-
"statusCode": modf(Float(self.value)/100).0, //whole number part
904-
"code": self.value,
905-
"message": self.description,
906-
"serverId": self.serverId,
907-
] as [String: Any]
908-
]
909-
return try? JSONSerialization.data(withJSONObject: jsonObject, options: JSONSerialization.WritingOptions.init(rawValue: 0))
910-
}()
931+
extension [String: Any] {
932+
func data() -> Data? {
933+
try? JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions.init(rawValue: 0))
934+
}
911935
}
912936

913937
class MockHTTPExecutor: NSObject, ARTHTTPExecutor {
@@ -1015,7 +1039,7 @@ class TestProxyHTTPExecutor: NSObject, ARTHTTPExecutor {
10151039
}
10161040
}
10171041

1018-
if var simulatedError = errorSimulator, let requestURL = request.url {
1042+
if let simulatedError = errorSimulator, let requestURL = request.url {
10191043
defer {
10201044
errorSimulator = nil
10211045
}
@@ -1058,9 +1082,9 @@ class TestProxyHTTPExecutor: NSObject, ARTHTTPExecutor {
10581082
return task
10591083
}
10601084

1061-
func simulateIncomingServerErrorOnNextRequest(_ errorValue: Int, description: String) {
1085+
func simulateIncomingServerErrorOnNextRequest(_ errorValue: Int, statusCode: Int = 401, description: String, data: [String: Any]? = nil) {
10621086
http.queue.sync {
1063-
errorSimulator = ErrorSimulator(value: errorValue, description: description, statusCode: 401, shouldPerformRequest: false, stubData: nil)
1087+
errorSimulator = ErrorSimulator(value: errorValue, description: description, statusCode: statusCode, shouldPerformRequest: false, stubDataDict: data)
10641088
}
10651089
}
10661090

@@ -1075,7 +1099,6 @@ class TestProxyHTTPExecutor: NSObject, ARTHTTPExecutor {
10751099
errorSimulator = ErrorSimulator(value: 0, description: "", statusCode: 200, shouldPerformRequest: false, stubData: data)
10761100
}
10771101
}
1078-
10791102
}
10801103

10811104
/// Records each message for test purpose.

Test/AblyTests/Tests/UtilitiesTests.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,73 @@ class UtilitiesTests: XCTestCase {
239239
}
240240
}
241241

242+
func test__Utilities__JSON_Encoder__should_decode_rest_error_response_with_only_error_field() throws {
243+
let test = Test()
244+
beforeEach__Utilities__JSON_Encoder()
245+
246+
let options = try AblyTests.commonAppSetup(for: test)
247+
let rest = ARTRest(options: options)
248+
let testHTTPExecutor = TestProxyHTTPExecutor(logger: .init(clientOptions: options))
249+
rest.internal.httpExecutor = testHTTPExecutor
250+
251+
testHTTPExecutor.simulateIncomingServerErrorOnNextRequest(
252+
40400,
253+
statusCode: 404,
254+
description: "Not found",
255+
data: [
256+
"error": "Object not found"
257+
]
258+
)
259+
260+
let request = URLRequest(url: URL(string: "https://www.example.com")!)
261+
waitUntil(timeout: testTimeout) { done in
262+
rest.internal.execute(request, wrapperSDKAgents:nil, completion: { response, _, error in
263+
guard let error = error as? ARTErrorInfo else {
264+
fail("Should be ARTErrorInfo"); done(); return
265+
}
266+
XCTAssertTrue(error.code == 40400)
267+
XCTAssertTrue(error.statusCode == 404)
268+
XCTAssertTrue(error.message == "Object not found")
269+
done()
270+
})
271+
}
272+
}
273+
274+
func test__Utilities__JSON_Encoder__should_decode_rest_error_response_with_complete_error_info() throws {
275+
let test = Test()
276+
beforeEach__Utilities__JSON_Encoder()
277+
278+
let options = try AblyTests.commonAppSetup(for: test)
279+
let rest = ARTRest(options: options)
280+
let testHTTPExecutor = TestProxyHTTPExecutor(logger: .init(clientOptions: options))
281+
rest.internal.httpExecutor = testHTTPExecutor
282+
283+
testHTTPExecutor.simulateIncomingServerErrorOnNextRequest(
284+
40400,
285+
statusCode: 404,
286+
description: "Not found",
287+
data: [
288+
"error": [
289+
"code": 40400,
290+
"message": "Object not found"
291+
]
292+
]
293+
)
294+
295+
let request = URLRequest(url: URL(string: "https://www.example.com")!)
296+
waitUntil(timeout: testTimeout) { done in
297+
rest.internal.execute(request, wrapperSDKAgents:nil, completion: { response, _, error in
298+
guard let error = error as? ARTErrorInfo else {
299+
fail("Should be ARTErrorInfo"); done(); return
300+
}
301+
XCTAssertTrue(error.code == 40400)
302+
XCTAssertTrue(error.statusCode == 404)
303+
XCTAssertTrue(error.message == "Object not found")
304+
done()
305+
})
306+
}
307+
}
308+
242309
func beforeEach__Utilities__EventEmitter() {
243310
eventEmitter = ARTInternalEventEmitter(queue: AblyTests.queue)
244311
receivedFoo1 = nil

0 commit comments

Comments
 (0)