Nenhuma descrição

UserFacingErrorMessage.swift 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import Foundation
  2. import StoreKit
  3. /// Maps backend, StoreKit, and file errors to short, safe copy for the UI (always via `L()`).
  4. enum UserFacingErrorMessage {
  5. enum PurchaseContext {
  6. case purchase
  7. case restore
  8. }
  9. static func jobSearchFailure(_ error: Error) -> String {
  10. let ns = error as NSError
  11. if ns.domain == "OpenAIJobSearchService", ns.code == 1 {
  12. return L("Job search is unavailable.")
  13. }
  14. if let urlError = error as? URLError, urlError.code == .cancelled {
  15. return L("The search was cancelled. Try again when you're ready.")
  16. }
  17. if let message = connectivityFailure(for: error) {
  18. return message
  19. }
  20. return L("Something went wrong while searching. Please try again in a moment.")
  21. }
  22. static func purchaseFailure(_ error: Error, context: PurchaseContext = .purchase) -> String {
  23. if let subscription = error as? SubscriptionStoreError {
  24. return subscription.userFacingMessage
  25. }
  26. if let storeKit = error as? StoreKitError {
  27. return message(for: storeKit, context: context)
  28. }
  29. if let purchase = error as? Product.PurchaseError {
  30. return message(for: purchase)
  31. }
  32. if let message = connectivityFailure(for: error) {
  33. return message
  34. }
  35. switch context {
  36. case .purchase:
  37. return L("We couldn't complete your purchase. Please try again.")
  38. case .restore:
  39. return L("We couldn't restore your purchases. Please try again.")
  40. }
  41. }
  42. static func pdfSaveFailure(_ error: Error) -> String {
  43. if let cocoa = error as? CocoaError {
  44. switch cocoa.code {
  45. case .fileWriteNoPermission:
  46. return L("We don't have permission to save to that folder. Choose another location or grant access.")
  47. case .fileWriteVolumeReadOnly:
  48. return L("This disk is read-only. Choose another folder to save your PDF.")
  49. case .fileWriteOutOfSpace:
  50. return L("The disk is full. Free up space, then try again.")
  51. default:
  52. break
  53. }
  54. }
  55. if let urlError = error as? URLError {
  56. switch urlError.code {
  57. case .fileDoesNotExist, .noPermissionsToReadFile:
  58. return L("We don't have permission to save to that folder. Choose another location or grant access.")
  59. default:
  60. break
  61. }
  62. }
  63. let ns = error as NSError
  64. if ns.domain == NSCocoaErrorDomain,
  65. ns.code == NSFileWriteOutOfSpaceError || ns.code == NSFileWriteVolumeReadOnlyError {
  66. if ns.code == NSFileWriteOutOfSpaceError {
  67. return L("The disk is full. Free up space, then try again.")
  68. }
  69. return L("This disk is read-only. Choose another folder to save your PDF.")
  70. }
  71. if let message = connectivityFailure(for: error) {
  72. return message
  73. }
  74. return L("We couldn't save the PDF. Try again or choose a different folder.")
  75. }
  76. private static func connectivityFailure(for error: Error) -> String? {
  77. if let storeKit = error as? StoreKitError,
  78. case .networkError(let urlError) = storeKit {
  79. return connectivityFailure(for: urlError)
  80. }
  81. if let urlError = error as? URLError {
  82. return connectivityFailure(for: urlError)
  83. }
  84. return nil
  85. }
  86. private static func connectivityFailure(for urlError: URLError) -> String? {
  87. switch urlError.code {
  88. case .notConnectedToInternet,
  89. .networkConnectionLost,
  90. .timedOut,
  91. .cannotFindHost,
  92. .cannotConnectToHost,
  93. .dnsLookupFailed:
  94. return L("We couldn't reach the server. Check your internet connection and try again.")
  95. case .cancelled:
  96. return L("The request was cancelled. Try again when you're ready.")
  97. default:
  98. return nil
  99. }
  100. }
  101. private static func message(for error: StoreKitError, context: PurchaseContext) -> String {
  102. switch error {
  103. case .userCancelled:
  104. return L("Purchase was cancelled.")
  105. case .networkError(let urlError):
  106. return connectivityFailure(for: urlError)
  107. ?? (context == .restore
  108. ? L("We couldn't restore your purchases. Please try again.")
  109. : L("We couldn't complete your purchase. Please try again."))
  110. case .notAvailableInStorefront:
  111. return L("This subscription isn't available in your App Store region.")
  112. case .notEntitled:
  113. return L("The purchase couldn't be verified. Please try again.")
  114. case .unsupported, .unknown, .systemError:
  115. return context == .restore
  116. ? L("We couldn't restore your purchases. Please try again.")
  117. : L("We couldn't complete your purchase. Please try again.")
  118. @unknown default:
  119. return context == .restore
  120. ? L("We couldn't restore your purchases. Please try again.")
  121. : L("We couldn't complete your purchase. Please try again.")
  122. }
  123. }
  124. private static func message(for error: Product.PurchaseError) -> String {
  125. switch error {
  126. case .productUnavailable:
  127. return L("That subscription isn’t available from the App Store right now.")
  128. case .purchaseNotAllowed:
  129. return L("Purchases aren't allowed on this account or device.")
  130. case .ineligibleForOffer:
  131. return L("This introductory offer isn't available for your account.")
  132. case .invalidQuantity, .invalidOfferIdentifier, .invalidOfferPrice,
  133. .invalidOfferSignature, .missingOfferParameters:
  134. return L("We couldn't complete your purchase. Please try again.")
  135. @unknown default:
  136. return L("We couldn't complete your purchase. Please try again.")
  137. }
  138. }
  139. }
  140. private extension SubscriptionStoreError {
  141. var userFacingMessage: String {
  142. switch self {
  143. case .productUnavailable:
  144. return L("That subscription isn’t available from the App Store right now.")
  145. }
  146. }
  147. }