UserFacingError.swift 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. import Foundation
  2. enum UserFacingError {
  3. static let generic = "Something went wrong. Please try again."
  4. static func message(for error: Error) -> String {
  5. if shouldUseGenericMessage(for: error) {
  6. return generic
  7. }
  8. let raw = (error as? LocalizedError)?.errorDescription ?? error.localizedDescription
  9. return sanitized(raw) ?? generic
  10. }
  11. static func networkMessage(for error: URLError) -> String {
  12. switch error.code {
  13. case .notConnectedToInternet:
  14. "You appear to be offline. Check your internet connection and try again."
  15. case .timedOut:
  16. "The request timed out. Please try again."
  17. case .networkConnectionLost:
  18. "The connection was interrupted. Please try again."
  19. default:
  20. generic
  21. }
  22. }
  23. static func sanitized(_ message: String) -> String? {
  24. let trimmed = message.trimmingCharacters(in: .whitespacesAndNewlines)
  25. guard !trimmed.isEmpty else { return nil }
  26. guard !containsSensitiveContent(trimmed.lowercased()) else { return nil }
  27. return trimmed
  28. }
  29. private static func shouldUseGenericMessage(for error: Error) -> Bool {
  30. if error is DecodingError || error is EncodingError {
  31. return true
  32. }
  33. switch error {
  34. case PostGenerationError.invalidResponse,
  35. CommentGenerationError.invalidResponse,
  36. TitleOptimizationError.invalidResponse:
  37. return true
  38. default:
  39. return false
  40. }
  41. }
  42. private static func containsSensitiveContent(_ lower: String) -> Bool {
  43. lower.contains("api key")
  44. || lower.contains("apikey")
  45. || lower.contains("openai")
  46. || lower.contains("invalid_api_key")
  47. || lower.contains("incorrect api key")
  48. || lower.contains("bearer ")
  49. || lower.contains("sk-")
  50. || lower.contains("authentication")
  51. || lower.contains("not configured")
  52. || lower.contains("response format")
  53. || lower.contains("request failed")
  54. || lower.contains("ai request failed")
  55. || lower.contains("couldn't reach the ai service")
  56. || lower.contains("nsurlerrordomain")
  57. || lower.contains("cocoaerrordomain")
  58. || lower.contains("error domain")
  59. || (lower.contains("could not read") && lower.contains("response"))
  60. || (lower.contains("couldn't parse") && lower.contains("ai"))
  61. || (lower.contains("status") && lower.range(of: #"\b[1-5]\d{2}\b"#, options: .regularExpression) != nil)
  62. }
  63. }