diff --git a/Code/Network/RKResponseDescriptor.h b/Code/Network/RKResponseDescriptor.h index f4aab9276e..dd41826930 100644 --- a/Code/Network/RKResponseDescriptor.h +++ b/Code/Network/RKResponseDescriptor.h @@ -158,6 +158,16 @@ */ - (BOOL)matchesResponse:(NSHTTPURLResponse *)response; +/** + Returns a Dictionary of parsed arguments extracted from the URL of the given response object. + + @param response The HTTP response object to compare with the base URL, path pattern, and status codes set of the receiver. + @return A dictionary of parsed arguments if the response matches the base URL, path pattern, and status codes set of the receiver, else `nil`. + @see `matchesResponse:` + + */ +- (NSDictionary *)parsedArgumentsFromResponse:(NSHTTPURLResponse *)response; + ///------------------------- /// @name Comparing Response Descriptors ///------------------------- diff --git a/Code/Network/RKResponseDescriptor.m b/Code/Network/RKResponseDescriptor.m index af0391b34e..698e8e1310 100644 --- a/Code/Network/RKResponseDescriptor.m +++ b/Code/Network/RKResponseDescriptor.m @@ -115,32 +115,48 @@ - (NSString *)description } - (BOOL)matchesPath:(NSString *)path +{ + return [self matchesPath:path parsedArguments:nil]; +} + +- (BOOL)matchesPath:(NSString *)path parsedArguments:(NSDictionary **)outParsedArguments { if (!self.pathPattern || !path) return YES; - return [self.pathPatternMatcher matchesPath:path tokenizeQueryStrings:NO parsedArguments:nil]; + RKPathMatcher *pathMatcher = [RKPathMatcher pathMatcherWithPattern:self.pathPattern]; + return [pathMatcher matchesPath:path tokenizeQueryStrings:NO parsedArguments:outParsedArguments]; } - (BOOL)matchesURL:(NSURL *)URL +{ + return [self matchesURL:URL parsedArguments:nil]; +} + +- (BOOL)matchesURL:(NSURL *)URL parsedArguments:(NSDictionary **)outParsedArguments { NSString *pathAndQueryString = RKPathAndQueryStringFromURLRelativeToURL(URL, self.baseURL); if (self.baseURL) { if (! RKURLIsRelativeToURL(URL, self.baseURL)) return NO; - return [self matchesPath:pathAndQueryString]; + return [self matchesPath:pathAndQueryString parsedArguments:outParsedArguments]; } else { - return [self matchesPath:pathAndQueryString]; + return [self matchesPath:pathAndQueryString parsedArguments:outParsedArguments]; } } - (BOOL)matchesResponse:(NSHTTPURLResponse *)response { - if (! [self matchesURL:response.URL]) return NO; + return [self matchesResponse:response parsedArguments:nil]; +} +- (BOOL)matchesResponse:(NSHTTPURLResponse *)response parsedArguments:(NSDictionary **)outParsedArguments +{ + if (![self matchesURL:response.URL parsedArguments:outParsedArguments]) return NO; + if (self.statusCodes) { if (! [self.statusCodes containsIndex:response.statusCode]) { return NO; } } - + return YES; } @@ -149,6 +165,17 @@ - (BOOL)matchesMethod:(RKRequestMethod)method return self.method & method; } +- (NSDictionary *)parsedArgumentsFromResponse:(NSHTTPURLResponse *)response +{ + NSDictionary *parsedArguments = nil; + if ([self matchesResponse:response parsedArguments:&parsedArguments]) + { + return parsedArguments; + } + + return nil; +} + - (BOOL)isEqual:(id)object { if (self == object) { diff --git a/Code/Network/RKResponseMapperOperation.m b/Code/Network/RKResponseMapperOperation.m index 1b5113604e..6e7e0160c1 100644 --- a/Code/Network/RKResponseMapperOperation.m +++ b/Code/Network/RKResponseMapperOperation.m @@ -132,6 +132,7 @@ @interface RKResponseMapperOperation () @property (nonatomic, strong, readwrite) NSError *error; @property (nonatomic, strong, readwrite) NSArray *matchingResponseDescriptors; @property (nonatomic, strong, readwrite) NSDictionary *responseMappingsDictionary; +@property (nonatomic, strong, readwrite) NSDictionary *responseMappingArgumentsDictionary; @property (nonatomic, strong) RKMapperOperation *mapperOperation; @property (nonatomic, copy) id (^willMapDeserializedResponseBlock)(id); @property (nonatomic, copy) void(^didFinishMappingBlock)(RKMappingResult *, NSError *); @@ -189,6 +190,7 @@ - (instancetype)initWithRequest:(NSURLRequest *)request self.responseDescriptors = responseDescriptors; self.matchingResponseDescriptors = [self buildMatchingResponseDescriptors]; self.responseMappingsDictionary = [self buildResponseMappingsDictionary]; + self.responseMappingArgumentsDictionary = [self buildResponseMappingArgumentsDictionary]; self.treatsEmptyResponseAsSuccess = YES; self.mappingMetadata = @{}; // Initialize the metadata } @@ -237,6 +239,30 @@ - (NSDictionary *)buildResponseMappingsDictionary return dictionary; } +- (NSDictionary *)buildResponseMappingArgumentsDictionary +{ + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + for (RKResponseDescriptor *responseDescriptor in self.matchingResponseDescriptors) { + + NSDictionary *arguments = [responseDescriptor parsedArgumentsFromResponse:self.response]; + if (arguments) + { + // We don't add nil keypath at an [NSNull null] key, because that causes a crash later + // in RKDictionaryByMergingDictionaryWithDictionary + if (responseDescriptor.keyPath) + { + [dictionary setObject:arguments forKey:responseDescriptor.keyPath]; + } + else + { + [dictionary addEntriesFromDictionary:arguments]; + } + } + } + + return dictionary; +} + - (RKMappingResult *)performMappingWithObject:(id)sourceObject error:(NSError **)error { @throw [NSException exceptionWithName:NSInternalInconsistencyException @@ -260,6 +286,12 @@ - (void)setMappingMetadata:(NSDictionary *)mappingMetadata NSDictionary *HTTPMetadata = @{ @"HTTP": @{ @"request": @{ @"URL": self.request.URL, @"method": self.request.HTTPMethod, @"headers": [self.request allHTTPHeaderFields] ?: @{} }, @"response": @{ @"URL": self.response.URL, @"headers": [self.response allHeaderFields] ?: @{} } } }; _mappingMetadata = RKDictionaryByMergingDictionaryWithDictionary(HTTPMetadata, mappingMetadata); + + if (self.responseMappingArgumentsDictionary) + { + NSDictionary *argumentsMetadata = @{ @"network" : @{ @"arguments" : self.responseMappingArgumentsDictionary } }; + _mappingMetadata = RKDictionaryByMergingDictionaryWithDictionary(argumentsMetadata, _mappingMetadata); + } } - (void)cancel diff --git a/Tests/Logic/Network/RKResponseMapperOperationTest.m b/Tests/Logic/Network/RKResponseMapperOperationTest.m index 040da5640e..4def58b644 100644 --- a/Tests/Logic/Network/RKResponseMapperOperationTest.m +++ b/Tests/Logic/Network/RKResponseMapperOperationTest.m @@ -518,6 +518,44 @@ - (void)testThatResponseMapperMakesResponseHeadersAvailableToMetadata expect(testUser.name).to.equal(@"application/json"); } +- (void)testThatResponseMapperMakesResponseMappingArgumentsAvailableToMetadataWithNilKeyPath +{ + NSURL *responseURL = [NSURL URLWithString:@"http://restkit.org/api/v1/users"]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:responseURL]; + [request setAllHTTPHeaderFields:@{ @"Content-Type": @"application/xml" }]; + NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:responseURL statusCode:200 HTTPVersion:@"1.1" headerFields:@{@"Content-Type": @"application/json"}]; + NSData *data = [@"{\"name\": \"Blake\"}" dataUsingEncoding:NSUTF8StringEncoding]; + + RKTestUser *testUser = [RKTestUser new]; + RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]]; + [mapping addAttributeMappingsFromDictionary:@{ @"@metadata.network.arguments.version": @"name" }]; + + RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping method:RKRequestMethodAny pathPattern:@"/api/:version/users" keyPath:nil statusCodes:[NSIndexSet indexSetWithIndex:200]]; + RKObjectResponseMapperOperation *mapper = [[RKObjectResponseMapperOperation alloc] initWithRequest:request response:response data:data responseDescriptors:@[ responseDescriptor ]]; + mapper.targetObject = testUser; + [mapper start]; + expect(testUser.name).to.equal(@"v1"); +} + +- (void)testThatResponseMapperMakesResponseMappingArgumentsAvailableToMetadataWithNonNilKeyPath +{ + NSURL *responseURL = [NSURL URLWithString:@"http://restkit.org/api/v1/users"]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:responseURL]; + [request setAllHTTPHeaderFields:@{ @"Content-Type": @"application/xml" }]; + NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:responseURL statusCode:200 HTTPVersion:@"1.1" headerFields:@{@"Content-Type": @"application/json"}]; + NSData *data = [@"{\"users\":{\"name\": \"Blake\"}}" dataUsingEncoding:NSUTF8StringEncoding]; + + RKTestUser *testUser = [RKTestUser new]; + RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[RKTestUser class]]; + [mapping addAttributeMappingsFromDictionary:@{ @"@metadata.network.arguments.users.version": @"name" }]; + + RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping method:RKRequestMethodAny pathPattern:@"/api/:version/users" keyPath:@"users" statusCodes:[NSIndexSet indexSetWithIndex:200]]; + RKObjectResponseMapperOperation *mapper = [[RKObjectResponseMapperOperation alloc] initWithRequest:request response:response data:data responseDescriptors:@[ responseDescriptor ]]; + mapper.targetObject = testUser; + [mapper start]; + expect(testUser.name).to.equal(@"v1"); +} + - (void)testThatResponseMapperMergesExistingMetadata { NSURL *responseURL = [NSURL URLWithString:@"http://restkit.org/api/v1/users"];