Finish Riley's monster commit

3b38d725d7
May the Gods have mercy on my soul.
This commit is contained in:
nythepegasus
2024-05-06 10:14:05 -04:00
committed by ny
parent 775167415a
commit c24de874e6
32 changed files with 1422 additions and 618 deletions

View File

@@ -22,12 +22,8 @@ public extension ALTServerError
case is EncodingError: self = ALTServerError(.invalidResponse, underlyingError: error)
case let error as NSError:
var userInfo = error.userInfo
if !userInfo.keys.contains(NSUnderlyingErrorKey)
{
// Assign underlying error (if there isn't already one).
userInfo[NSUnderlyingErrorKey] = error
}
userInfo[NSUnderlyingErrorKey] = error
self = ALTServerError(.underlyingError, userInfo: userInfo)
}
}

View File

@@ -8,7 +8,17 @@
import Foundation
extension NSError
#if canImport(UIKit)
import UIKit
public typealias ALTFont = UIFont
#elseif canImport(AppKit)
import AppKit
public typealias ALTFont = NSFont
#endif
import AltSign
public extension NSError
{
@objc(alt_localizedFailure)
var localizedFailure: String? {
@@ -21,52 +31,52 @@ extension NSError
let debugDescription = (self.userInfo[NSDebugDescriptionErrorKey] as? String) ?? (NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSDebugDescriptionErrorKey) as? String)
return debugDescription
}
@objc(alt_localizedTitle)
var localizedTitle: String? {
return self.userInfo[ALTLocalizedTitleErrorKey] as? String
}
@objc(alt_errorWithLocalizedFailure:)
func withLocalizedFailure(_ failure: String) -> NSError
{
var userInfo = self.userInfo
userInfo[NSLocalizedFailureErrorKey] = failure
if let failureReason = self.localizedFailureReason
switch self
{
userInfo[NSLocalizedFailureReasonErrorKey] = failureReason
case var error as any ALTLocalizedError:
error.errorFailure = failure
return error as NSError
default:
var userInfo = self.userInfo
userInfo[NSLocalizedFailureReasonErrorKey] = failure
return ALTWrappedError(error: self, userInfo: userInfo)
}
else if self.localizedFailure == nil && self.localizedFailureReason == nil && self.localizedDescription.contains(self.localizedErrorCode)
}
@objc(alt_errorWithLocalizedTitle:)
func withLocalizedTitle(_ title: String) -> NSError {
switch self
{
// Default localizedDescription, so replace with just the localized error code portion.
userInfo[NSLocalizedFailureReasonErrorKey] = "(\(self.localizedErrorCode).)"
case var error as any ALTLocalizedError:
error.errorTitle = title
return error as NSError
default:
var userInfo = self.userInfo
userInfo[ALTLocalizedTitleErrorKey] = title
return ALTWrappedError(error: self, userInfo: userInfo)
}
else
{
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedDescription
}
if let localizedDescription = NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSLocalizedDescriptionKey) as? String
{
userInfo[NSLocalizedDescriptionKey] = localizedDescription
}
// Don't accidentally remove localizedDescription from dictionary
// userInfo[NSLocalizedDescriptionKey] = NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSLocalizedDescriptionKey) as? String
if let recoverySuggestion = self.localizedRecoverySuggestion
{
userInfo[NSLocalizedRecoverySuggestionErrorKey] = recoverySuggestion
}
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
return error
}
func sanitizedForCoreData() -> NSError
func sanitizedForSerialization() -> NSError
{
var userInfo = self.userInfo
userInfo[NSLocalizedFailureErrorKey] = self.localizedFailure
userInfo[NSLocalizedDescriptionKey] = self.localizedDescription
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
userInfo[NSDebugDescriptionErrorKey] = self.localizedDebugDescription
// Remove userInfo values that don't conform to NSSecureEncoding.
userInfo = userInfo.filter { (key, value) in
return (value as AnyObject) is NSSecureCoding
@@ -75,69 +85,165 @@ extension NSError
// Sanitize underlying errors.
if let underlyingError = userInfo[NSUnderlyingErrorKey] as? Error
{
let sanitizedError = (underlyingError as NSError).sanitizedForCoreData()
let sanitizedError = (underlyingError as NSError).sanitizedForSerialization()
userInfo[NSUnderlyingErrorKey] = sanitizedError
}
if #available(iOS 14.5, macOS 11.3, *), let underlyingErrors = userInfo[NSMultipleUnderlyingErrorsKey] as? [Error]
{
let sanitizedErrors = underlyingErrors.map { ($0 as NSError).sanitizedForCoreData() }
let sanitizedErrors = underlyingErrors.map { ($0 as NSError).sanitizedForSerialization() }
userInfo[NSMultipleUnderlyingErrorsKey] = sanitizedErrors
}
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
return error
}
func formattedDetailedDescription(with font: ALTFont) -> NSAttributedString {
#if canImport(UIKit)
let boldFontDescriptor = font.fontDescriptor.withSymbolicTraits(.traitBold) ?? font.fontDescriptor
#else
let boldFontDescriptor = font.fontDescriptor.withSymbolicTraits(.bold)
#endif
let boldFont = ALTFont(descriptor: boldFontDescriptor, size: font.pointSize) ?? font
var preferredKeyOrder = [
NSDebugDescriptionErrorKey,
NSLocalizedDescriptionKey,
NSLocalizedFailureErrorKey,
NSLocalizedFailureReasonErrorKey,
NSLocalizedRecoverySuggestionErrorKey,
ALTLocalizedTitleErrorKey,
// ALTSourceFileErrorKey,
// ALTSourceLineErrorKey,
NSUnderlyingErrorKey
]
if #available(iOS 14.5, macOS 11.3, *) {
preferredKeyOrder.append(NSMultipleUnderlyingErrorsKey)
}
var userInfo = self.userInfo
userInfo[NSDebugDescriptionErrorKey] = self.localizedDebugDescription
userInfo[NSLocalizedFailureErrorKey] = self.localizedFailure
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
let sortedUserInfo = userInfo.sorted { a, b in
let indexA = preferredKeyOrder.firstIndex(of: a.key)
let indexB = preferredKeyOrder.firstIndex(of: b.key)
switch (indexA, indexB) {
case (let indexA?, let indexB?): return indexA < indexB
case (_?, nil): return true // indexA exists, indexB is nil, indexA should come first
case (nil, _?): return false // indexB exists, indexB is nil, indexB should come first
case (nil, nil): return a.key < b.key // both nil, so sort alphabetically
}
}
let detailedDescription = NSMutableAttributedString()
for (key, value) in sortedUserInfo
{
let keyName: String
switch key
{
case NSDebugDescriptionErrorKey: keyName = NSLocalizedString("Debug Description", comment: "")
case NSLocalizedDescriptionKey: keyName = NSLocalizedString("Error Description", comment: "")
case NSLocalizedFailureErrorKey: keyName = NSLocalizedString("Failure", comment: "")
case NSLocalizedFailureReasonErrorKey: keyName = NSLocalizedString("Failure Reason", comment: "")
case NSLocalizedRecoverySuggestionErrorKey: keyName = NSLocalizedString("Recovery Suggestion", comment: "")
case ALTLocalizedTitleErrorKey: keyName = NSLocalizedString("Title", comment: "")
// case ALTSourceFileErrorKey: keyName = NSLocalizedString("Source File", comment: "")
// case ALTSourceLineErrorKey: keyName = NSLocalizedString("Source Line", comment: "")
case NSUnderlyingErrorKey: keyName = NSLocalizedString("Underlying Error", comment: "")
default:
if #available(iOS 14.5, macOS 11.3, *), key == NSMultipleUnderlyingErrorsKey
{
keyName = NSLocalizedString("Underlying Errors", comment: "")
}
else
{
keyName = key
}
}
let attributedKey = NSAttributedString(string: keyName, attributes: [.font: boldFont])
let attributedValue = NSAttributedString(string: String(describing: value), attributes: [.font: font])
let attributedString = NSMutableAttributedString(attributedString: attributedKey)
attributedString.mutableString.append("\n")
attributedString.append(attributedValue)
if !detailedDescription.string.isEmpty
{
detailedDescription.mutableString.append("\n\n")
}
detailedDescription.append(attributedString)
}
// Support dark mode
#if canImport(UIKit)
if #available(iOS 13, *)
{
detailedDescription.addAttribute(.foregroundColor, value: UIColor.label, range: NSMakeRange(0, detailedDescription.length))
}
#else
detailedDescription.addAttribute(.foregroundColor, value: NSColor.labelColor, range: NSMakeRange(0, detailedDescription.length))
#endif
return detailedDescription
}
}
extension Error
public extension NSError
{
typealias UserInfoProvider = (Error, String) -> Any?
@objc
class func alt_setUserInfoValueProvider(forDomain domain: String, provider: UserInfoProvider?) {
NSError.setUserInfoValueProvider(forDomain: domain) { error, key in
let nsError = error as NSError
switch key{
case NSLocalizedDescriptionKey:
if nsError.localizedFailure != nil {
// Error has localizedFailure, so return nil to construct localizedDescription from it + localizedFailureReason
return nil
} else if let localizedDescription = provider?(error, NSLocalizedDescriptionKey) as? String {
// Only call provider() if there is no localizedFailure
return localizedDescription
}
/* Otherwise return failureReason for localizedDescription to avoid system prepending "Operation Failed" message
Do NOT return provider(NSLocalizedFailureReason) which might be unexpectedly nil if unrecognized error code. */
return nsError.localizedFailureReason
default:
return provider?(error, key)
}
}
}
}
public extension Error
{
var underlyingError: Error? {
let underlyingError = (self as NSError).userInfo[NSUnderlyingErrorKey] as? Error
return underlyingError
return (self as NSError).userInfo[NSUnderlyingErrorKey] as? Error
}
var localizedErrorCode: String {
let localizedErrorCode = String(format: NSLocalizedString("%@ error %@", comment: ""), (self as NSError).domain, (self as NSError).code as NSNumber)
return localizedErrorCode
return String(format: NSLocalizedString("%@ %@", comment: ""), (self as NSError).domain, self.displayCode as NSNumber)
}
}
protocol ALTLocalizedError: LocalizedError, CustomNSError
{
var failure: String? { get }
var underlyingError: Error? { get }
}
var displayCode: Int {
guard let serverError = self as? ALTServerError else {
return (self as NSError).code
}
extension ALTLocalizedError
{
var errorUserInfo: [String : Any] {
let userInfo = ([
NSLocalizedDescriptionKey: self.errorDescription,
NSLocalizedFailureReasonErrorKey: self.failureReason,
NSLocalizedFailureErrorKey: self.failure,
NSUnderlyingErrorKey: self.underlyingError
] as [String: Any?]).compactMapValues { $0 }
return userInfo
/* We want AltServerError codes to start at 2000, but we can't change them without breaking AltServer compatibility.
Instead we just add 2000 when displaying code to the user to make it appear as if the codes start at 2000 anyway.
*/
return 2000 + serverError.code.rawValue
}
var underlyingError: Error? {
// Error's default implementation calls errorUserInfo,
// but ALTLocalizedError.errorUserInfo calls underlyingError.
// Return nil to prevent infinite recursion.
return nil
}
var errorDescription: String? {
guard let errorFailure = self.failure else { return (self.underlyingError as NSError?)?.localizedDescription }
guard let failureReason = self.failureReason else { return errorFailure }
let errorDescription = errorFailure + " " + failureReason
return errorDescription
}
var failureReason: String? { (self.underlyingError as NSError?)?.localizedDescription }
var recoverySuggestion: String? { (self.underlyingError as NSError?)?.localizedRecoverySuggestion }
var helpAnchor: String? { (self.underlyingError as NSError?)?.helpAnchor }
}