137 lines
3.6 KiB
Swift
137 lines
3.6 KiB
Swift
//
|
|
// Copyright 2021 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import ObjectiveC
|
|
|
|
extension NSError {
|
|
|
|
@objc
|
|
public var hasIsRetryable: Bool { hasIsRetryableImpl }
|
|
|
|
@objc
|
|
public var isRetryable: Bool { isRetryableImpl }
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
extension Error {
|
|
public var hasIsRetryable: Bool { (self as NSError).hasIsRetryable }
|
|
|
|
public var isRetryable: Bool { (self as NSError).isRetryableImpl }
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
extension NSError {
|
|
|
|
fileprivate var hasIsRetryableImpl: Bool {
|
|
if self is IsRetryableProvider {
|
|
return true
|
|
}
|
|
if self.isNetworkFailureOrTimeout {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
fileprivate var isRetryableImpl: Bool {
|
|
// Error and NSError have a special relationship.
|
|
// They can be "cast" back and forth, but are separate objects.
|
|
//
|
|
// If Error is cast to NSError, a new NSError will wrap the Error.
|
|
// This is called "NSError bridging".
|
|
//
|
|
// NSError implements Error protocol, but casting NSError to Error
|
|
// might unwrap a bridged wrapper.
|
|
//
|
|
// If you roundtrip-cast Error to NSError and back (or vice versa),
|
|
// you should not count on ending up with the same as instance as
|
|
// you began with, even though you sometimes will.
|
|
//
|
|
// When trying to cast an error to IsRetryableProvider,
|
|
// we need to try casting both the Error and NSError form.
|
|
if let error = self as? IsRetryableProvider {
|
|
return error.isRetryableProvider
|
|
}
|
|
if let error = (self as Error) as? IsRetryableProvider {
|
|
return error.isRetryableProvider
|
|
}
|
|
|
|
if self.isNetworkFailureOrTimeout {
|
|
// We can safely default to retrying network failures.
|
|
return true
|
|
}
|
|
|
|
// This value should always be set for all errors by this
|
|
// var is consulted. If not, default to retrying in production.
|
|
if CurrentAppContext().isRunningTests {
|
|
Logger.warn("Error without retry behavior specified: \(self)")
|
|
} else {
|
|
owsFailDebug("Error without retry behavior specified: \(self)")
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
public protocol IsRetryableProvider {
|
|
var isRetryableProvider: Bool { get }
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
extension OWSAssertionError: IsRetryableProvider {
|
|
public var isRetryableProvider: Bool { false }
|
|
}
|
|
|
|
extension OWSGenericError: IsRetryableProvider {
|
|
public var isRetryableProvider: Bool { false }
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
// NOTE: We typically prefer to use a more specific error.
|
|
@objc
|
|
public class OWSRetryableError: NSObject, CustomNSError, IsRetryableProvider {
|
|
@objc
|
|
public static var asNSError: NSError {
|
|
OWSRetryableError() as Error as NSError
|
|
}
|
|
|
|
// MARK: - IsRetryableProvider
|
|
|
|
public var isRetryableProvider: Bool { true }
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
// NOTE: We typically prefer to use a more specific error.
|
|
@objc
|
|
public class OWSUnretryableError: NSObject, CustomNSError, IsRetryableProvider {
|
|
@objc
|
|
public static var asNSError: NSError {
|
|
OWSUnretryableError() as Error as NSError
|
|
}
|
|
|
|
// MARK: - IsRetryableProvider
|
|
|
|
public var isRetryableProvider: Bool { false }
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
public enum SSKUnretryableError: Error, IsRetryableProvider {
|
|
case partialLocalProfileFetch
|
|
case stickerDecryptionFailure
|
|
case downloadCouldNotMoveFile
|
|
case downloadCouldNotDeleteFile
|
|
case messageProcessingFailed
|
|
|
|
// MARK: - IsRetryableProvider
|
|
|
|
public var isRetryableProvider: Bool { false }
|
|
}
|