From 0a033596da6c9446e2bcf4cd67febc83b81b2ca7 Mon Sep 17 00:00:00 2001 From: Blake Watters Date: Sat, 22 Dec 2012 19:03:30 -0500 Subject: [PATCH] Relax the use of use an the informal procotol for the `errorMessage` property in favor of the `description` method. closes #1104, closes #1087, closes #1095 * Change contract to the use the `description` method instead of `errorMessage`. This makes it work with any class out of the box * Add import for RKErrorMessage to the Support.h header so it is immediately available * Fix incorrect keyPath in the README.md * Add additional notes about how the errors are constructed to the README --- Code/Network/RKResponseMapperOperation.h | 4 +-- Code/Network/RKResponseMapperOperation.m | 2 +- Code/ObjectMapping/RKErrorMessage.h | 4 --- Code/ObjectMapping/RKErrorMessage.m | 3 +- Code/Support.h | 1 + README.md | 4 ++- .../Network/RKResponseMapperOperationTest.m | 29 +++++++++++++++++++ 7 files changed, 37 insertions(+), 10 deletions(-) diff --git a/Code/Network/RKResponseMapperOperation.h b/Code/Network/RKResponseMapperOperation.h index 34328465..bd518f38 100644 --- a/Code/Network/RKResponseMapperOperation.h +++ b/Code/Network/RKResponseMapperOperation.h @@ -197,9 +197,9 @@ /** Returns a representation of a mapping result as an `NSError` value. - The returned `NSError` object is in the `RKErrorDomain` domain and has the `RKMappingErrorFromMappingResult` code. The value for the `NSLocalizedDescriptionKey` is computed by retrieving the objects in the mapping result as an array, evaluating `valueForKeyPath:@"errorMessage"` against the array, and joining the returned error messages by comma to form a single string value. The source error objects are returned with the `NSError` in the `userInfo` dictionary under the `RKObjectMapperErrorObjectsKey` key. + The returned `NSError` object is in the `RKErrorDomain` domain and has the `RKMappingErrorFromMappingResult` code. The value for the `NSLocalizedDescriptionKey` is computed by retrieving the objects in the mapping result as an array, evaluating `valueForKeyPath:@"description"` against the array, and joining the returned error messages by comma to form a single string value. The source error objects are returned with the `NSError` in the `userInfo` dictionary under the `RKObjectMapperErrorObjectsKey` key. - The `errorMessage` property is significant as it is an informal protocol that must be adopted by objects wishing to representing response errors. + This implementation assumes that the class used to represent the response error will return a string description of the client side error when sent the `description` message. @return An error object representing the objects contained in the mapping result. @see `RKErrorMessage` diff --git a/Code/Network/RKResponseMapperOperation.m b/Code/Network/RKResponseMapperOperation.m index ddfe301b..93d50a1f 100644 --- a/Code/Network/RKResponseMapperOperation.m +++ b/Code/Network/RKResponseMapperOperation.m @@ -37,7 +37,7 @@ NSError *RKErrorFromMappingResult(RKMappingResult *mappingResult) NSArray *collection = [mappingResult array]; NSString *description = nil; if ([collection count] > 0) { - description = [[collection valueForKeyPath:@"errorMessage"] componentsJoinedByString:@", "]; + description = [[collection valueForKeyPath:@"description"] componentsJoinedByString:@", "]; } else { RKLogWarning(@"Expected mapping result to contain at least one object to construct an error"); } diff --git a/Code/ObjectMapping/RKErrorMessage.h b/Code/ObjectMapping/RKErrorMessage.h index 929d1fde..2aea33fa 100644 --- a/Code/ObjectMapping/RKErrorMessage.h +++ b/Code/ObjectMapping/RKErrorMessage.h @@ -23,10 +23,6 @@ /** The `RKErrorMessage` is a simple class used for representing error messages returned by a remote backend system with which the client application is communicating. Error messages are typically returned in a response body in the Client Error class (status code 4xx range). - ## Error Message Informal Protocol - - The `errorMessage` property method is the sole method of an informal protocol that must be adopted by objects wishing to represent error messages within RestKit. This protocol is by the `RKErrorFromMappingResult` function when constructing `NSError` messages from a mapped response body. - @see `RKErrorFromMappingResult` */ @interface RKErrorMessage : NSObject diff --git a/Code/ObjectMapping/RKErrorMessage.m b/Code/ObjectMapping/RKErrorMessage.m index 6ff7bc85..1a102c4a 100644 --- a/Code/ObjectMapping/RKErrorMessage.m +++ b/Code/ObjectMapping/RKErrorMessage.m @@ -24,8 +24,7 @@ - (NSString *)description { - return [NSString stringWithFormat:@"<%@:%p error message = \"%@\" userInfo = %@>", - NSStringFromClass([self class]), self, self.errorMessage, self.userInfo]; + return self.errorMessage; } @end diff --git a/Code/Support.h b/Code/Support.h index fdc85226..5fe1d789 100644 --- a/Code/Support.h +++ b/Code/Support.h @@ -20,6 +20,7 @@ // Load shared support code #import "RKErrors.h" +#import "RKErrorMessage.h" #import "RKMIMETypes.h" #import "RKLog.h" #import "RKPathMatcher.h" diff --git a/README.md b/README.md index 5be6d3b6..54da4854 100644 --- a/README.md +++ b/README.md @@ -230,9 +230,10 @@ operation.managedObjectCache = managedObjectStore.managedObjectCache; // GET /articles/error.json returns a 422 (Unprocessable Entity) // JSON looks like {"errors": "Some Error Has Occurred"} +// You can map errors to any class, but `RKErrorMessage` is included for free RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]]; // The entire value at the source key path containing the errors maps to the message -[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:[NSNull null] toKeyPath:@"message"]]; +[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:[NSNull null] toKeyPath:@"errorMessage"]]; NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError); // Any response in the 4xx status code range with an "errors" key path uses this mapping @@ -241,6 +242,7 @@ RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptor NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://restkit.org/articles/error.json"]]; RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[errorDescriptor]]; [operation setCompletionBlockWithSuccess:nil failure:^(RKObjectRequestOperation *operation, NSError *error) { + // The `description` method of the class the error is mapped to is used to construct the value of the localizedDescription NSLog(@"Loaded this error: %@", [error localizedDescription]); }]; ``` diff --git a/Tests/Logic/Network/RKResponseMapperOperationTest.m b/Tests/Logic/Network/RKResponseMapperOperationTest.m index b1cf03bc..484d5826 100644 --- a/Tests/Logic/Network/RKResponseMapperOperationTest.m +++ b/Tests/Logic/Network/RKResponseMapperOperationTest.m @@ -14,6 +14,20 @@ NSString *RKPathAndQueryStringFromURLRelativeToURL(NSURL *URL, NSURL *baseURL); +@interface RKServerError : NSObject +@property (nonatomic, copy) NSString *message; +@property (nonatomic, assign) NSInteger code; +@end + +@implementation RKServerError + +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ (%ld)", self.message, (long) self.code]; +} + +@end + @interface RKObjectResponseMapperOperationTest : RKTestCase @end @@ -121,6 +135,21 @@ NSString *RKPathAndQueryStringFromURLRelativeToURL(NSURL *URL, NSURL *baseURL); expect([mapper.error localizedDescription]).to.equal(@"Loaded an unprocessable client error response (422)"); } +- (void)testMappingServerErrorToCustomErrorClass +{ + RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKServerError class]]; + [mapping addAttributeMappingsFromArray:@[ @"code", @"message" ]]; + RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping pathPattern:nil keyPath:nil statusCodes:[NSIndexSet indexSetWithIndex:422]]; + NSURL *URL = [NSURL URLWithString:@"http://restkit.org"]; + NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URL statusCode:422 HTTPVersion:@"1.1" headerFields:@{@"Content-Type": @"application/json"}]; + NSData *data = [@"{\"code\": 12345, \"message\": \"This is the error message\"}" dataUsingEncoding:NSUTF8StringEncoding]; + RKObjectResponseMapperOperation *mapper = [[RKObjectResponseMapperOperation alloc] initWithResponse:response data:data responseDescriptors:@[responseDescriptor]]; + [mapper start]; + expect(mapper.error).notTo.beNil(); + expect(mapper.error.code).to.equal(RKMappingErrorFromMappingResult); + expect([mapper.error localizedDescription]).to.equal(@"This is the error message (12345)"); +} + #pragma mark - Response Descriptor Matching - (void)testThatResponseMapperMatchesBaseURLWithoutPathAppropriately