-
Notifications
You must be signed in to change notification settings - Fork 335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add root error info to public error #4680
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
// Created by Nacho Soto on 8/31/22. | ||
|
||
import Foundation | ||
import StoreKit | ||
|
||
/// An error returned by a `RevenueCat` public API. | ||
public typealias PublicError = NSError | ||
|
@@ -38,8 +39,56 @@ extension PurchasesError { | |
/// let error = ErrorUtils.unknownError().asPublicError | ||
/// let errorCode = error as? ErrorCode | ||
/// ``` | ||
/// | ||
/// Info about the root error can be accessed in userInfo. | ||
/// Example: | ||
/// ``` | ||
/// let error = ErrorUtils.unknownError().asPublicError | ||
/// let rootErrorInfo = error.userInfo["rc_root_error"] as? [String: Any] | ||
/// let rootErrorCode = rootErrorInfo?["code"] as? Int | ||
/// let rootErrorDomain = rootErrorInfo?["domain"] as? String | ||
/// let rootErrorLocalizedDescription = rootErrorInfo?["localizedDescription"] as? String | ||
/// ``` | ||
/// | ||
/// If the root error comes from StoreKit, some extra info will be added to the root error. | ||
/// Example: | ||
/// ``` | ||
/// let error = ErrorUtils.unknownError().asPublicError | ||
/// let rootErrorInfo = error.userInfo["rc_root_error"] as? [String: Any] | ||
/// let storeKitErrorInfo = rootErrorInfo?["storeKitError"] as? [String: Any] | ||
/// let storeKitErrorDescription = storeKitErrorInfo?["description"] as? String | ||
/// // If it's a SKError: | ||
/// let skErrorCode = storeKitErrorInfo?["skErrorCode"] as? Int | ||
/// // If it's a StoreKitError.networkError: | ||
/// let urlErrorCode = storeKitErrorInfo?["urlErrorCode"] as? Int | ||
/// let urlErrorFailingUrl = storeKitErrorInfo?["urlErrorFailingUrl"] as? String | ||
/// // If it's a StoreKitError.systemError: | ||
/// let systemErrorDescription = storeKitErrorInfo?["systemErrorDescription"] as? Int | ||
/// ``` | ||
var asPublicError: PublicError { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know it's a draft, but it would be great to list the keys we'll include in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm so I included it in the docs for the |
||
return NSError(domain: Self.errorDomain, code: self.errorCode, userInfo: self.userInfo) | ||
let rootError: Error = self.rootError(from: self) | ||
let rootNSError = rootError as NSError | ||
var rootErrorInfo: [String: Any] = [ | ||
"code": rootNSError.code, | ||
"domain": rootNSError.domain, | ||
"localizedDescription": rootNSError.localizedDescription | ||
] | ||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *) { | ||
if let storeKitErrorInfo = self.getStoreKitErrorInfoIfAny(error: rootError) { | ||
rootErrorInfo = rootErrorInfo.merging(["storeKitError": storeKitErrorInfo]) | ||
} | ||
} | ||
let userInfoToUse = self.userInfo.merging([ErrorDetails.rootErrorKey: rootErrorInfo]) | ||
return NSError(domain: Self.errorDomain, code: self.errorCode, userInfo: userInfoToUse) | ||
} | ||
|
||
private func rootError(from error: Error) -> Error { | ||
let nsError = error as NSError | ||
if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? Error { | ||
return rootError(from: underlyingError) | ||
} else { | ||
return error | ||
} | ||
} | ||
|
||
} | ||
|
@@ -65,3 +114,43 @@ extension PurchasesError { | |
} | ||
|
||
} | ||
|
||
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *) | ||
private extension PurchasesError { | ||
|
||
func getStoreKitErrorInfoIfAny(error: Error) -> [String: Any]? { | ||
if let skError = error as? SKError { | ||
return [ | ||
"skErrorCode": skError.code.rawValue, | ||
"description": skError.code.trackingDescription | ||
] | ||
} else if let storeKitError = error as? StoreKitError { | ||
let resultMap: [String: Any] = ["description": storeKitError.trackingDescription] | ||
switch storeKitError { | ||
case .unknown, | ||
.userCancelled, | ||
.notAvailableInStorefront, | ||
.notEntitled: | ||
return resultMap | ||
case let .networkError(urlError): | ||
return resultMap.merging([ | ||
"urlErrorCode": urlError.errorCode, | ||
"urlErrorFailingUrl": urlError.failureURLString ?? "missing_value" | ||
]) | ||
case let .systemError(systemError): | ||
return resultMap.merging([ | ||
"systemErrorDescription": systemError.localizedDescription | ||
]) | ||
|
||
@unknown default: | ||
Logger.warn(Strings.storeKit.unknown_storekit_error(storeKitError)) | ||
return resultMap | ||
} | ||
} else if let storeKitError = error as? StoreKit.Product.PurchaseError { | ||
return ["description": storeKitError.trackingDescription] | ||
} else { | ||
return nil | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it would be useful to log what other error type we might be missing? 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, in this case I'm assuming it's because the root error is not a store kit error (which may happen in several situations). I could add a log here, but I think the situation may happen relatively often... Note that this method is only to add extra data to the |
||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My main concern with this approach is whether we only expose PublicError everywhere and/or if we use this method somewhere else... Need to research it a bit more.