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(_ 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 } }