diff --git a/AIStudyApp/AIStudyApp/Core/Network/APIClient.swift b/AIStudyApp/AIStudyApp/Core/Network/APIClient.swift index cf58870..9f02b74 100644 --- a/AIStudyApp/AIStudyApp/Core/Network/APIClient.swift +++ b/AIStudyApp/AIStudyApp/Core/Network/APIClient.swift @@ -63,12 +63,7 @@ actor APIClient { switch httpResponse.statusCode { case 200, 201: - do { - let envelope = try JSONDecoder().decode(APIEnvelope.self, from: data) - return envelope.data - } catch { - throw APIError.decodingFailed(error.localizedDescription) - } + return try decodeResponse(data) case 401 where !isRetry: if let newToken = await refreshAccessToken() { self.token = newToken @@ -109,6 +104,25 @@ actor APIClient { NotificationCenter.default.post(name: .tokenExpired, object: nil) } } + + // MARK: - Decoding + + private func decodeResponse(_ data: Data) throws -> T { + let decoder = JSONDecoder() + // Try unwrapped response first (no envelope), then wrapped + if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + json["data"] == nil && json["success"] == nil { + return try decoder.decode(T.self, from: data) + } + // Has envelope wrapper + do { + let envelope = try decoder.decode(APIEnvelope.self, from: data) + return envelope.data + } catch { + // Fallback: try decoding T directly (e.g. server returns unwrapped on some endpoints) + return try decoder.decode(T.self, from: data) + } + } } // MARK: - Helper for encoding arbitrary Encodable