diff --git a/BanYaroGo/API/APIClient.swift b/BanYaroGo/API/APIClient.swift index 69942ec..48041b4 100644 --- a/BanYaroGo/API/APIClient.swift +++ b/BanYaroGo/API/APIClient.swift @@ -42,7 +42,7 @@ final class APIClient { mimeType: String = "image/jpeg" ) async throws -> Data { let boundary = "Boundary-\(UUID().uuidString)" - let url = baseURL.appending(path: path) + let url = Self.makeURL(baseURL: baseURL, path: path) var req = URLRequest(url: url) req.httpMethod = "POST" req.setValue("application/json", forHTTPHeaderField: "Accept") @@ -69,7 +69,7 @@ final class APIClient { } private func perform(method: String, path: String, body: Data?) async throws -> T { - let url = baseURL.appending(path: path) + let url = Self.makeURL(baseURL: baseURL, path: path) var req = URLRequest(url: url) req.httpMethod = method req.setValue("application/json", forHTTPHeaderField: "Accept") @@ -98,7 +98,7 @@ final class APIClient { /// Convenience for DELETE with no response body. func delete(_ path: String) async throws { - let url = baseURL.appending(path: path) + let url = Self.makeURL(baseURL: baseURL, path: path) var req = URLRequest(url: url) req.httpMethod = "DELETE" if let token { req.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") } @@ -116,7 +116,7 @@ final class APIClient { /// Convenience for PATCH with JSON body, decoding the response. func patch(_ path: String, body: B) async throws -> T { let data = try encoder.encode(body) - let url = baseURL.appending(path: path) + let url = Self.makeURL(baseURL: baseURL, path: path) var req = URLRequest(url: url) req.httpMethod = "PATCH" req.setValue("application/json", forHTTPHeaderField: "Accept") @@ -135,6 +135,12 @@ final class APIClient { return try decoder.decode(T.self, from: respData) } + /// Constructs a URL by joining baseURL and path as strings — `URL.appending(path:)` + /// percent-encodes `?` and breaks query strings. + private static func makeURL(baseURL: URL, path: String) -> URL { + URL(string: baseURL.absoluteString + path) ?? baseURL + } + /// FastAPI returns errors as {"detail": "message"} or {"detail": [{...}]}. private static func parseErrorDetail(from data: Data) -> String? { if let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {