Improve error message handling with user-friendly messages
- Add ErrorMessageParser in Kotlin and Swift to detect network errors and technical messages, replacing them with human-readable text - Update all ViewModels to use ErrorMessageParser.parse() for error display - Remove redundant error popup from LoginView (error shows inline only) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,22 +1,82 @@
|
||||
import Foundation
|
||||
|
||||
/// Utility for parsing and cleaning error messages from API responses
|
||||
/// Utility for parsing and cleaning error messages from API responses and network errors
|
||||
enum ErrorMessageParser {
|
||||
|
||||
// Network/connection error patterns to detect
|
||||
private static let networkErrorPatterns: [(pattern: String, message: String)] = [
|
||||
("Could not connect to the server", "Unable to connect to the server. Please check your internet connection."),
|
||||
("NSURLErrorDomain", "Unable to connect to the server. Please check your internet connection."),
|
||||
("The Internet connection appears to be offline", "No internet connection. Please check your network settings."),
|
||||
("A server with the specified hostname could not be found", "Unable to connect to the server. Please check your internet connection."),
|
||||
("The request timed out", "Request timed out. Please try again."),
|
||||
("The network connection was lost", "Connection was interrupted. Please try again."),
|
||||
("An SSL error has occurred", "Secure connection failed. Please try again."),
|
||||
("CFNetwork", "Unable to connect to the server. Please check your internet connection."),
|
||||
("kCFStreamError", "Unable to connect to the server. Please check your internet connection."),
|
||||
("Code=-1004", "Unable to connect to the server. Please check your internet connection."),
|
||||
("Code=-1009", "No internet connection. Please check your network settings."),
|
||||
("Code=-1001", "Request timed out. Please try again."),
|
||||
("Code=-1003", "Unable to connect to the server. Please check your internet connection."),
|
||||
("Code=-1005", "Connection was interrupted. Please try again."),
|
||||
("Code=-1200", "Secure connection failed. Please try again."),
|
||||
("UnresolvedAddressException", "Unable to connect to the server. Please check your internet connection."),
|
||||
("ConnectException", "Unable to connect to the server. Please check your internet connection."),
|
||||
("SocketTimeoutException", "Request timed out. Please try again."),
|
||||
("Connection refused", "Unable to connect to the server. The server may be down."),
|
||||
("Connection reset", "Connection was interrupted. Please try again.")
|
||||
]
|
||||
|
||||
// Indicators that a message is technical/developer-facing
|
||||
private static let technicalIndicators = [
|
||||
"Exception",
|
||||
"Error Domain=",
|
||||
"UserInfo=",
|
||||
"at com.",
|
||||
"at org.",
|
||||
"at java.",
|
||||
"at kotlin.",
|
||||
"at io.",
|
||||
"Caused by:",
|
||||
"Stack trace:",
|
||||
".kt:",
|
||||
".java:",
|
||||
".swift:",
|
||||
"0x",
|
||||
"Code=",
|
||||
"interface:",
|
||||
"LocalDataTask",
|
||||
"NSUnderlyingError",
|
||||
"_kCF"
|
||||
]
|
||||
|
||||
/// Parses error messages to extract user-friendly text
|
||||
/// If the error message is JSON, extract relevant error details
|
||||
/// - Parameter rawMessage: The raw error message from the API
|
||||
/// Handles network errors, JSON error responses, and raw error messages
|
||||
/// - Parameter rawMessage: The raw error message from the API or exception
|
||||
/// - Returns: A user-friendly error message
|
||||
static func parse(_ rawMessage: String) -> String {
|
||||
// Check if the message looks like JSON (starts with { or [)
|
||||
let trimmed = rawMessage.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard trimmed.hasPrefix("{") || trimmed.hasPrefix("[") else {
|
||||
// Not JSON, return as-is
|
||||
return rawMessage
|
||||
|
||||
// Check for network/connection errors first (these are technical messages from exceptions)
|
||||
for (pattern, friendlyMessage) in networkErrorPatterns {
|
||||
if trimmed.localizedCaseInsensitiveContains(pattern) {
|
||||
return friendlyMessage
|
||||
}
|
||||
}
|
||||
|
||||
// If it's JSON, it's not meant for user display
|
||||
// Try to parse and extract meaningful error info
|
||||
// Check if it looks like a technical exception message
|
||||
if isTechnicalError(trimmed) {
|
||||
return "Something went wrong. Please try again."
|
||||
}
|
||||
|
||||
// Check if the message looks like JSON (starts with { or [)
|
||||
guard trimmed.hasPrefix("{") || trimmed.hasPrefix("[") else {
|
||||
// Not JSON - if it's a short, readable message, return it
|
||||
// Otherwise return a generic message
|
||||
return isUserFriendly(trimmed) ? trimmed : "Something went wrong. Please try again."
|
||||
}
|
||||
|
||||
// If it's JSON, try to parse and extract meaningful error info
|
||||
guard let data = trimmed.data(using: .utf8) else {
|
||||
return "An error occurred. Please try again."
|
||||
}
|
||||
@@ -47,4 +107,15 @@ enum ErrorMessageParser {
|
||||
// If we couldn't parse or extract a message, return a generic error
|
||||
return "An error occurred. Please try again."
|
||||
}
|
||||
|
||||
/// Checks if the message looks like a technical/developer error (stack trace, exception, etc)
|
||||
private static func isTechnicalError(_ message: String) -> Bool {
|
||||
return technicalIndicators.contains { message.localizedCaseInsensitiveContains($0) }
|
||||
}
|
||||
|
||||
/// Checks if a message is user-friendly (short, no technical jargon)
|
||||
private static func isUserFriendly(_ message: String) -> Bool {
|
||||
// If it's short and doesn't contain technical indicators, it's probably user-friendly
|
||||
return message.count < 200 && !isTechnicalError(message)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user