2

This question will probably come off as very basic, and something that probably can be found through searching, but despite my efforts of searching StackOverflow and just google I can't find any up-to-date thread or post regarding how to handle the different responses of a REST API, and, as I've found out, having an up-to-date thread is important to save trouble down the road when errors occur. So, to jump into it, I have an API endpoint on my server for logging in. It responds, as one would assume, with either two cases given login credentials;

If the login information succeeds, it returns this JSON Object:

{
    "user": {
        "id": 1,
        "type": "user",
        "name": "username",
        "api_token": "accesstokenhere"
    },
    "access_token": "accesstokenhere"
}

If it doesn't succeed, it gives this response

{
    "message": "Invalid credentials"
}

Now I have the login screen for my app, upon pressing "log in", submit the information to the server and get this response back, which is not of issue and very well documented. I have the following code so far:

import SwiftUI
import Combine
import Foundation

public struct UserModel: Decodable {
    let id: Int
    let username: String
    let age: Int

    enum CodingKeys: String, CodingKey {
        case id = "id"
        case username = "name"
        case age = "age"
    }
}

public struct UserResponse: Decodable {
    public let user: UserModel
    public let accessToken: String

    enum CodingKeys: String, CodingKey {
        case user = "user"
        case accessToken = "access_token"
    }
}


public class UserFetcher: ObservableObject {
    public let objectWillChange = PassthroughSubject<UserFetcher,Never>()

    @Published var hasFinished: Bool = false {
        didSet {
            objectWillChange.send(self)
        }
    }
    var user: UserResponse?

    @Published var incorrectLogin: Bool = false {
        didSet {
            objectWillChange.send(self)
        }
    }

    init(){
        guard let url = URL(string: "https://mywebsite.com/api/login") else { return }

        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "POST"

        URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in

            do {
                if let d = data {
                    let decodedRes = try JSONDecoder().decode(UserResponse.self, from: d)
                    DispatchQueue.main.async {
                        self.user = decodedRes
                        self.hasFinished = true
                        print("Dispatching")
                    }
                } else {
                    print("No Data")
                }
            } catch {
                print("Error")
            }

        }.resume()
    }
}

I have taken this section in its entirety except for minor tweaks to fit the different object from another file I have for a similar task, albeit that it has no alternate responses and so I didn't have to handle any other types of data responses.

I'm still fairly new to swift, so I have basic understanding of do-try-catch syntax, but I don't how I would catch different response models or where to place them in my code to prevent any errors from happening.

Ideally, I would like it to toggle the incorrectLogin variable, which can be observed and trigger a popup saying incorrect login information, as all login screens do when you input incorrect credentials. If it doesn't, it should just toggle the hasFinished variable and leave incorrectLogin as false, and then I would use the user model to do all of the behind the scenes stuff.

Again, I'm still fairly new to swift, I'm sure there's probably security issues here or something else I'm overlooking, and please, let me know if that's the case.

1 Answer 1

2

A suitable solution is an enum with associated values.

Add a struct for the error case

public struct ErrorResponse: Decodable {
    let message : String
}

and the enum

enum Response : Decodable {

    case success(UserResponse)
    case failure(ErrorResponse)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            let userData = try container.decode(UserResponse.self)
            self = .success(userData)
        } catch DecodingError.typeMismatch {
            let errorData = try container.decode(ErrorResponse.self)
            self = .failure(errorData)
        }
    }
}

After decoding the data switch on the result and handle the cases

do {
    if let d = data {
        let result = try JSONDecoder().decode(Response.self, from: d)
        switch result {
            case .success(let userData):
                DispatchQueue.main.async {
                    self.user = userData
                    self.hasFinished = true
                    print("Dispatching")
                }
            case .success(let errorData):
                print(errorData.message)
            // handle the error
        }
        
    } else {
        print("No Data")
    }
} catch {
    print(error) // never print a meaningless literal string in a Decoding catch block
}
Sign up to request clarification or add additional context in comments.

5 Comments

Can u edit this one for ObjectMapper too please
Please consider to drop ObjectMapper. Codable is more efficient and is built-in.
will drop in future...but now i can't....it would be very helpful if u can provide object mapper code now
I'm not familiar with ObjectMapper, sorry.
Btw..when i implemented this method using Codable (removed alamofire from project)..it always prints success(nil...it never goes to failure)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.