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
This commit is contained in:
Blake Watters
2012-12-22 19:03:30 -05:00
parent 7984d4f2ac
commit 0a033596da
7 changed files with 37 additions and 10 deletions

View File

@@ -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`

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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

View File

@@ -20,6 +20,7 @@
// Load shared support code
#import "RKErrors.h"
#import "RKErrorMessage.h"
#import "RKMIMETypes.h"
#import "RKLog.h"
#import "RKPathMatcher.h"

View File

@@ -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]);
}];
```

View File

@@ -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