AWS API Gateway Error Handling with Generated SDK (Swift) - swift

I'm fairly new to AWS and using API Gateway -> Lambda -> RDS. Also, I'm using the generated SDK for Swift in my iOS mobile application that is consuming the API.
The straight forward paths work fine when returning success (200). However, I'm trying to add in more error handling for edge cases. If an API request doesn't find the resource it's expected, I'm trying to return a 404 error. I've added this to the API Integration Response and Method Response appropriately. I can test this and get my error model returned as expected along with the correct HTTP Status code of 404.
However, I'm struggling with how to handle this on the mobile application side. How would this be handled by the generated SDK? It just throws an error in the API call and I expected a way to retrieve my "Error" model object that I've defined.
Here is normal path
Here is failed path with 404
Generated SDK (Swift) method:
public func userUseridentityGet(useridentity: String) -> AWSTask<RSAPI_UserModel> {
let headerParameters = [
"Content-Type": "application/json",
"Accept": "application/json",
]
let queryParameters:[String:Any] = [:]
var pathParameters:[String:Any] = [:]
pathParameters["useridentity"] = useridentity
return self.invokeHTTPRequest("GET", urlString: "/user/{useridentity}", pathParameters: pathParameters, queryParameters: queryParameters, headerParameters: headerParameters, body: nil, responseClass: RSAPI_UserModel.self) as! AWSTask<RSAPI_UserModel>
}
At runtime, I get the following logged out when I print the error out:
Error occurred: Error
Domain=com.amazonaws.AWSAPIGatewayErrorDomain Code=1 "(null)"
UserInfo={HTTPBody={
code = 404;
message = "No Result Found.";
"request-id" = "132b8eaa-7b24-11e7-b1fd-d342f0413b7d";
type = NotFound; }, HTTPHeaderFields={type = immutable dict, count = 8, entries => 0 :
x-cache = {contents = "Error
from cloudfront"} 1 : Content-Type = {contents = "application/json"} 3 : x-amzn-requestid =
{contents =
"131a5075-7b24-11e7-87bd-9fcb4cb4e04e"} 4 : Via = {contents = "1.1
bd4761ff0774f9ee778140b91a0431c9.cloudfront.net (CloudFront)"} 6 :
Date = {contents = "Mon, 07 Aug
2017 03:54:13 GMT"} 10 : Content-Length = 134 11 : x-amzn-trace-id =
{contents =
"sampled=0;root=1-5987e464-d3d8f7801b7ae5aa6a52fc1b"} 12 :
x-amz-cf-id = {contents =
"QKpl64W1qDaeo0zlsx2iOwTW0oO_jyPRmMT7ByPKLnen04qiPEeD6w=="} } }
The question is how do I get that de-serialized into my "Error" object model that I've defined? How do I appropriately detect this error condition so I can write logic in my mobile application to handle it?

For a workaround, I've just handled this by manually extracting out the data. If anyone has a better way of dealing with this. I'd definitely be interested.
if let error = task?.error as NSError? {
print("Error occurred: \(error)")
if error.code == 1 {
let httpBody : NSDictionary = (error.userInfo["HTTPBody"] as? NSDictionary)!
print("Code \(httpBody["code"] ?? "none") - Type \(httpBody["type"] ?? "none") - Message \(httpBody["message"] ?? "none") - RequestID \(httpBody["request-id"] ?? "none")")
}
return nil
}

Related

Swift pass json response into PKPass Object

I'm creating a PKPass from my firebase server and trying to send it to my IOS application so the user can add it to their wallet but I'm unsure about how to convert the JSON response data that I receive in my IOS app to the necessary PKPass Object.
JSON Response:
{"Content-Length":"1093412","function-execution-id":"xx84qotfonme","Date":"Wed, 29 Apr 2020 00:44:00 GMT","Content-Disposition":"attachment; filename=Event.pkpass","Server":"Google Frontend","alt-svc":"h3-Q050=\":443\"; ma=2592000,h3-Q049=\":443\"; ma=2592000,h3-Q048=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,h3-T050=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"","Etag":"W\/\"10af24-tF23uU9DIoMPnvm2jrbrN4e5JAg\"","x-cloud-trace-context":"104b4a56a5b82510599fa9ab75fcaf0b;o=1","x-powered-by":"Express","Content-Type":"application\/vnd.apple.pkpass; charset=utf-8"}
It seems like the response contains the ticket as it has filename etc.
IOS Side:
var requestData : [String : String]? = [String : String]()
requestData?.updateValue(postId, forKey: "postId");
AF.request(URLString, method: .post, parameters: requestData).responseString { response in
guard let resp = response.data else { return }
do {
let pass = try PKPass(data: resp)
print(pass)
} catch {
print(error)
}
I get this error when I try to pass the response data in the PKPass object
Error Domain=PKPassKitErrorDomain Code=1 "The pass cannot be read because it isn’t valid." UserInfo={NSLocalizedDescription=The pass cannot be read because it isn’t valid., NSUnderlyingError=0x600003c91a70 {Error Domain=PKPassKitErrorDomain Code=1 "(null)"}}
The event.pass is the one apple provides and I have added my passTypeIdentifier & teamIdentifier which seem to be working fine.
You can't just simply change the pass.json in the example. The manifest has to be regenerated and resigned.
You pass is failing to load because you are using the original signature from the example, but you have changed the data in pass.json which renders the signature invalid.

Cloudant function-clause error at HTTP GET request

This is my first question here and I have not much experience in coding so please bear with me. Thanks!
I defined some documents in my Bluemix Cloudant account with different cars which have different characteristics. I want to get one entry from an IOS Swift front-end App.
This is an example query url:
https://$ACCOUNT-bluemix.cloudant.com/cars/_design/car_index/_search/car_index_name?q=size:small
Now the problem: If I use this url in a browser I get the correct results in JSON format back without any error. But if the app makes the request a function-clause error is logged while the request itself seems to be successful.
I read that a function_clause error is caused by some bug in the Javascript Cloudant uses for indexing the documents. The Javascript I'm using is exactely the same as Cloudant states it in the tutorials.
Has anyone an idea why it works in the browser but not in the App?
Thank you very much for any help!
Here is all the code:
This is the method I use in swift to make the request:
func databaseRequest(size: String, interior: String, fuel: String) {
let baseURL = "https://$ACCOUNT-bluemix.cloudant.com/cars/_design/car_index/_search/car_index_name?q="
let queryURL = "size:\(size)"
let completeURL: String = baseURL + queryURL
let completeURLModified = completeURL.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
let requestURL = URL(string: completeURLModified!)
var request = URLRequest(url: requestURL!)
request.httpMethod = "GET"
request.setValue("Basic \(credentials)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request){data, response, error in
guard error == nil else{
print("There was an error:", error as Any)
return
}
guard data == data else{
print("Data is empty")
return
}
let jsonResponse = try! JSONSerialization.jsonObject(with: data!, options: [])
print("This is JSON Response", jsonResponse)
}; task.resume()
}
This is the response from the JSON answer:
This is JSON Response {
error = "unknown_error";
reason = "function_clause";
ref = 1944801346;
}
The rest of log from http headers if this is helpful:
Optional(<NSHTTPURLResponse: 0x6080000349c0> { URL: https://$ACCOUNT-bluemix.cloudant.com/cars/_design/car_index/_search/car_index_name?q=size:small } { status code: 500, headers {
"Cache-Control" = "must-revalidate";
"Content-Length" = 70;
"Content-Type" = "application/json";
Date = "Thu, 24 Nov 2016 04:41:03 GMT";
Server = "CouchDB/2.0.0 (Erlang OTP/17)";
"Strict-Transport-Security" = "max-age=31536000";
Via = "1.1 lb1.bm-cc-dal-01 (Glum/1.31.3)";
"X-Cloudant-Backend" = "bm-cc-dal-01";
"X-Content-Type-Options" = nosniff;
"X-Couch-Request-ID" = 51e5e0b5e1;
"X-Couch-Stack-Hash" = 1944801346;
"X-CouchDB-Body-Time" = 0;
Last but not least the Javascript file I use as Index in the design document in Cloudant:
function (doc) {
index("name", doc.name, {"store": true});
if (doc.fuel){ index("fuel", doc.fuel, {"store": true});}
if (doc.interior){ index("interior", doc.interior, {"store": true});}
if (doc.size){index("size", doc.size, {"store": true});
}}
I think this error is due to cloudant trying to decode whatever you passed as \(credentials) as a base64 encoded string. If \(credentials) is not a valid base64 encoded string (e.g. contains characters other than a-z, A-Z, 0-9, +, / and =), then my guess is that cloudant's base64 decoding function fails with the above error.
You need to make sure that \(credentials) is the string <your_username>:<your_password> encoded correctly. E.g. if your username is john and your password is doe, then \(credentials) should be am9objpkb2U=.

Swift: listBlobsSegmentedWithContinuationToken method generates errors in Swift sample

I got this code from this github: azure-storage-ios/BlobSample/
I am able to create my container and add blobs to it. I verified they are there in the azure portal. I am using the SAS token method per this:
var containerURL = "https://<mystorage>.blob.core.windows.net/profiles?se=2016-09-05T00%3A00%3A00Z&sp=rw&sv=2015-02-21&sr=c&sig=<mysig>"
var usingSAS = true
self.container = AZSCloudBlobContainer(url: NSURL(string: containerURL)!, error: &error)
// ... later:
blob.uploadFromText(textTextField.text ?? "", completionHandler: { (error: NSError?) -> Void in
})
But now, I am trying to load blob but receive one or more errors!
func reloadBlobList() {
self.container.listBlobsSegmentedWithContinuationToken(nil, prefix: nil, useFlatBlobListing: true, blobListingDetails: AZSBlobListingDetails.All, maxResults: 50) { (error : NSError?, results : AZSBlobResultSegment?) -> Void in
if let error = error {
print("error code! \(error.code)")
print("error description! \(error.description)")
}
I get this error about AuthorizationPermissionMismatch mainly
error code! 3
error description! Error Domain=com.Microsoft.AzureStorage.ErrorDomain Code=3 "(null)" UserInfo={Code=AuthorizationPermissionMismatch, AdditionalErrorDetails={
}, rawErrorData={length = 279, capacity = 279, bytes = 0xefbbbf3c3f786d6c2076657273696f6e ... 3c2f4572726f723e}, OperationContext=, RequestResult=, HTTP Status Code=403, URLResponse= { URL: https://.blob.core.windows.net/profiles?sig=&api-version=2015-04-05&sp=rw&se=2016-09-05T00%3A00%3A00Z&sv=2015-02-21&sr=c&restype=container&comp=list&maxresults=50&include=snapshots,metadata,uncommittedblobs,copy } { status code: 403, headers {
"Content-Length" = 279;
"Content-Type" = "application/xml";
Date = "Fri, 27 May 2016 08:12:19 GMT";
Server = "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0";
"x-ms-request-id" = "90746e2f-0001-0114-7eef-b7be1e000000";
"x-ms-version" = "2015-04-05";
} }, Message=This request is not authorized to perform this operation using this permission.
RequestId:90746e2f-0001-0114-7eef-b
the key I generated earlier had the r/w permission and I was able to post blobs to the container, what else should I've done for the retrieval?
var containerURL =
"https://.blob.core.windows.net/profiles?se=2016-09-05T00%3A00%3A00Z&sp=rw&sv=2015-02-21&sr=c&sig="
In order to list blobs in a blob container, you need List (l) permission in your SAS. Currently you only have Read (r) and Write (w) permission. Can you try by creating a new SAS with List permission as well?

How to get 401 error from servicestack swift client?

First our codes
let req = SignUp()
req.loginName = "abc#abc.com"
req.passWord = "xxx"
do{
let resp = try client.put(req) <---Where we had an error
} catch {
//some error handling
//.....
}
And then, when we input the correct information, everything is fine but when the login credential is wrong, we had expected to get an 401 error with a proper error message, we didn't. And when we traced it trying to find the origin of this, we had traced it back to JsonServicClient.swift (generated from ServiceStack swift plugin for Xcode 7.2), line 266.
public func send<T : JsonSerializable>(intoResponse:T, request:NSMutableURLRequest) throws -> T {
var response:NSURLResponse? = nil
var data = NSData()
do {
data = try NSURLConnection.sendSynchronousRequest(request, returningResponse: &response)
var error:NSError? = NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil)
if let dto = self.handleResponse(intoResponse, data: data, response: response!, error: &error) {
//^^^error here, response is null/can't be null
return dto
}
if let e = error {
throw e
}
return T()
} catch var ex as NSError? {
if let e = self.handleResponse(intoResponse, data: data, response: response!, error: &ex) {
return e
}
throw ex!
}
}
So here is the question, how to properly use ServiceStack swift plugin to get 401 error? When the service returns 400, everything is fine. This problem only happens when the servicestack server api returns 401. Which is by design, we supposed to return 401 when a user authentication fails.
This is a bug in Swift's sendSynchronousRequest which sometimes returns a null response which makes it impossible to determine what the server error response was. I've added a fix for this issue in this commit where it will now throw an unknown Error instead of segfaulting but as Swift doesn't return a HTTP Response we can't query it to return any more info about the error, e.g:
do {
try client.post(request)
} catch let responseError as NSError {
//Swift Bug: 401 returns an unitialized response so status is nil
if let status:ResponseStatus = responseError.convertUserInfo() {
}
}
To get the latest version you can either replace to latest JsonServiceClient.swift or delete the JsonServiceClient.swift and Add/Remove a new Service Reference which will download the latest version.
If you can, you can also switch to use the Async API's which doesn't have this issue:
client.postAsync(request)
.error { responseError in
let status:ResponseStatus = responseError.convertUserInfo()!
status.errorCode //= Unauthorized
}

Alamofire JSON response with strange characters in Swift

I'm using Alamofire 3.0 and swift 2 and get a strange response from the following request:
func requestURLwithHeadersAndParams(URLstr:String, connectionMethod: Alamofire.Method, header: [String : String], body:[String : AnyObject], completion: (reponse: String, statusCode: Int, error: String) -> Void) {
///Singleton Alamofire connection manager
let alamofireManager = Alamofire.Manager.sharedInstance
alamofireManager.request(connectionMethod, URLstr, parameters: body, encoding: ParameterEncoding.JSON, headers: header).responseJSON (){
response in
print("\nSuccess: \(response.result.isSuccess)")
switch response.result {
case .Success(let value):
print("Response Status code: \((response.response?.statusCode)!)\n")
print("\(value)")
completion(reponse: "\(value)" , statusCode: (response.response?.statusCode)! , error: "")
case .Failure(let error):
print("Error Code:\(error.code) - Description:\(error.localizedDescription)")
completion(reponse: "Error" , statusCode: (response.response?.statusCode)!, error: error.localizedDescription)
} } }
the value property contains:
"{\n \"auth_token\" = \"qcW-mQmyX8ototieJu7WK\";\n avatar = {\n standard = {\n url = \"<null>\";\n };\n url = \"<null>\";\n };\n birthdate = \"<null>\";\n \"created_at\" = \"2015-10-28T07:02:20.445Z\";\n email = \"elrope#abt.com\";\n \"first_name\" = El;\n id = 12;\n \"last_name\" = Perro;\n role = user;\n \"shooter_type\" = \"\";\n \"updated_at\" = \"2015-10-28T07:42:37.860Z\";\n}"
any idea how can I get rid of all the escape characters ('\' and '\n') since the JSON serialization using SwiftyJSON in the method calling this method can't recognize the string as a json string and fails when I do let json = JSON(reponse) with error:
Error Domain=SwiftyJSONErrorDomain Code=901 "Dictionary["auth_token"]
failure, It is not an dictionary" UserInfo=0x6030002d7c00
{NSLocalizedDescription=Dictionary["auth_token"] failure, It is not an
dictionary})
I found your JSON invalid itself.
e.g. avatar, standard are not strings, where are the quotes?
I belevie using , and : is better practice than using ; and =
I tested on SwiftyJSON and after above changes everything works fine. So maybe the issue is on your backend. Try to validate raw JSON using web browser.
Your JSON is invalid.
Actually, your value string is nothing else than the .description output of an NSDictionary!
Demonstration in a Playground:
Needless to say that this isn't JSON... so of course it fails when you try to give this to SwiftyJSON.
So, either you were confused somewhere and you're using your dictionary's description instead of its actual content...
...or this error comes from the server.