2024-08-06 10:43:52 +09:00
|
|
|
//
|
|
|
|
|
// ALTLocalizedError.swift
|
|
|
|
|
// AltStore
|
|
|
|
|
//
|
|
|
|
|
// Created by Riley Testut on 10/14/22.
|
|
|
|
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import Foundation
|
2023-09-07 18:00:53 -05:00
|
|
|
|
|
|
|
|
#if !ALTJIT
|
2024-08-06 10:43:52 +09:00
|
|
|
import AltSign
|
2023-09-07 18:00:53 -05:00
|
|
|
#endif
|
2024-08-06 10:43:52 +09:00
|
|
|
|
|
|
|
|
public let ALTLocalizedTitleErrorKey = "ALTLocalizedTitle"
|
|
|
|
|
public let ALTLocalizedDescriptionKey = "ALTLocalizedDescription"
|
|
|
|
|
|
|
|
|
|
public protocol ALTLocalizedError<Code>: LocalizedError, CustomNSError, CustomStringConvertible
|
|
|
|
|
{
|
|
|
|
|
associatedtype Code: ALTErrorCode
|
|
|
|
|
|
|
|
|
|
var code: Code { get }
|
|
|
|
|
var errorFailureReason: String { get }
|
|
|
|
|
|
|
|
|
|
var errorTitle: String? { get set }
|
|
|
|
|
var errorFailure: String? { get set }
|
|
|
|
|
|
|
|
|
|
var sourceFile: String? { get set }
|
|
|
|
|
var sourceLine: UInt? { get set }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public extension ALTLocalizedError
|
|
|
|
|
{
|
|
|
|
|
var sourceFile: String? {
|
|
|
|
|
get { nil }
|
|
|
|
|
set {}
|
|
|
|
|
}
|
|
|
|
|
var sourceLine: UInt? {
|
|
|
|
|
get { nil }
|
|
|
|
|
set {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public protocol ALTErrorCode: RawRepresentable where RawValue == Int
|
|
|
|
|
{
|
|
|
|
|
associatedtype Error: ALTLocalizedError where Error.Code == Self
|
|
|
|
|
static var errorDomain: String { get } // Optional
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public protocol ALTErrorEnum: ALTErrorCode
|
|
|
|
|
{
|
|
|
|
|
associatedtype Error = DefaultLocalizedError<Self>
|
|
|
|
|
var errorFailureReason: String { get }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// LocalizedError & CustomNSError & CustomStringConvertible
|
|
|
|
|
public extension ALTLocalizedError
|
|
|
|
|
{
|
|
|
|
|
var errorCode: Int { self.code.rawValue }
|
|
|
|
|
var errorDescription: String? {
|
2024-12-25 19:18:26 +05:30
|
|
|
// Use errorFailure directly without relying on bridging to NSError
|
|
|
|
|
return self.errorFailure ?? self.failureReason
|
2024-08-06 10:43:52 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var failureReason: String? {
|
|
|
|
|
return self.errorFailureReason
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var errorUserInfo: [String : Any] {
|
2024-12-25 19:18:26 +05:30
|
|
|
let userInfo: [String: Any?] = [
|
2024-08-06 10:43:52 +09:00
|
|
|
NSLocalizedFailureErrorKey: self.errorFailure,
|
|
|
|
|
ALTLocalizedTitleErrorKey: self.errorTitle,
|
2024-12-07 17:45:09 +05:30
|
|
|
ALTSourceFileErrorKey: self.sourceFile,
|
2024-12-25 19:18:26 +05:30
|
|
|
ALTSourceLineErrorKey: self.sourceLine,
|
2024-08-06 10:43:52 +09:00
|
|
|
]
|
2024-12-25 19:18:26 +05:30
|
|
|
|
2024-08-06 10:43:52 +09:00
|
|
|
return userInfo.compactMapValues { $0 }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var description: String {
|
2024-12-14 01:48:14 +05:30
|
|
|
let description = "\(self.errorCode) “\(self.localizedDescription)”"
|
2024-08-06 10:43:52 +09:00
|
|
|
return description
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Default Implementations
|
|
|
|
|
public extension ALTLocalizedError where Code: ALTErrorEnum
|
|
|
|
|
{
|
|
|
|
|
static var errorDomain: String {
|
|
|
|
|
return Code.errorDomain
|
|
|
|
|
}
|
|
|
|
|
// ALTErrorEnum Codes provide their failure reason directly.
|
|
|
|
|
var errorFailureReason: String {
|
|
|
|
|
return self.code.errorFailureReason
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Default Implementations
|
|
|
|
|
public extension ALTErrorCode
|
|
|
|
|
{
|
|
|
|
|
static var errorDomain: String {
|
|
|
|
|
let typeName = String(reflecting: Self.self) // "\(Self.self)" doesn't include module name, but String(reflecting:) does.
|
2024-12-25 19:18:26 +05:30
|
|
|
let errorDomain = typeName.replacingOccurrences(of: "ErrorCode", with: "Error")
|
2024-08-06 10:43:52 +09:00
|
|
|
return errorDomain
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public extension ALTLocalizedError
|
|
|
|
|
{
|
|
|
|
|
// Allows us to initialize errors with localizedTitle + localizedFailure
|
|
|
|
|
// while still using the error's custom initializer at callsite.
|
|
|
|
|
init(_ error: Self, localizedTitle: String? = nil, localizedFailure: String? = nil)
|
|
|
|
|
{
|
|
|
|
|
self = error
|
|
|
|
|
if let localizedTitle
|
|
|
|
|
{
|
|
|
|
|
self.errorTitle = localizedTitle
|
|
|
|
|
}
|
|
|
|
|
if let localizedFailure
|
|
|
|
|
{
|
|
|
|
|
self.errorFailure = localizedFailure
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 14:29:45 -05:00
|
|
|
private extension ALTLocalizedError
|
|
|
|
|
{
|
|
|
|
|
var userInfoValues: [(String, Any)] {
|
|
|
|
|
let userInfoValues = Mirror(reflecting: self).children.compactMap { (label, value) -> (String, Any)? in
|
|
|
|
|
guard let userInfoValue = value as? any UserInfoValueProtocol,
|
|
|
|
|
let key: any StringProtocol = userInfoValue.key ?? label?.dropFirst() // Remove leading underscore
|
|
|
|
|
else { return nil }
|
|
|
|
|
|
|
|
|
|
return (String(key), userInfoValue.wrappedValue)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return userInfoValues
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-06 10:43:52 +09:00
|
|
|
public struct DefaultLocalizedError<Code: ALTErrorEnum>: ALTLocalizedError
|
|
|
|
|
{
|
|
|
|
|
public let code: Code
|
|
|
|
|
|
|
|
|
|
public var errorTitle: String?
|
|
|
|
|
public var errorFailure: String?
|
|
|
|
|
public var sourceFile: String?
|
|
|
|
|
public var sourceLine: UInt?
|
|
|
|
|
|
|
|
|
|
public init(_ code: Code, localizedTitle: String? = nil, localizedFailure: String? = nil, sourceFile: String? = #fileID, sourceLine: UInt? = #line)
|
|
|
|
|
{
|
|
|
|
|
self.code = code
|
|
|
|
|
self.errorTitle = localizedTitle
|
|
|
|
|
self.errorFailure = localizedFailure
|
|
|
|
|
self.sourceFile = sourceFile
|
|
|
|
|
self.sourceLine = sourceLine
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Custom Operators
|
|
|
|
|
/// These allow us to pattern match ALTErrorCodes against arbitrary errors via ~ prefix.
|
|
|
|
|
prefix operator ~
|
|
|
|
|
public prefix func ~<Code: ALTErrorCode>(expression: Code) -> NSError
|
|
|
|
|
{
|
|
|
|
|
let nsError = NSError(domain: Code.errorDomain, code: expression.rawValue)
|
|
|
|
|
return nsError
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public func ~=(pattern: any Swift.Error, value: any Swift.Error) -> Bool
|
|
|
|
|
{
|
|
|
|
|
let isMatch = pattern._domain == value._domain && pattern._code == value._code
|
|
|
|
|
return isMatch
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// These operators *should* allow us to match ALTErrorCodes against arbitrary errors,
|
|
|
|
|
// but they don't work as of iOS 16.1 and Swift 5.7.
|
|
|
|
|
//
|
|
|
|
|
//public func ~=<Error: ALTLocalizedError>(pattern: Error, value: Swift.Error) -> Bool
|
|
|
|
|
//{
|
|
|
|
|
// let isMatch = pattern._domain == value._domain && pattern._code == value._code
|
|
|
|
|
// return isMatch
|
|
|
|
|
//}
|
|
|
|
|
//
|
|
|
|
|
//public func ~=<Code: ALTErrorCode>(pattern: Code, value: Swift.Error) -> Bool
|
|
|
|
|
//{
|
|
|
|
|
// let isMatch = Code.errorDomain == value._domain && pattern.rawValue == value._code
|
|
|
|
|
// return isMatch
|
|
|
|
|
//}
|