user14542817
user14542817

Reputation:

Trouble implementing Swift Result<Success, Error> in API request

I am intentionally creating a user that already exists in MongoDB to display an error message to the user, such as "Please choose a different username" or "email already in use" but I am having trouble decoding the server's response.

My model is designed to handle the success(user/token) or error response(message)...

I am able to successfully decode the user object when an account is created on the backend...

What I am doing wrong? I feel like I should be using an enum for the error model somehow??

// POSTMAN RESPONSE WHEN USERNAME ALREADY TAKEN
{
    "success": false,
    "message": "Please choose a different username"
}

// XCODE ERROR MESSAGE 

//...Expected to decode Dictionary<String, Any> but found a string/data 
//instead.", underlyingError: nil))

// DATA MODELS

struct UserResponse: Decodable {
    let success: Bool
    let message: ErrorResponse?
    var user: User?
    var token: String?
}

struct ErrorResponse: Decodable, Error {
    let message: String
}
class LoginService {
    
     static func createAccount(username: String, email: String, password: String, 
completion: @escaping(Result <UserResponse, Error>) -> Void) {
     
        let user = UserSignup(username: username, email: email, password: password)
        
        // Create URL code
       
        do {
            let encoder = JSONEncoder()
            urlRequest.httpBody = try encoder.encode(user)
           
            let dataTask = URLSession.shared.dataTask(with: urlRequest) { data, response, error in
               
                if let error = error {
                    completion(.failure(error))
                    return
                }
                guard let jsonData = data else { return }
    
                do {
                    let responseObject = try JSONDecoder().decode(UserResponse.self, from: jsonData)
                    switch responseObject.success {
                    case true:
                        completion(.success(responseObject))
                    case false:
                        // not working
                        guard let errorMessage = responseObject.message else {
                          return
                        }
                        completion(.failure(errorMessage))
                    }
                } catch {
                    print(error)
                }
            }
            dataTask.resume()
        } catch {
            // handle error
        }
    }

Upvotes: 1

Views: 594

Answers (1)

vadian
vadian

Reputation: 285082

The error says message is a string so declare it as String

struct UserResponse: Decodable {
    let success: Bool
    let message: String?
    var user: User?
    var token: String?
}

I feel like I should be using an enum for the error model somehow

That's a good idea to get rid of the optionals. The enum first decodes status and depending on the value it decodes the user and token on success and the error message on failure

struct UserResponse {
    let user: User
    let token: String
}

enum Response : Decodable {
    case success(UserResponse)
    case failure(String)
    
    private enum CodingKeys : String, CodingKey { case success, message, user, token }
    
    init(from decoder : Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let success = try container.decode(Bool.self, forKey: .success)
        if success {
            let user = try container.decode(User.self, forKey: .user)
            let token = try container.decode(String.self, forKey: .token)
            self = .success(UserResponse(user: user, token: token))
        } else {
            let message = try container.decode(String.self, forKey: .message)
            self = .failure(message)
        }
    }
}

Then you can replace

   let responseObject = try JSONDecoder().decode(UserResponse.self, from: jsonData)
    switch responseObject.success {
    case true:
        completion(.success(responseObject))
    case false:
        // not working
        guard let errorMessage = responseObject.message else {
          return
        }
        completion(.failure(errorMessage))
    }

with

   let response = try JSONDecoder().decode(Response.self, from: jsonData)
    switch response {
    case .success(let responseObject):
        completion(.success(responseObject))
    case .failure(let errorMessage)
        completion(.failure(errorMessage))
    }

Upvotes: 2

Related Questions