Networking error management

Hello,

I encounter two issues with my networking function.
The function is working properly when all entries are correctly entered.
Now I would like to implement my error management.
I'm having this first issue:
Invalid conversion from throwing function of type '(Data?, URLResponse?, Error?) throws -> Void' to non-throwing function type '(Data?, URLResponse?, Error?) -> Void' (line 15)

Here is the code :
Code Block Swift
func connect(url: String) throws {
        guard let encoded = try? JSONEncoder().encode(connexion) else {
            print("Fail to encode newMed")
            return
        }
        let url = URL(string: "/api/\(url)")!
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        request.httpBody = encoded
        URLSession.shared.dataTask(with: url) { data, res, error in
            guard let httpResponse = res as? HTTPURLResponse,
                    (200...299).contains(httpResponse.statusCode)
            else if (res == nil) {
                    throw ConnexionError.invalidServerRes /* here is the error */
                } else {
                self.handleServerError(res!)
                return
            }
            if let data = data {
                let decoder = JSONDecoder()
                if let json = try? decoder.decode(Connexion.self, from: data) {
                    print(json)
                    self.connexion.id = json.id
                    self.connexion.token = json.token
                    self.connexion.sessionID = json.sessionID
                    self.signInSuccess = true
                } else {
                    let dataString = String(decoding: data, as: UTF8.self)
                    print("Invalid response \(dataString)")
                }
            }
        }.resume()
    }
}

This is the enum would like to use for the error handling:
Code Block
enum ConnexionError: Error {
    case invalidPasswd
    case invalidId
    case invalidServerRes
}

This is my class:
Code Block
class Connexion: Codable, ObservableObject {
    enum CodingKeys: String, CodingKey {
        case email, password, id, token, sessionID
    }
    @Published var token: String = ""
    @Published var sessionID: String = ""
    @Published var id: String = ""    
    @Published var email: String = ""
    @Published var password: String = ""
    init() {    }
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        sessionID = try container.decode(String.self, forKey: .sessionID)
        token = try container.decode(String.self, forKey: .token)
        id = try container.decode(String.self, forKey: .id)
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(email, forKey: .email)
        try container.encode(password, forKey: .password)
    }
}

https://developer.apple.com/forums/thread/671055


The second error, is coming out of nowhere... (if you don't know it is okay)
When using Postman, I get this:
Code Block
{
"id": "5fc565209ce8b43c1315da9b",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
"sessionID": "O7KdKIuDMVHeVV91AWcApPYezCXEfz7n"
}

But when I print the dataString (line 30) it's another story. I get almost all the BDD ...
Answered by OOPer in 658495022

Also, why can't I implement throwsin my function when it is throwing ?

If you declare your connect as throws, it should:
  • return normally

  • return with error (when error thrown)

The completion handler of dataTask is executed after connect returned.

This is why I would like to implement a throw scenario to my request

If you are having such use case in mind, completion handler pattern might be easier:
Code Block
func connect(url: String,
onError errorHandler: @escaping (Error?)->Void) { //<-
let encoded: Data
do {
encoded = try JSONEncoder().encode(connexion)
} catch {
print("Fail to encode newMed: \(error)")
return
}
let url = URL(string: "/api/\(url)")!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = encoded
URLSession.shared.dataTask(with: url) { data, res, error in
if let error = error {
errorHandler(error)
return
}
guard let response = res else {
errorHandler(ConnexionError.invalidServerRes) //<-
return
}
guard let httpResponse = response as? HTTPURLResponse,
case 200...299 = httpResponse.statusCode
else {
self.handleServerError(res!)
return
}
guard let data = data else {
print("data is nil")
return
}
let decoder = JSONDecoder()
do {
let json = try decoder.decode(Connexion.self, from: data)
print(json)
self.connexion.id = json.id
self.connexion.token = json.token
self.connexion.sessionID = json.sessionID
self.signInSuccess = true
} catch {
let dataString = String(data: data, encoding: .utf8) ?? "Unknown encoding"
print("Invalid response \(error) - \(dataString)")
}
}.resume()
}

And use it as:
Code Block
Button(action: {
self.showGroup = false
if connexion.isNotEmpty {
self.connect(url: "url", onError: { err in
self.showError = true
})
} else {
self.showEmpty = true
}
}) {
Text("button")
}



Settings : 

Sorry, far from I expected. Please show all the customizable things of Postman, including Http Method, Authorization, Header and Body.

I'm having this first issue:

It is clear. You cannot throw from inside the completion handler of dataTask.
The closure is called by iOS and iOS would never handle errors.

You usually use (your own) completion handler to tell error to the caller, but in your case, adding a new @Published property holding the error would be an alternative.


when I print the dataString

Please show the core part of the dataString.
And please show the settings of Postman when you get the right response.
I guess you are not sending the same request in your code.

You usually use (your own) completion handler to tell error to the caller, but in your case, adding a new @Published property holding the error would be an alternative.

So in my class I should be adding a @Published property variable to handle errors ?

This is why I would like to implement a throw scenario to my request
Code Block Swift
Button(action: {
    self.showGroup = false
    if (connexion.checkEmpty) {
do {
            try self.connect(url: "url")
        } catch {
            self.showError = true
        }
  } else {
        self.showEmpty = true
    }
}) {
    Text("button")
}

If iOS doesn't allow me to make throws is it better to make small functions for error management.
Code Block Swift
            URLSession.shared.dataTask(with: request) { data, res, error in
                guard let httpResponse = res as? HTTPURLResponse,
                        (200...299).contains(httpResponse.statusCode) else {
                        self.handleServerError(res)
                    return

Code Block Swift
func handleServerError(_ res: URLResponse?) {
    print("ERROR: Status Code: \(res!): the status code MUST be between 200 and 299")
}

Also, why can't I implement throws in my function when it is throwing ?
Code Block
func connect(url: String) throws {




This is an exemple of the POST
Code Block
{"email":"XXXX@hell.com","password":"123456!"}

Settings :
Code Block
KEY = Content-Type
VALUE = application/json

Here is and example of the dataString I'm receiving:
Code Block
[{"idTransfert":[],"_id":"60099ddc0eb9734ede9e831d","email":"test@test.eu","password":"4f8c9f9b94527112dbc","tokens":[{"_id":"60099de20eb9734ede9e8320","token":"eyJhbGciVCJ9.eyJfaWQ9.nEh2K7W0"},{"_id":"60099e160eb9734ede9e8323","token":"eyJhbGciOiJI.eyJfaWQiOiI2M9.nkJRk134b0"}],"sessions":[{"_id":"60099e160eb9734ede9e8325","sessionId":"nQIGGmN-766Z6nLwJBwSuLsQwlwP-WFu"}],"__v":2}]

Here another version of my class:
Code Block Swift
class Connexion: Codable, ObservableObject {
    enum CodingKeys: String, CodingKey {
        case email, password, id, token, sessionID
    }
/*   @Published var token: [Token] = [.init()] */
/*   @Published var sessionID: [SessionID] = [.init()]*/
    @Published var token: String = ""
    @Published var sessionID: String = ""
    @Published var id: String = ""
    @Published var email: String = ""
    @Published var password: String = ""
    init() {    }
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
/*        sessionID = try container.decode([SessionID].self, forKey: .sessionID)*/
/*        token = try container.decode([Token].self, forKey: .token)*/
        sessionID = try container.decode(String.self, forKey: .sessionID)
        token = try container.decode(String.self, forKey: .token)
        id = try container.decode(String.self, forKey: .id)
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(email, forKey: .email)
        try container.encode(password, forKey: .password)
    }
    var checkEmpty: Bool {
        if (email.isEmpty || password.isEmpty) {
            return false
        }
        return true
    }
}

Accepted Answer

Also, why can't I implement throwsin my function when it is throwing ?

If you declare your connect as throws, it should:
  • return normally

  • return with error (when error thrown)

The completion handler of dataTask is executed after connect returned.

This is why I would like to implement a throw scenario to my request

If you are having such use case in mind, completion handler pattern might be easier:
Code Block
func connect(url: String,
onError errorHandler: @escaping (Error?)->Void) { //<-
let encoded: Data
do {
encoded = try JSONEncoder().encode(connexion)
} catch {
print("Fail to encode newMed: \(error)")
return
}
let url = URL(string: "/api/\(url)")!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = encoded
URLSession.shared.dataTask(with: url) { data, res, error in
if let error = error {
errorHandler(error)
return
}
guard let response = res else {
errorHandler(ConnexionError.invalidServerRes) //<-
return
}
guard let httpResponse = response as? HTTPURLResponse,
case 200...299 = httpResponse.statusCode
else {
self.handleServerError(res!)
return
}
guard let data = data else {
print("data is nil")
return
}
let decoder = JSONDecoder()
do {
let json = try decoder.decode(Connexion.self, from: data)
print(json)
self.connexion.id = json.id
self.connexion.token = json.token
self.connexion.sessionID = json.sessionID
self.signInSuccess = true
} catch {
let dataString = String(data: data, encoding: .utf8) ?? "Unknown encoding"
print("Invalid response \(error) - \(dataString)")
}
}.resume()
}

And use it as:
Code Block
Button(action: {
self.showGroup = false
if connexion.isNotEmpty {
self.connect(url: "url", onError: { err in
self.showError = true
})
} else {
self.showEmpty = true
}
}) {
Text("button")
}



Settings : 

Sorry, far from I expected. Please show all the customizable things of Postman, including Http Method, Authorization, Header and Body.

I have this error when printing from line 44 of the code you send.
Code Block
Invalid response typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil)) -


I don't have other setting
Http Method: POST
Body: raw:
Code Block
{"email":"XXXX@hell.com","password":"123456!"}

Headers : 
Code Block
KEY = Content-Type
VALUE = application/json

Here is and example of the dataString I'm receiving from my request in my code:
Code Block
[
{
"idTransfert":[],"
_id":"60099ddc0eb9734ede9e831d",
"email":"test@test.eu",
"password":"4f8c9f9b94527112dbc",
"tokens":[
{
"_id":"60099de20eb9734ede9e8320",
"token":"eyJhbGciVCJ9.eyJfaWQ9.nEh2K7W0"
},
{
"_id":"60099e160eb9734ede9e8323",
"token":"eyJhbGciOiJI.eyJfaWQiOiI2M9.nkJR k134b0"
}
],
"sessions":
[
{
"_id":"60099e160eb9734ede9e8325",
"sessionId":"nQIGGmN-766Z6nLwJBwSuLsQwlwP-WFu"
}
],
"__v":2
}
]

API response:
status 200
Code Block  
{
"id": "5fc565209ce8b43c1315da9b",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
"sessionID": "xiJOIhPj4l1qYz9uQRwAnWN38QBy12Qp"
}

I have this error when printing from line 44 of the code you send.

That is the expected result when the response data is as your dataString.


I don't have other setting 

Then I have no clue as for now. Better re-check all the values -- url, email, password, or any other parameters if more.
I edited y Connexion Class:
Code Block
class Connexion: Codable, ObservableObject {
    enum CodingKeys: String, CodingKey {
        case email, password, id = "_id", token = "tokens", sessionID = "sessions", idTransfert
    }
    @Published var token: [Token] = [.init()] /* 1 */
    @Published var sessionID: [SessionID] = [.init()] /* 1 */
    @Published var idTransfert: [String] = []
    @Published var id: String = ""
    @Published var email: String = ""
    @Published var password: String = ""
}

(1) they both have _id and token or sessionId depending on the class.
But I think what could cause the error is the array [ { ... } ] of the JSON.

I tried to make make an array from the Codingkey like so:
Code Block Swift
let container = try decoder.container(keyedBy: [CodingKeys].self)

But Instance method 'container(keyedBy:)' requires that '[Connexion.CodingKeys]' conform to 'CodingKey'

Do have any ideas ?

PS: tbh their API sucks a lot
Networking error management
 
 
Q