how to determine the specific 409 error from an AFError? - ios

I have a method that returns a Single<(HTTPURLResponse, Any)> doing a call to a webservice.
This call returns an 409 for multiple reasons and this reason is passed as a JSON in the response.
I know the JSON is in the data attribute of the DataResponse object but I would like to have it in the AFError that I pass when an error occurs. I want to display the specific 409 error message related to the JSON response to the user to allow him understand what happened.
How could I do that ?
I searched for that in Stackoverflow and also on the github of Alamofire but couldn't find any help to my case.
return Single<(HTTPURLResponse, Any)>.create(subscribe: { single in
let request = self.sessionManager.request(completeURL, method: httpMethod, parameters: params, encoding: encoding, headers: headers)
request.validate().responseJSON(completionHandler: { (response) in
let result = response.result
switch result {
case let .success(value): single(.success((response.response!, value)))
case let .failure(error): single(.error(error))
}
})
return Disposables.create { request.cancel() }
})
I'm working with Alamofire 4.9.1

request.validate().responseJSON { (response) in
let statusCode = response.response?.statusCode ?? 0
guard statusCode != 409 else {
if let data = response.data, let errorJson = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
let errorMessage = errorJson["message"] as? String
let customError = CustomError(message: errorMessage)
single(.error(customError))
}
return
}
let result = response.result
switch result {
case let .success(value): single(.success((response.response!, value)))
case let .failure(error): single(.error(error))
}
}
I guess you can achieve your requirement by this way. create a custom Error class to pass the error to completion. dont forget to call completion if errorJson is not serialised.
class CustomError: Error {
var localizedDescription: String { message ?? "" }
var message: String?
init(message: String?) {
self.message = message
}
}

Related

Swift - Alamofire - Completion Handler is not executed when I call my function

As the title says, I have a function with these parameters:
typealias Handler = (Swift.Result<Any?, APIErrors>) -> Void
func viewApi (ownerId: String, accessToken: String, completionHandler: #escaping Handler ){
var viewArray: [Information] = [Information]()
let headers: HTTPHeaders = [
.authorization("Bearer \(accessToken)")
]
let viewUrl = "\(viewTaskUrl)?userId=\(ownerId)"
let url = URL(string: viewUrl)
var request = URLRequest(url: url!)
request.method = .get
request.headers = headers
var taskList:[Task] = []
AF.request(request).responseJSON(completionHandler: { (response) in
var holder = Information(id: 0, title: "", description: "", status: "")
switch response.result {
case .success(let data):
do{
if response.response?.statusCode == 200 {
// completionHandler(.success("JSON HERE: \
(String(describing: json))"))
let jsonData = try JSONSerialization.data(withJSONObject: data)
let jsonString = String(data: jsonData, encoding: .utf8)
let trySplit = jsonString?.components(separatedBy: ",")
for i in 0...trySplit!.count-1{
if trySplit![i].contains("id"){
let id = trySplit![i].components(separatedBy: ":")[1]
holder.id = Int(id)!
}
if trySplit![i].contains("title"){
let title = trySplit![i].components(separatedBy: ":")[1]
holder.title = title
// print("TITLE: \(title)")
}
if trySplit![i].contains("description"){
let desc = trySplit![i].components(separatedBy: ":")[1]
holder.description = desc
}
if trySplit![i].contains("status"){
let stat = trySplit![i].components(separatedBy: ":")[1]
let edited = stat.replacingOccurrences(of: "}", with: "")
holder.status = edited
viewArray.append(holder)
}
}
}else{
// completionHandler(.failure(.custom(message: "Please check your network connectivity")))
print("Error")
}
}catch{
print(error.localizedDescription)
}
case .failure(let err):
print (err)
}
print("ARRAY: \(viewArray)")
for i in 0 ..< viewArray.count {
let task = Task()
let edited = viewArray[i].status.replacingOccurrences(of: "]", with: "")
let new = viewArray[i].title.replacingOccurrences(of: "\"", with: "")
task.taskName = new
task.id = viewArray[i].id
task.taskStatus = edited
task.taskDescription = viewArray[i].description
task.email = ownerId
taskList.append(task)
}
try! self.realm.write{
self.realm.add(taskList)
}
}).resume()
}
and that function runs well, it does what it needs to do by returning a JSON object and storing the contents in Realm but when I call it in my main ViewController, it does not go in the completionHandler and tries to store the contents from Realm into an array returns an empty array even though the Realm is populated. Here is the call in my ViewController:
self.APIManager.viewApi(ownerId: self.userId!, accessToken: self.token!) { (result) in
switch result{
case .success(_):
//does not run
print("SUCCESS COMPLETION")
case .failure(let err):
print(err.localizedDescription)
}
}
Is there any way for the function to run before the rest of the code is executed so that the array is not empty and so that it actually goes in my completionHandler?
P.S. Don't mind the janky way I'm storing the JSON Object since I still have to find a better way to store it into an array besides converting it to string and then cutting it
try something like this approach:
func viewApi (ownerId: String, accessToken: String, completionHandler: #escaping Handler ) {
//...
AF.request(request).responseJSON { response in // <-- here
//...
completionHandler(.success("JSON HERE:(String(describing: json))")) // <-- here
//...
}
//...
}
what is your Handler type ? and why the completion handler is not setting
return the handler type according to the failure, success example.
self.APIManager.viewApi(ownerId: self.userId!, accessToken: self.token!, completionHandler: CompletionHandler) { (result) in
switch result{
case .success(_):
completionHandler(success:true)
case .failure(let err):
print(err.localizedDescription)
completionHandler(success: false)
}
}
you can return the raw response as it is in the completion, write a helper class to process the json response as per your requirement

How to get key Value from post webservice API and fetch that to tableview

I got data from API in this format but the problem is that I want to get all questions and answers from the API but whenever I try to get the value by using the key value it returns nil value and application crashes
this is my api data looks like after getting into a dictionary
here's my code for getting data from API
Alamofire.request(url, method: .post, parameters: parameters,encoding: JSONEncoding.default, headers: header ).responseJSON {
response in
switch response.result {
case .success:
print(response)
if let result = response.result.value {
print(result)
let responseDict = result as! [String : Any]
print(responseDict)
let data = responseDict["Result"] as! [Any]
print(data)
}
break
case .failure(let error):
print(error)
}
}
You can try
if let res = responseDict["Result"] as? [[String:Any]] {
for item in res {
if let ques = item["Question"] as? String {
print(ques)
}
if let op = item["Options"] as? [[String:Any]] {
print(op)
}
}
}

errors decoding json with Alamofire

I try to decode JSON data from web using Alamofire. My app is sending the same GET requests, which differs by id. Some JSON is decoded successfully, but some can not be decoded. What can be the problem? How can I solve this issue? All responses are checked by JSON validator and are valid. Trying to decode with URLSession.shared.dataTask(with: url) just cannot decode a single response, even response that was successfully decoded with Alamofire
Code is:
var hostURL = "https://public-api.nazk.gov.ua/v1/declaration/"
hostURL = hostURL + declarationID
print(hostURL)
Alamofire.request(hostURL).responseData { response in
switch response.result {
case .success(let data):
let declarationInfoElement = try? JSONDecoder().decode(DeclarationInfoElement.self, from: data)
print(declarationInfoElement)
case .failure:
print("fail")
}
}
Console output is:
https://public-api.nazk.gov.ua/v1/declaration/3509369f-b751-444a-be38-dfa66bb8728f
https://public-api.nazk.gov.ua/v1/declaration/3e7ad106-2053-48e4-a5d2-a65a9af313be
https://public-api.nazk.gov.ua/v1/declaration/743b61d5-5082-409f-baa0-9742b4cc2751
https://public-api.nazk.gov.ua/v1/declaration/5d98b3d9-8ca6-4d5d-b39f-e4de98d451aa
https://public-api.nazk.gov.ua/v1/declaration/7e3c488c-4d6a-49a3-aefb-c760f317dca4
nil
Optional(Customs_UA.DeclarationInfoElement(id: "4647cd5d-5877-4606-8e61-5ac5869b71e0")
nil
nil
nil
#objc func getJSON(){
let hostURL = "https://public-api.nazk.gov.ua/v1/declaration/"
print(hostURL)
Alamofire.request(hostURL).responseData { response in
switch response.result {
case .success(let data):
do {
if let json = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? Dictionary<String,Any>
{
print(json)
} else {
print("bad json")
}
} catch let error as NSError {
print(error)
}
print(data)
case .failure:
print("fail")
}
}
}
The problem is that some parameters of the JSON are optional. You have to post your DeclarationInfoElement class to check.
Use something like this to detect the error.
class DeclarationInfoElement: Decodable {
let id: String?
let created_date: String?
/// and so on
}

Parsing JSON Lines with Alamofire/Codable

Is it possible to parse JSON lines with Alamofire and codable?
Here is my code right now.
Alamofire.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseString {(response) in
switch response.result {
case .success(let value):
print ("response is \(value)")
case .failure(let error):
print ("error is \(error)")
}
}
This prints all the JSON lines as a string but I want to serialize the response as an array of JSON. How would I do that? The problem with JSON lines is that it returns each set of json on a separate line so it is not familiar to alamofire.
Here is what I tried as if this was traditional JSON which obviously it is not so this did not work:
Alamofire.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON {(response) in
switch response.result {
case .success(let value):
print ("response is \(value)")
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
let data = try! JSONSerialization.data(withJSONObject: value)
do {
let logs = try decoder.decode([Logs].self, from: data)
completion(logs)
} catch let error {
print ("error parsing get logs: \(error)")
}
case .failure(let error):
print ("failed get logs: \(error) ** \(response.result.value ?? "")")
}
}
For anybody unfamiliar with json lines here is the official format info: http://jsonlines.org
{"_logtype":"syslogline","_ingester":"agent","_ip":"40.121.203.183","pid":5573,"program":"docker","_host":"k8s-master-5A226838-0","logsource":"k8s-master-5A226838-0","_app":"syslog","_file":"/var/log/syslog","_line":"docker[5573]: I0411 00:18:39.644199 6124 conversion.go:134] failed to handle multiple devices for container. Skipping Filesystem stats","_ts":1491869920198,"timestamp":"2017-04-11T00:18:39.000Z","_id":"804760774821019649"}
{"_logtype":"syslogline","_ingester":"agent","_ip":"40.121.203.183","pid":5573,"program":"docker","_host":"k8s-master-5A226838-0","logsource":"k8s-master-5A226838-0","_app":"syslog","_file":"/var/log/syslog","_line":"docker[5573]: I0411 00:18:39.644167 6124 conversion.go:134] failed to handle multiple devices for container. Skipping Filesystem stats","_ts":1491869920198,"timestamp":"2017-04-11T00:18:39.000Z","_id":"804760774821019648"}
{"_logtype":"syslogline","_ingester":"agent","_ip":"40.121.203.183","pid":5573,"program":"docker","_host":"k8s-master-5A226838-0","logsource":"k8s-master-5A226838-0","_app":"syslog","_file":"/var/log/syslog","_line":"docker[5573]: I0411 00:18:37.053730 6124 operation_executor.go:917] MountVolume.SetUp succeeded for volume \"kubernetes.io/secret/6f322c04-e1d2-11e6-bca0-000d3a111245-default-token-swb07\" (spec.Name: \"default-token-swb07\") pod \"6f322c04-e1d2-11e6-bca0-000d3a111245\" (UID: \"6f322c04-e1d2-11e6-bca0-000d3a111245\").","_ts":1491869917193,"timestamp":"2017-04-11T00:18:37.000Z","_id":"804760762212941824"}
Here is an example of writing custom DataSerializer in Alamofire that you could use for decoding your Decodable object.
I am using an example from random posts json url https://jsonplaceholder.typicode.com/posts
Here is an example of Post class and custom serializer class PostDataSerializer.
struct Post: Decodable {
let userId: Int
let id: Int
let title: String
let body: String
}
struct PostDataSerializer: DataResponseSerializerProtocol {
enum PostDataSerializerError: Error {
case InvalidData
}
var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<[Post]> {
return { request, response, data, error in
if let error = error {
return .failure(error)
}
guard let data = data else {
return .failure(PostDataSerializerError.InvalidData)
}
do {
let jsonDecoder = JSONDecoder()
let posts = try jsonDecoder.decode([Post].self, from: data)
return .success(posts)
} catch {
return .failure(error)
}
}
}
}
You could simply hook this upto your Alamofire client which sends request to remote url like so,
let request = Alamofire.request("https://jsonplaceholder.typicode.com/posts")
let postDataSerializer = PostDataSerializer()
request.response(responseSerializer: postDataSerializer) { response in
print(response)
}
You could also do additional error checking for the error and http response code in serializeResponse getter of the custom serializer.
For you json line, it seems that each json object is separated with new line character in so called json line format. You could simply split the line with new line characters and decode each line to Log.
Here is Log class I created.
struct Log: Decodable {
let logType: String
let ingester: String
let ip: String
let pid: Int
let host: String
let logsource: String
let app: String
let file: String
let line: String
let ts: Float64
let timestamp: String
let id: String
enum CodingKeys: String, CodingKey {
case logType = "_logtype"
case ingester = "_ingester"
case ip = "_ip"
case pid
case host = "_host"
case logsource
case app = "_app"
case file = "_file"
case line = "_line"
case ts = "_ts"
case timestamp
case id = "_id"
}
}
And custom log serializer that you could use with your Alamofire. I have not handled error in the following serializer, I hope you can do it.
struct LogDataSerializer: DataResponseSerializerProtocol {
enum LogDataSerializerError: Error {
case InvalidData
}
var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<[Post]> {
return { request, response, data, error in
if let error = error {
return .failure(error)
}
guard let data = data else {
return .failure(LogDataSerializerError.InvalidData)
}
do {
let jsonDecoder = JSONDecoder()
let string = String(data: data, encoding: .utf8)!
let allLogs = string.components(separatedBy: .newlines)
.filter { $0 != "" }
.map { jsonLine -> Log? in
guard let data = jsonLine.data(using: .utf8) else {
return nil
}
return try? jsonDecoder.decode(Log.self, from: data)
}.flatMap { $0 }
return .success(allLogs)
} catch {
return .failure(error)
}
}
}
}
Alamofire is extensible, so I'd suggest writing your own response ResponseSerializer that can parse JSON by line. It seems that each line should parse fine, they're just not parseable together since the whole document isn't valid JSON.

Alamofire request coming up nil

I'm developing an iOS app which user WebServices and I find Alamofire just perfect for what I'm doing but I'm having a problem; the app asks the user to login which is an Alamofire call and does it just fine.
The problem is, it has to create a collection view based on the content of another Alamofire request but is always nil.
func getJSON(URLToRequest: String) -> JSON {
let comp:String = (prefs.valueForKey("COMPANY") as? String)!
let params = ["company":comp]
var json:JSON!
let request = Alamofire.request(.POST, URLToRequest, parameters: params).responseJSON {
response in
switch response.result {
case .Success:
if let value = response.result.value {
json = JSON(value)
}
default:
json = JSON("");
}
}
debugPrint(request.response)
return json;
}
The same codeblock works perfect for the Login but doesn't in this case BTW the debug Print always print nil
You're trying to access to request.response before it has been set, remember that Alamofire works asynchronously, so you have to return in your case the JSON using closures, but remember that Alamofire also returns an error, so I strongly recommend use the following code instead:
func getJSON(URLToRequest: String, completionHandler: (inner: () throws -> JSON?) -> ()) {
let comp:String = (prefs.valueForKey("COMPANY") as? String)!
let params = ["company":comp]
let request = Alamofire.request(.POST, URLToRequest, parameters: params).responseJSON {
response in
// JSON to return
var json : JSON?
switch response.result {
case .Success:
if let value = response.result.value {
json = JSON(value)
}
completionHandler(inner: { return json })
case .Failure(let error):
completionHandler(inner: { throw error })
}
}
The trick is that the getJSON function takes an additional closure called 'inner' of the type () throws -> JSON?. This closure will either provide the result of the computation, or it will throw. The closure itself is being constructed during the computation by one of two means:
In case of an error: inner: {throw error}
In case of success: inner: {return json}
And then you can call it like in this way:
self.getJSON("urlTORequest") { (inner: () throws -> JSON?) -> Void in
do {
let result = try inner()
} catch let error {
print(error)
}
}
I hope this help you.

Resources