1

I'm trying to build a URLSession in SwiftUI to handle API token based authentication for login to a web app.

I'm able to get the API working fine in Postman, but unable to get any sort of readable response in SwiftUI. I'm new to SwiftUI and debugging. From my web server's logs, which handles the authorization, it appears as though the login request from my Swift code is successful, but I can't seem to parse out the body or headers.

I keep getting "LoginResponse(message: nil)" in my canvas.

Here's my SwiftUI code reduced to just trying to print the URLSession response.

import Foundation

enum AuthenticationError: Error {
    case invalidCredentials
    case custom(errorMessage: String)
}

struct LoginRequestBody: Codable {
    let email: String
    let password: String
}

struct LoginResponse: Codable {
    let message: String?
}

class Sessionservice {
    
    func login(emailAddress: String, password: String, completion: @escaping (Result<String, AuthenticationError>) -> Void){
        
        guard let url = URL(string: "https://www.example.com/api/v1/auth/sign_in") else {
            completion(.failure(.custom(errorMessage:"Site is unavailable.  Try again later.")))
            return
        }
        
        let body = LoginRequestBody(email: "email@example.com", password: "password")
        
        
        var request = URLRequest(url: url)
                request.httpMethod = "POST"
                request.addValue("application/json", forHTTPHeaderField: "Content-Type")
                request.httpBody = try? JSONEncoder().encode(body)

                
                URLSession.shared.dataTask(with: request) { (data, response, error) in
                    
                    guard let data = data, error == nil else {
                        completion(.failure(.custom(errorMessage: "No data")))
                        return
                    }
                    
                    try! JSONDecoder().decode(LoginResponse.self, from: data)

                    guard let loginResponse = try? JSONDecoder().decode(LoginResponse.self, from: data) else {
                        completion(.failure(.custom(errorMessage: "loginResponse Failure")))
                        return
                    }
                    print(loginResponse)
                }.resume()
    }
}
John Gerard
  • 299
  • 4
  • 19
  • 3
    Don't `try?`, never `try?` in a Decodable environment. You throw away the important information if a DecodingError occurs. `catch` the `error` and `print` it. – vadian May 08 '21 at 04:59
  • @vadian so how do I catch the error and print it? – John Gerard May 08 '21 at 05:04
  • 1
    @JohnGerard ` do { try JSONEncoder().encode(body) } catch let error { print(error) } ` – Sergio Bost May 08 '21 at 05:14
  • @SergioBost thanks. I tried that for both encode and decode and no errors in canvas. Still nil. – John Gerard May 08 '21 at 05:21
  • 3
    The code is not related to SwiftUi at all. Debug your code, set breakpoints and watch variables or insert `print` lines. – vadian May 08 '21 at 05:48
  • `print(String(data: data, encoding: .utf8))` in the `dataTask(with:completion:)` closure. – Larme May 08 '21 at 09:15
  • @Larme I added guard let response = (String(data: data, encoding: .utf8)) else { completion(.failure(.custom(errorMessage: "Response Failure"))) return } print(response) and got "{"data":{"message":"Logged in successfully.","user":{"role":"consumer"}}} LoginResponse(message: nil)" in the canvas. Any idea why it's not being parsed? – John Gerard May 09 '21 at 01:56
  • If you'd use a proper `do{try}catch` you'll get an error saying why. You aren't parsing the `data` key on the JSON. You expect `{"message":"Logged in successfully"}` or `{"message":"Logged in successfully.","user":{"role":"consumer"}}` (since you can ignore `user`), but not `{"data":yourExpectation}`. See https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html https://stackoverflow.com/questions/30720497/swift-do-try-catch-syntax etc. – Larme May 09 '21 at 09:26

1 Answers1

1
import Foundation
import SwiftUI


enum AuthenticationError: Error {
case invalidCredentials
case custom(errorMessage: String)
}

struct LoginRequestBody: Codable {
let email: String
let password: String
}

struct LoginResponse: Decodable {
let data: LoginValue?
let success: Bool?
let client: String?
}

struct LoginToken: Decodable {
let token: String?
}

struct LoginValue: Decodable {
let message: String?
let user: UserRole?
let success: Bool?
}

struct UserRole: Decodable {
let role: String?
}

class Sessionservice {


func login(email: String, password: String, completion: @escaping (Result<String, AuthenticationError>) -> Void){

    guard let url = URL(string: "https://www.example.com/api/v1/auth/sign_in") else {
        completion(.failure(.custom(errorMessage:"Site is unavailable.  Try again later.")))
        return
    }
    
    let body = LoginRequestBody(email: email, password: password)
    
    
    var request = URLRequest(url: url)
            request.httpMethod = "POST"
            request.addValue("application/json", forHTTPHeaderField: "Content-Type")
            request.httpBody = try? JSONEncoder().encode(body)

            
            URLSession.shared.dataTask(with: request) { (data, response, error) in
                
                if let response = response as? HTTPURLResponse {

                    guard let token = response.value(forHTTPHeaderField: "Accesstoken") else {
                        completion(.failure(.custom(errorMessage: "Missing Access Token")))
                        return
                    }
                    completion(.success(token))
                }
                
                guard let data = data, error == nil else { return }
                guard let loginResponse = try? JSONDecoder().decode(LoginResponse.self, from: data) else { return }
                guard let messageData = loginResponse.data else {return}
                guard let message = messageData.message else {return}
                guard let userRole = messageData.user else {return}
                guard let role = userRole.role else {return}
                completion(.success(message))
                completion(.success(role))
               
            }.resume()
    
            
}
}
John Gerard
  • 299
  • 4
  • 19