How to get 401 error from servicestack swift client? - swift

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
}

Related

Accessing Google API data from within 3 async callbacks and a function in SwiftUI

I know this question is asked a lot, but I can't figure out how to apply any answers to my program. Sorry in advance this async stuff makes absolutely zero sense to me.
Basically, I have a button in SwiftUI that, when pressed, calls a function that makes two API calls to Google Sheets using Alamofire and GoogleSignIn.
Button("Search") {
if fullName != "" {
print(SheetsAPI.nameSearch(name: fullName, user: vm.getUser()) ?? "Error")
}
}
This function should return the values of some cells on success or nil on an error. However, it only ever prints out "Error". Here is the function code.
static func nameSearch<S: StringProtocol>(name: S, advisory: S = "", user: GIDGoogleUser?) -> [String]? {
let name = String(name)
let advisory = String(advisory)
let writeRange = "'App Control'!A2:C2"
let readRange = "'App Control'!A4:V4"
// This function can only ever run when user is logged in, ! should be fine?
let user = user!
let parameters: [String: Any] = [
"range": writeRange,
"values": [
[
name,
nil,
advisory
]
]
]
// What I want to be returned
var data: [String]?
// Google Identity said use this wrapper so that the OAuth tokens refresh
user.authentication.do { authentication, error in
guard error == nil else { return }
guard let authentication = authentication else { return }
// Get the access token to attach it to a REST or gRPC request.
let token = authentication.accessToken
let headers: HTTPHeaders = ["Authorization": "Bearer \(token)"]
AF.request("url", method: .put, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseString { response in
switch response.result {
case .success:
// I assume there is a better way to make two API calls...
AF.request("anotherURL", headers: headers).responseDecodable(of: NameResponseModel.self) { response2 in
switch response2.result {
case .success:
guard let responseData = response2.value else { return }
data = responseData.values[0]
// print(responseData.values[0]) works fine
case .failure:
print(response2.error ?? "Unknown error.")
data = nil
}
}
case .failure:
print(response.error ?? "Unknown error.")
data = nil
}
}
}
// Always returns nil, "Unknown error." never printed
return data
}
The model struct for my second AF request:
struct NameResponseModel: Decodable { let values: [[String]] }
An example API response for the second AF request:
{
"range": "'App Control'!A4:V4",
"majorDimension": "ROWS",
"values": [
[
"Bob Jones",
"A1234",
"Cathy Jones",
"1234 N. Street St. City, State 12345"
]
]
}
I saw stuff about your own callback function as a function parameter (or something along those lines) to handle this, but I was completely lost. I also looked at Swift async/await, but I don't know how that works with callback functions. Xcode had the option to refactor user.authentication.do { authentication, error in to let authentication = try await user.authentication.do(), but it threw a missing parameter error (the closure it previously had).
EDIT: user.authentication.do also returns void--another reason the refactor didn't work (I think).
There is probably a much more elegant way to do all of this so excuse the possibly atrocious way I did it.
Here is the link to Google Identity Wrapper info.
Thanks in advance for your help.
Solved my own problem.
It appears (according to Apple's async/await intro video) that when you have an unsupported callback that you need to run asynchronously, you wrap it in something called a Continuation, which allows you to manually resume the function on the thread, whether throwing or returning.
So using that code allows you to run the Google Identity token refresh with async/await.
private static func auth(_ user: GIDGoogleUser) async throws -> GIDAuthentication? {
typealias AuthContinuation = CheckedContinuation<GIDAuthentication?, Error>
return try await withCheckedThrowingContinuation { (continuation: AuthContinuation) in
user.authentication.do { authentication, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: authentication)
}
}
}
}
static func search(user: GIDGoogleUser) async throws {
// some code
guard let authentication = try await auth(user) else { ... }
// some code
}
I then ran that before using Alamofire's built-in async/await functionality for each request (here's one).
let dataTask = AF.request(...).serializingDecodable(NameResponseModel.self)
let response = try await dataTask.value
return response.values[0]

Alamofire, Swift: What could be causing this error?

What could be causing this error?
All of a sudden out of nowhere I started getting the error below. I have reinstalled the cocoapod, cleaned the build folder, and reinstalled the app already and none of that has fixed the error.
ERROR: Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
CODE:
let recoverUrl = "http://www.website.com/recover.php?email=\(emailData)&local=application"
let urlEncodedString = recoverUrl.replacingOccurrences(of: " ", with: "%20")
parseRecover(url: urlEncodedString)
//////////////
func parseRecover(url : String){ AF.request(url).responseJSON(completionHandler: { response in self.parseData(JSONData: response.data!) }) }
func parseData(JSONData : Data){
do {
var readableJSON = try JSONSerialization.jsonObject(with: JSONData, options: .mutableContainers) as! JSONObject
if let recoverJSON = readableJSON["Recover"] as? [JSONObject] {
for i in 0..<recoverJSON.count {
let JSON = recoverJSON[i]
let status = JSON["status"] as! String
let message = JSON["message"] as! String
if status == "Error" {self.Alert01("\(message)")}
else if status == "Success" { self.Alert02("\(message)") }
}}}
catch { print(error) }
}
ERROR IS OCCURING AT:
func parseRecover(url : String){ AF.request(url).responseJSON(completionHandler: { response in self.parseData(JSONData: response.data!) }) }
There's no guarantee that a response has data, so force unwrapping the value can lead to crashes. I suggest you create Decodable types to parse your responses and use Alamofire's responseDecodable method to handle your responses.
Additionally, even if you don't adopt Decodable, responseJSON already parses your response Data using JSONSerialization, so you can just access the response.result to see the output.
SOLVED: The issue was within my php file. I was using $_GET[''] and it should have been a $_POST[''] or $_REQUEST['']

AWS API Gateway Error Handling with Generated SDK (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
}

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?

Swift catching error

Why is
if error = response.result.error else {
print (error)
}
Not allowed and throws an error within the function below? I would have expected it should go through well.
Alamofire.request(Router.RegisterUser(parameters))
.validate()
.responseJSON(completionHandler: { (response) -> Void in
if let receivedData = response.result.value {
let json = JSON(receivedData)
// Check for error in JSON
if let message = json["error"]["message"].string {
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: message)
print(error)
return
}
completionHandler(.Success(true), response: JSON(receivedData))
}
if error = response.result.error else {
print (error)
}
})
I use the validate function to avoid successful responses to the completionhandler, when indeed an error code is returned by the server. The problem now is, that the only error I can currently receive is the one, thrown by validate(). But I use error handling in my API as well and will return a suitable error message to the user.
Error Domain=com.alamofire.error Code=-6003 "Response status code was
unacceptable: 400" UserInfo={NSLocalizedFailureReason=Response status
code was unacceptable: 400}
How can I achieve this now? As by using validate, the response will never get processed as far as I understand?
Alamofire.request(Router.RegisterUser(parameters))
.validate()
.responseJSON(completionHandler: { (response) -> Void in
switch(response.result) {
case .Success(let response):
print(response)
case .Failure(let error):
print("FAILURE: \(error)")
}
})
}
There seems to be 2 issues in your code:
You are missing a let or guard to check if there is an error.
print(error) should be inside if as you are checking error is not nil and trying to print error in else which will be nil.