I have a file for handling http post requests to a NestJS api. My website works great with it, it accepts any response from the api. But in xcode, i have to hardcode the response. The website can accept a bool response, but xcode only accepts json as i know.
Can someone correct me, and tell me, if you can make xcode accept any response from the api. Thanks!
HttpPOST.swift
import Foundation
func apiCall(url: String, body: Dictionary<String, AnyHashable>, completion: @escaping (Response) -> Void){
guard let url = URL(string: "http://192.168.1.71:3000/user/login")else{
return
}
var request = URLRequest(url: url)
// method, body, headers
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: AnyHashable] = body
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
// make rqs
let task = URLSession.shared.dataTask(with: request){ data, response, error in
guard let data = data,error == nil else {
return
}
do {
let response = try JSONDecoder().decode(Response.self, from: data)
completion(response)
}
catch {
print(error)
}
}
task.resume()
}
struct Response: Codable {
var usernameError: String?;
var passwordError: String?;
var logOk: Bool?;
var token: String?;
}
Response example
Response(usernameError: Optional("enteruname"), passwordError: Optional("enterpasswd"), logOk: Optional(false), token: nil)
>Solution :
Make the return type generic to be able to return any Decodable type even a single Bool value.
This is a modern version of your code, it throws any possible error
func apiCall<T: Decodable>(urlString: String = "http://192.168.1.71:3000/user/login", body: [String:AnyHashable]) async throws -> T {
guard let url = URL(string: urlString) else { throw URLError(.badURL) }
var request = URLRequest(url: url)
// method, body, headers
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: body)
// make rqs
let (data, _) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(T.self, from: data)
}
And use it
Task {
do {
let result : Response = try await apiCall(body: ["Foo":"Bar"])
} catch {
print(error)
}
}
Edit:
To distinguish the success and failure cases declare Response as enum with associated values. It returns the token on success and the error messages on failure
enum Response: Decodable {
case success(String), failure((String,String))
private enum CodingKeys: String, CodingKey { case usernameError, passwordError, logOk, token}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let ok = try container.decode(Bool.self, forKey: .logOk)
if ok {
self = .success(try container.decode(String.self, forKey: .token))
} else {
let usernameError = try container.decodeIfPresent(String.self, forKey: .usernameError) ?? ""
let passwordError = try container.decodeIfPresent(String.self, forKey: .passwordError) ?? ""
self = .failure((usernameError, passwordError))
}
}
}
and change the calling code to
Task {
do {
let result : Response = try await apiCall(body: ["Foo":"Bar"])
switch result {
case .success(let token): print(token)
case .failure(let usernameError, let passwordError): print(usernameError, passwordError)
}
} catch {
print(error)
}
}