Almost done

This commit is contained in:
Daniel Arantes Loverde
2026-06-08 10:46:52 -03:00
parent aec1193944
commit 23d9752bfd
25 changed files with 476 additions and 257 deletions

View File

@@ -82,7 +82,6 @@ final class ApiClient {
}
func send<T: Decodable>(_ request: ApiRequest) async throws -> T {
#if canImport(LCEssentials) && os(iOS)
return try await sendWithLCEssentials(request)
#else
@@ -119,7 +118,7 @@ private extension ApiClient {
do {
return try JSONDecoder().decode(T.self, from: data)
} catch {
throw NetworkError.decodeError(String(data: data, encoding: .utf8))
throw NetworkError.decodeError(sanitizedBody(data))
}
} catch {
let mapped = mapError(error)
@@ -156,6 +155,7 @@ private extension ApiClient {
case "POST": return .post
case "PUT": return .put
case "DELETE": return .delete
case "PATCH": return .patch
default: return .get
}
}
@@ -274,7 +274,7 @@ private extension ApiClient {
do {
return try JSONDecoder().decode(type, from: data)
} catch {
throw NetworkError.decodeError(String(data: data, encoding: .utf8))
throw NetworkError.decodeError(sanitizedBody(data))
}
}
@@ -328,7 +328,7 @@ private extension ApiClient {
return msg
}
}
return String(data: data, encoding: .utf8)
return sanitizedBody(data)
}
func serverPayload(from data: Data) -> ApiErrorDescriptor? {
@@ -364,48 +364,41 @@ private extension ApiClient {
#if canImport(LCEssentials) && os(iOS)
func serverMessage(from error: NSError) -> String? {
// Tenta buscar no userInfo por chaves conhecidas
for key in [NSLocalizedFailureReasonErrorKey, "body", "responseBody", "data", "message"] {
if let value = error.userInfo[key] as? String,
!value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
if let data = value.data(using: .utf8),
let parsed = serverMessage(from: data),
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
if parsed.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
continue
}
return parsed
}
if !value.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
return value
let t = parsed.trimmingCharacters(in: .whitespacesAndNewlines)
if t.hasPrefix("{") { continue }
if let safe = sanitizedMessage(t) { return safe }
}
let t = value.trimmingCharacters(in: .whitespacesAndNewlines)
if !t.hasPrefix("{"), let safe = sanitizedMessage(t) { return safe }
} else if let valueData = error.userInfo[key] as? Data {
if let parsed = serverMessage(from: valueData),
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
return parsed
if let parsed = serverMessage(from: valueData) {
let t = parsed.trimmingCharacters(in: .whitespacesAndNewlines)
if !t.hasPrefix("{"), let safe = sanitizedMessage(t) { return safe }
}
}
}
// Tenta varrer qualquer chave do userInfo por strings que possam ser JSON ou mensagens
for (_, value) in error.userInfo {
if let str = value as? String, !str.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
if str.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
if let data = str.data(using: .utf8),
let parsed = serverMessage(from: data),
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
return parsed
}
}
if let str = value as? String,
str.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{"),
let data = str.data(using: .utf8),
let parsed = serverMessage(from: data) {
let t = parsed.trimmingCharacters(in: .whitespacesAndNewlines)
if !t.hasPrefix("{"), let safe = sanitizedMessage(t) { return safe }
}
}
if let description = error.userInfo[NSLocalizedDescriptionKey] as? String,
!description.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
!description.lowercased().contains("nsurlerrordomain") {
return description
!description.lowercased().contains("nsurlerrordomain"),
let safe = sanitizedMessage(description) {
return safe
}
return nil
@@ -435,6 +428,25 @@ private extension ApiClient {
}
#endif
func sanitizedBody(_ data: Data) -> String? {
guard let raw = String(data: data, encoding: .utf8) else { return nil }
return sanitizedMessage(raw)
}
func sanitizedMessage(_ raw: String) -> String? {
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
guard trimmed.isEmpty == false else { return nil }
// Drop data URLs (base64 images)
if trimmed.lowercased().hasPrefix("data:image") { return "Erro ao processar imagem." }
// Drop fields containing base64,
if trimmed.contains("base64,") { return "Resposta do servidor inválida." }
// Truncate long strings (raw JSON bodies, etc.)
if trimmed.count > 300 {
return String(trimmed.prefix(300)) + ""
}
return trimmed
}
func buildURL(path: String, query: [URLQueryItem]) throws -> URL {
guard var components = URLComponents(url: ApiConfig.baseURL, resolvingAgainstBaseURL: false) else {
throw NetworkError.invalidURL