I am trying map error response from server xml. In version 0.21 of restkit it worked ok. In last version it did not (0.22 and above). Possible response from server is <authorization-fail/>. Many server functions can lead to this response.
+ (void)addAuthErrorMapping:(RKObjectManager*)objectManager
{
RKObjectMapping* errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil
toKeyPath:#"errorMessage"]];
RKResponseDescriptor* errorResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"authorization-fail"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:errorResponseDescriptor];
}
In 0.21 version I am getting dictionary with authorisation-fail key. In last versions result is empty dictionary.
This should never have worked.
The server should be returning the response to you with a non-2xx HTTP response code, and you should be using that code to trigger your response descriptor.
As it is, you're triggering the response descriptor all the time and telling it to look for - and drill into - the authorisation-fail. So, if that key exists but is empty you should get an RKErrorMessage that's empty.
If you have some other response descriptor that matches the response before this one (i.e. any other with a nil path pattern and success response code) then this one will never work and that is why you're getting an empty dictionary.
So, the best option is to change the server response, and the workaround solution is to use a dynamic mapping which checks the content and provides either a good response or an error mapping to be used.
Related
I have the following JSON coming from my server...
{"Devices": [
{
"uuid": "d9084134-d5f7-43a3-9613-7faa769a822a",
"label": "",
"location": "a13d45f4-5ce0-48e3-bc3f-4076bb007037",
"capability": 0
},
{
"uuid": "a4ee0d3f-4a6a-4c61-81bd-3dfa9ab19e85",
"label": "",
"location": "a13d45f4-5ce0-48e3-bc3f-4076bb007037",
"capability": 3
}
]}
I'm trying to map this properly but I'm struggling with it... I have the following mappings setup...
RKEntityMapping *entityMapping = [RKEntityMapping mappingForEntityForName:#"Device" inManagedObjectStore:managedObjectStore];
[entityMapping setIdentificationAttributes:[NSArray arrayWithObject:#"uuid"]];
[entityMapping addAttributeMappingsFromDictionary:#{
#"uuid": #"uuid",
#"label": #"label",
#"location": #"location",
#"capability": #"capability"
}];
// GET
RKRequestDescriptor *getRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[entityMapping inverseMapping] objectClass:[Device class] rootKeyPath:#"Device" method:RKRequestMethodGET];
[[RKObjectManager sharedManager] addRequestDescriptor:getRequestDescriptor];
[[RKObjectManager sharedManager].router.routeSet addRoute:[RKRoute routeWithClass:[Device class] pathPattern:#"Devices" method:RKRequestMethodGET]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:entityMapping method:RKRequestMethodGET pathPattern:#"Devices" keyPath:#"Devices" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];
I have my keyPath set to "devices" which I would've thought would work. But RestKit isn't understanding it.
I'm using the following to get the objects from my server...
[[RKObjectManager sharedManager] getObjectsAtPath:#"Devices" parameters:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:#"%d", clientID], #"clientId", #"topic", #"forTopic", nil] success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
...
}... // Abbreviated for brevity.
So I believe my path "Devices" is correct for the pathPattern of "Devices". Are the parameters messing it up? I've had parameters before and it's always worked without having to specify anything in the mapping.
I'm using RestKit 0.27.1 currently.
UPDATE - Fixed JSON
I fixed the JSON as mentioned by Mahendra GP. So this is now a proper array. The RestKit log is now showing the JSON coming through in the trace log. However it still can't identify which object it is (Device).
Snippet of the log...
...and targetObject: (null)
2018-03-09 02:44:05.603734-0700 MyTestApp[31576:6868006] D restkit.object_mapping:RKMapperOperation.m:440 Finished performing object mapping. Results: (null)
2018-03-09 02:44:05.605334-0700 MyTestApp[31576:6868006] E restkit.network:RKObjectRequestOperation.m:172 GET 'http://[MY_IP]:8080/Devices?clientId=5199&forTopic=topic' (200 OK / 0 objects) [request=2.6488s mapping=0.0000s total=2.6633s]: Error Domain=org.restkit.RestKit.ErrorDomain Code=1001 "No response descriptors match the response loaded."
I finally figured out my issue by fluke trial and error. I wasn't able to find any specific posts on SO or over on RestKit's site. Therefore I thought I would post the, simple, solution to the problem here.
I kept getting errors that none of my response descriptors matched any of the key paths. I thought it had to do with the parameters I was passing to the GET as it would show that in the error trying to compare it to just the Devices path that the response descriptor was setup with. However this turned out not to be the issue at all.
The issue was with my RKResponseDescriptor setup. I originally had...
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:entityMapping method:RKRequestMethodGET pathPattern:#"Devices" keyPath:#"Devices" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
The solution here is the "pathPattern". This needed to be set to nil.
I've been using RestKit for many many years. Prior to the 0.2x releases. I remembered something about RestKit being able to identify an entity and it's multiple just by pluralizing it. I finally thought that perhaps because I was specifying it in the pathPattern it was causing the issue. So once I eventually set that to nil it finally started working. I actually think the reason why was that setting it to nil was like setting it it to a wildcard for any pathPattern since my path actually had parameters tagged onto them.
However in another use case I had I did specify the pathPattern but instead of it being the plural of the mapped entity it was something different. So instead of Devices I had something like DeviceForUser. So there seems to be a problem when specifying the plural of the entity in the pathPattern. Especially if using parameters in the getObjectsAtPath call.
So for clarity, all of the code above is the same. It just needs this change...
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:entityMapping method:RKRequestMethodGET pathPattern:nil keyPath:#"Devices" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
I hope this helps anyone else who might run into this.
Having a bit of a strange problem, I'm using RestKit to post a CoreData object to a remote web service. Everything on the remote end looks to be working fine but it seems like RestKit is having some issues mapping the response.
My object response mappings look like this, I have a User class with a UserProfile one to one relationship:
//UserProfile
RKEntityMapping * userProfileMapping =
[RKEntityMapping mappingForEntityForName:NSStringFromClass([UserProfile class])
inManagedObjectStore:[manager managedObjectStore]];
//…mapping
//User
RKEntityMapping * userMapping =
[RKEntityMapping mappingForEntityForName:NSStringFromClass([User class])
inManagedObjectStore:[manager managedObjectStore]];
//…mapping
//relationship mapping
[userMapping addPropertyMapping:[RKRelationshipMapping
relationshipMappingFromKeyPath:#"userProfile"
toKeyPath:#"userProfile"
withMapping:userProfileMapping]];
//add the response mappings for GET success
[manager addResponseDescriptorsFromArray:#[
[RKResponseDescriptor responseDescriptorWithMapping:userMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"currentUser"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]] …rest of my mappings];
The request mapping is the inverse of userMapping
RKRequestDescriptor * currentUserRequestDescriptor =
[RKRequestDescriptor requestDescriptorWithMapping:[userMapping inverseMapping]
objectClass:[User class]
rootKeyPath:#"currentUser"
method:RKRequestMethodPOST];
[manager addRequestDescriptor:currentUserRequestDescriptor];
My GET requests on the User class are working fine, POST is also working correctly.
To update my User coreData object I'm using `postObject'
[objectManager postObject:updatedUser
path:#"update"
parameters:nil
success:…handler
failure:…handler]
The POST works correctly and the object is saved in my remote service.
However, my success handler is never called and I never get the updated User object. I have logging on and I see the valid JSON in the response.
response.body={"currentUser":{….user object}}
Instead, failure is called, it seems to want a 400 response instead of the 200 my service is responding with.
"NSLocalizedDescription" -> "Expected status code in (400-499), got 200"
I don't see a way to set the expected statusCodes on RKRequestDescriptor like on RKResponseDescriptor.
Thanks
Because your User response descriptor uses method:RKRequestMethodGET so it won't be considered as a match for the response from your POST request.
Instead, set the method to RKRequestMethodAny.
Presumably RestKit is expecting a 400-499 because you have a response descriptor configured to handle error responses at that path.
How can I get all the headers that are set for the RKObjectRequestOperation. I need to view the headers as there is something strange happening after a user has logged out, and want to view all headers before the operation starts.
Also is restkit caching authorization headers? I've tried setting the value in the manual request to an empty string and used [[RKObjectManager sharedManager].HTTPClient clearAuthorizationHeader]; to try clearing them, but the server is still receiving the Authorization header.
You can print defaultHeaders to check what is actually set in headers.
[[[RKObjectManager sharedManager] HTTPClient] defaultHeaders]
I am using the postObject and putObject functions of restKit. I need to extract some info from the payload and put it in the header before sending it out.. any suggestions on how I can do that?
ex:
[[RKObjectManager sharedManager] postObject:object path:OBJECT parameters:nil success:success failure:failure];
I will probably have to do something like
// Create Payload for this request
// update header accordingly
// call postObject
I would like to avoid throwing away existing code..
I was able to do this by using all the params that I was passing before making the RK request.
I am writing an iOS app that requests data from Yelp.
I currently have code that manages Yelp requests/responses and parses the JSON data returned internally. The current code builds the OAuth parameters for its requests using OAuthConsumer by Jon Crosby.
I came upon RestKit yesterday, and found it very appealing. It would probably eliminate much of the request submission and response parsing I am doing.
But I hit a roadblock, because I have not been able to figure out how to generate the OAuth parameters that Yelp requires with RestKit. I worked through the Ray Wenderlich RestKit tutorial at http://www.raywenderlich.com/13097/intro-to-restkit-tutorial, but it uses a client ID and client secret (as required by Foursquare).
Yelp requests need to have a consumer key, token, signature method, signature, timestamp and nonce. I have been unable to find an add-on for RestKit that can generate this particular set of OAuth parameters.
I am now generating my RestKit GET requests using an AFOAuth1Client developed by Matt Thompson. Now the Yelp API is returning an Invalid Signature error when I send a request.
I am puzzled because I have checked the fields in the HTTP Authorization header, and they look correct. The error seems to indicate that Yelp wants the oauth parameters in the URL, but the API documentation says that it is acceptable to send them in the authorization header.
Here is the Invalid Signature error I'm getting:
2013-08-26 15:34:54.806 RestKitYelpGroupon[2157:400b] E restkit.network:RKObjectRequestOperation.m:575 Object request failed: Underlying HTTP request operation failed with error: Error Domain=org.restkit.RestKit.ErrorDomain Code=-1011 "Expected status code in (200-299), got 400" UserInfo=0xa876190 {NSLocalizedRecoverySuggestion={"error": {"text": "Signature was invalid", "id": "INVALID_SIGNATURE", "description": "Invalid signature. Expected signature base string: GET\u0026http%3A%2F%2Fapi.yelp.com%2Fv2%2Fsearch\u0026ll%3D32.893282%252C-117.195083%26oauth_consumer_key%3D(MY CONSUMER KEY)%26oauth_nonce%3DC0F25D91-B473-4059-B5F6-2D850A144A1D%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1377556409%26oauth_token%3D(MY OAUTH TOKEN)%26oauth_version%3D1.0%26term%3Dsushi"}}, AFNetworkingOperationFailingURLRequestErrorKey=http://api.yelp.com/v2/search?ll=32.893282%2C-117.195083&term=sushi>, NSErrorFailingURLKey=http://api.yelp.com/v2/search?ll=32.893282%2C-117.195083&term=sushi, NSLocalizedDescription=Expected status code in (200-299), got 400, AFNetworkingOperationFailingURLResponseErrorKey=}
Here is the code I am using to generate the requests:
NSURL *baseURL = [NSURL URLWithString:#"http://api.yelp.com/v2"];
AFOAuth1Client *oauth1Client = [[AFOAuth1Client alloc] initWithBaseURL:baseURL key:consumerKey secret:consumerSecret];
oauth1Client.accessToken = [[AFOAuth1Token alloc] initWithKey:tokenKey
secret:tokenSecret
session:nil
expiration:[NSDate distantFuture]
renewable:NO];
[oauth1Client registerHTTPOperationClass:[AFJSONRequestOperation class]];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[oauth1Client setDefaultHeader:#"Accept" value:#"application/json"];
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:oauth1Client];
RKObjectMapping *couponMapping = [RKObjectMapping mappingForClass:[Coupon class]];
[couponMapping addAttributeMappingsFromDictionary:#{#"id" : #"id"}];
RKObjectMapping *businessMapping = [RKObjectMapping mappingForClass:[Business class]];
[businessMapping addAttributeMappingsFromDictionary:#{#"name" : #"name"}];
[businessMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"deals" toKeyPath:#"location" withMapping:couponMapping]];
RKResponseDescriptor * responseDescriptor = [RKResponseDescriptor
responseDescriptorWithMapping:businessMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"response.venues"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:responseDescriptor];
[objectManager getObjectsAtPath:#"http://api.yelp.com/v2/search"
parameters:queryParams
success:^(RKObjectRequestOperation * operaton, RKMappingResult *mappingResult)
Any further assistance is greatly appreciated!
Take a look at using AFOAuth2Client and setting it as the client when you init your RKObjectManager.