// // ApiRequest.swift // Nextcloud Cookbook iOS Client // // Created by Vincent Meilinger on 16.11.23. // import Foundation import OSLog struct ApiRequest { let path: String let method: RequestMethod let authString: String? let headerFields: [HeaderField] let body: Data? init( path: String, method: RequestMethod, authString: String? = nil, headerFields: [HeaderField] = [], body: Data? = nil ) { self.method = method self.path = path self.headerFields = headerFields self.authString = authString self.body = body } func send(pathCompletion: Bool = true) async -> (Data?, NetworkError?) { Logger.network.debug("\(method.rawValue) \(path) sending ...") // Prepare URL let urlString = pathCompletion ? UserSettings.shared.serverProtocol + UserSettings.shared.serverAddress + path : path guard var components = URLComponents(string: urlString) else { return (nil, .missingUrl) } // Ensure path percent encoding is applied correctly components.percentEncodedPath = components.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? components.path guard let url = components.url else { return (nil, .missingUrl) } // Create URL request var request = URLRequest(url: url) request.httpMethod = method.rawValue // Set authentication string, if needed if let authString = authString { request.setValue( "Basic \(authString)", forHTTPHeaderField: "Authorization" ) } // Set other header fields for headerField in headerFields { request.setValue( headerField.getValue(), forHTTPHeaderField: headerField.getField() ) } // Set http body if let body = body { request.httpBody = body } // Wait for and return data and (decoded) response do { let (data, response) = try await URLSession.shared.data(for: request) if let error = decodeURLResponse(response: response as? HTTPURLResponse, data: data) { Logger.network.debug("\(method.rawValue) \(path) FAILURE: \(error.localizedDescription)") return (nil, error) } Logger.network.debug("\(method.rawValue) \(path) SUCCESS!") return (data, nil) } catch { Logger.network.debug("\(method.rawValue) \(path) FAILURE: \(error.localizedDescription)") return (nil, .connectionError(underlying: error)) } } private func decodeURLResponse(response: HTTPURLResponse?, data: Data?) -> NetworkError? { guard let response = response else { return .unknownError(detail: "No HTTP response") } let statusCode = response.statusCode switch statusCode { case 200...299: return nil default: let body = data.flatMap { String(data: $0, encoding: .utf8) } return .httpError(statusCode: statusCode, body: body) } } }