Reputation:
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
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