APIClient: URL.appending(path:) frisst Query-Strings — String-Concat statt
URL.appending(path:) behandelt den Input als reinen Path-Component und percent-encoded Sonderzeichen, also auch ?. Damit wurde aus /api/dogs/123/diary?limit=50 ein /api/dogs/123/diary%3Flimit=50, und der Server lieferte was Anderes als JSON zurück → 'data was not valid JSON'. Betraf auch Wetter, Giftköder, Verlorene Hunde, Gassi-Zeiten und alle anderen Endpoints mit Query. Jetzt: baseURL.absoluteString + path.
This commit is contained in:
parent
3373305b23
commit
12f8ba0be8
1 changed files with 10 additions and 4 deletions
|
|
@ -42,7 +42,7 @@ final class APIClient {
|
||||||
mimeType: String = "image/jpeg"
|
mimeType: String = "image/jpeg"
|
||||||
) async throws -> Data {
|
) async throws -> Data {
|
||||||
let boundary = "Boundary-\(UUID().uuidString)"
|
let boundary = "Boundary-\(UUID().uuidString)"
|
||||||
let url = baseURL.appending(path: path)
|
let url = Self.makeURL(baseURL: baseURL, path: path)
|
||||||
var req = URLRequest(url: url)
|
var req = URLRequest(url: url)
|
||||||
req.httpMethod = "POST"
|
req.httpMethod = "POST"
|
||||||
req.setValue("application/json", forHTTPHeaderField: "Accept")
|
req.setValue("application/json", forHTTPHeaderField: "Accept")
|
||||||
|
|
@ -69,7 +69,7 @@ final class APIClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func perform<T: Decodable>(method: String, path: String, body: Data?) async throws -> T {
|
private func perform<T: Decodable>(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)
|
var req = URLRequest(url: url)
|
||||||
req.httpMethod = method
|
req.httpMethod = method
|
||||||
req.setValue("application/json", forHTTPHeaderField: "Accept")
|
req.setValue("application/json", forHTTPHeaderField: "Accept")
|
||||||
|
|
@ -98,7 +98,7 @@ final class APIClient {
|
||||||
|
|
||||||
/// Convenience for DELETE with no response body.
|
/// Convenience for DELETE with no response body.
|
||||||
func delete(_ path: String) async throws {
|
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)
|
var req = URLRequest(url: url)
|
||||||
req.httpMethod = "DELETE"
|
req.httpMethod = "DELETE"
|
||||||
if let token { req.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") }
|
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.
|
/// Convenience for PATCH with JSON body, decoding the response.
|
||||||
func patch<T: Decodable, B: Encodable>(_ path: String, body: B) async throws -> T {
|
func patch<T: Decodable, B: Encodable>(_ path: String, body: B) async throws -> T {
|
||||||
let data = try encoder.encode(body)
|
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)
|
var req = URLRequest(url: url)
|
||||||
req.httpMethod = "PATCH"
|
req.httpMethod = "PATCH"
|
||||||
req.setValue("application/json", forHTTPHeaderField: "Accept")
|
req.setValue("application/json", forHTTPHeaderField: "Accept")
|
||||||
|
|
@ -135,6 +135,12 @@ final class APIClient {
|
||||||
return try decoder.decode(T.self, from: respData)
|
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": [{...}]}.
|
/// FastAPI returns errors as {"detail": "message"} or {"detail": [{...}]}.
|
||||||
private static func parseErrorDetail(from data: Data) -> String? {
|
private static func parseErrorDetail(from data: Data) -> String? {
|
||||||
if let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
if let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue