Login and Home
This commit is contained in:
104
pedi-foods/Sources/PediFoods/Services/ApiClient.swift
Normal file
104
pedi-foods/Sources/PediFoods/Services/ApiClient.swift
Normal file
@@ -0,0 +1,104 @@
|
||||
import Foundation
|
||||
#if canImport(FoundationNetworking)
|
||||
import FoundationNetworking
|
||||
#endif
|
||||
|
||||
enum NetworkError: Error, LocalizedError {
|
||||
case invalidURL
|
||||
case invalidResponse
|
||||
case httpError(Int, String?)
|
||||
case decodeError
|
||||
case rateLimited
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidURL: return "URL invalida"
|
||||
case .invalidResponse: return "Resposta invalida do servidor"
|
||||
case .httpError(let code, let message):
|
||||
return message ?? "Erro HTTP (\(code))"
|
||||
case .decodeError: return "Erro ao interpretar dados"
|
||||
case .rateLimited: return "Muitas requisicoes. Tente novamente."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ApiRequest {
|
||||
let path: String
|
||||
let method: String
|
||||
let module: ApiModule
|
||||
let requiresAuth: Bool
|
||||
let queryItems: [URLQueryItem]
|
||||
let body: Data?
|
||||
|
||||
init(path: String,
|
||||
method: String = "GET",
|
||||
module: ApiModule = .none,
|
||||
requiresAuth: Bool = true,
|
||||
queryItems: [URLQueryItem] = [],
|
||||
body: Data? = nil) {
|
||||
self.path = path
|
||||
self.method = method
|
||||
self.module = module
|
||||
self.requiresAuth = requiresAuth
|
||||
self.queryItems = queryItems
|
||||
self.body = body
|
||||
}
|
||||
}
|
||||
|
||||
final class ApiClient {
|
||||
private let session: URLSession
|
||||
private let tokenStore: TokenStore
|
||||
|
||||
init(session: URLSession = .shared, tokenStore: TokenStore = DefaultTokenStore()) {
|
||||
self.session = session
|
||||
self.tokenStore = tokenStore
|
||||
}
|
||||
|
||||
func send<T: Decodable>(_ request: ApiRequest) async throws -> T {
|
||||
let url = try buildURL(path: request.path, query: request.queryItems)
|
||||
var urlRequest = URLRequest(url: url)
|
||||
urlRequest.httpMethod = request.method
|
||||
urlRequest.httpBody = request.body
|
||||
urlRequest.setValue("application/json", forHTTPHeaderField: "Accept")
|
||||
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
if let token = ApiConfig.token(for: request.module) {
|
||||
urlRequest.setValue(token, forHTTPHeaderField: "Atomenta-Token")
|
||||
}
|
||||
if request.requiresAuth, let jwt = tokenStore.jwt {
|
||||
urlRequest.setValue("Bearer \(jwt)", forHTTPHeaderField: "Authorization")
|
||||
}
|
||||
|
||||
let (data, response) = try await session.data(for: urlRequest)
|
||||
guard let http = response as? HTTPURLResponse else {
|
||||
throw NetworkError.invalidResponse
|
||||
}
|
||||
|
||||
if http.statusCode == 429 {
|
||||
throw NetworkError.rateLimited
|
||||
}
|
||||
|
||||
if !(200...299).contains(http.statusCode) {
|
||||
let message = String(data: data, encoding: .utf8)
|
||||
throw NetworkError.httpError(http.statusCode, message)
|
||||
}
|
||||
|
||||
do {
|
||||
return try JSONDecoder().decode(T.self, from: data)
|
||||
} catch {
|
||||
throw NetworkError.decodeError
|
||||
}
|
||||
}
|
||||
|
||||
private func buildURL(path: String, query: [URLQueryItem]) throws -> URL {
|
||||
guard var components = URLComponents(url: ApiConfig.baseURL, resolvingAgainstBaseURL: false) else {
|
||||
throw NetworkError.invalidURL
|
||||
}
|
||||
components.path = components.path.appending(path)
|
||||
if !query.isEmpty {
|
||||
components.queryItems = query
|
||||
}
|
||||
guard let url = components.url else { throw NetworkError.invalidURL }
|
||||
return url
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user