From 3d712af6c516c6d05f45653063d6c17cfaa0d63c Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Thu, 11 May 2023 14:29:45 -0500 Subject: [PATCH] [Shared] Adds @UserInfoValue property wrapper for ALTLocalizedErrors ALTLocalizedErrors now automatically include all properties annotated with @UserInfoValue in userInfo when bridged to NSError. --- AltStore.xcodeproj/project.pbxproj | 6 +++++ Shared/Errors/ALTLocalizedError.swift | 19 +++++++++++++- Shared/Errors/UserInfoValue.swift | 38 +++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 Shared/Errors/UserInfoValue.swift diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 3395b5a9..11e682ae 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -345,6 +345,8 @@ BFF7C90F257844C900E55F36 /* AltXPC.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = BFF7C904257844C900E55F36 /* AltXPC.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; BFF7C920257844FA00E55F36 /* ALTPluginService.m in Sources */ = {isa = PBXBuildFile; fileRef = BF5C5FCE237DF69100EDD0C6 /* ALTPluginService.m */; }; BFF7C9342578492100E55F36 /* ALTAnisetteData.m in Sources */ = {isa = PBXBuildFile; fileRef = BFB49AA823834CF900D542D9 /* ALTAnisetteData.m */; }; + D5189C012A01BC6800F44625 /* UserInfoValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5189C002A01BC6800F44625 /* UserInfoValue.swift */; }; + D5189C022A01BC6800F44625 /* UserInfoValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5189C002A01BC6800F44625 /* UserInfoValue.swift */; }; D519AD46292D665B004B12F9 /* Managed.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB39B5B252BC10E00D1BE50 /* Managed.swift */; }; D51AD27E29356B7B00967AAA /* ALTWrappedError.h in Headers */ = {isa = PBXBuildFile; fileRef = D51AD27C29356B7B00967AAA /* ALTWrappedError.h */; settings = {ATTRIBUTES = (Public, ); }; }; D51AD27F29356B7B00967AAA /* ALTWrappedError.m in Sources */ = {isa = PBXBuildFile; fileRef = D51AD27D29356B7B00967AAA /* ALTWrappedError.m */; }; @@ -862,6 +864,7 @@ BFF7EC4C25081E9300BDE521 /* AltStore 8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 8.xcdatamodel"; sourceTree = ""; }; BFFCFA45248835530077BFCE /* AltDaemon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AltDaemon.entitlements; sourceTree = ""; }; C9EEAA842DA87A88A870053B /* Pods_AltStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltStore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D5189C002A01BC6800F44625 /* UserInfoValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoValue.swift; sourceTree = ""; }; D51AD27C29356B7B00967AAA /* ALTWrappedError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTWrappedError.h; sourceTree = ""; }; D51AD27D29356B7B00967AAA /* ALTWrappedError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTWrappedError.m; sourceTree = ""; }; D52C08ED28AEC37A006C4AE5 /* AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersion.swift; sourceTree = ""; }; @@ -1917,6 +1920,7 @@ isa = PBXGroup; children = ( D5DB145828F9DC1000A8F606 /* ALTLocalizedError.swift */, + D5189C002A01BC6800F44625 /* UserInfoValue.swift */, D51AD27C29356B7B00967AAA /* ALTWrappedError.h */, D51AD27D29356B7B00967AAA /* ALTWrappedError.m */, ); @@ -2533,6 +2537,7 @@ BFECAC8224FD950B0077C41F /* ServerProtocol.swift in Sources */, BFECAC8124FD950B0077C41F /* ALTServerError+Conveniences.swift in Sources */, BFECAC7F24FD950B0077C41F /* CodableError.swift in Sources */, + D5189C012A01BC6800F44625 /* UserInfoValue.swift in Sources */, D51AD28029356B8000967AAA /* ALTWrappedError.m in Sources */, BFECAC8624FD950B0077C41F /* Result+Conveniences.swift in Sources */, BF265D1925F843A000080DC9 /* NSError+AltStore.swift in Sources */, @@ -2645,6 +2650,7 @@ BF66EED32501AECA007EE018 /* AltStore2ToAltStore3.xcmappingmodel in Sources */, BF66EEA52501AEC5007EE018 /* Benefit.swift in Sources */, BF66EED22501AECA007EE018 /* AltStore4ToAltStore5.xcmappingmodel in Sources */, + D5189C022A01BC6800F44625 /* UserInfoValue.swift in Sources */, BFAECC5B2501B0A400528F27 /* Bundle+AltStore.swift in Sources */, BF66EECD2501AECA007EE018 /* StoreAppPolicy.swift in Sources */, BF66EEE82501AED0007EE018 /* UserDefaults+AltStore.swift in Sources */, diff --git a/Shared/Errors/ALTLocalizedError.swift b/Shared/Errors/ALTLocalizedError.swift index 08c00cf8..57bfe60b 100644 --- a/Shared/Errors/ALTLocalizedError.swift +++ b/Shared/Errors/ALTLocalizedError.swift @@ -73,13 +73,15 @@ public extension ALTLocalizedError } var errorUserInfo: [String : Any] { - let userInfo: [String: Any?] = [ + var userInfo: [String: Any?] = [ NSLocalizedFailureErrorKey: self.errorFailure, ALTLocalizedTitleErrorKey: self.errorTitle, ALTSourceFileErrorKey: self.sourceFile, ALTSourceLineErrorKey: self.sourceLine, ] + userInfo.merge(self.userInfoValues) { (_, new) in new } + return userInfo.compactMapValues { $0 } } @@ -132,6 +134,21 @@ public extension ALTLocalizedError } } +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 + } +} + public struct DefaultLocalizedError: ALTLocalizedError { public let code: Code diff --git a/Shared/Errors/UserInfoValue.swift b/Shared/Errors/UserInfoValue.swift new file mode 100644 index 00000000..0e3758d4 --- /dev/null +++ b/Shared/Errors/UserInfoValue.swift @@ -0,0 +1,38 @@ +// +// UserInfoValue.swift +// AltStore +// +// Created by Riley Testut on 5/2/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation + +protocol UserInfoValueProtocol +{ + associatedtype Value + + var key: String? { get } + var wrappedValue: Value { get } +} + +@propertyWrapper +public struct UserInfoValue: UserInfoValueProtocol +{ + public let key: String? + public var wrappedValue: Value + + // Necessary for memberwise initializers to work as expected + // https://github.com/apple/swift-evolution/blob/main/proposals/0258-property-wrappers.md#memberwise-initializers + public init(wrappedValue: Value) + { + self.wrappedValue = wrappedValue + self.key = nil + } + + public init(wrappedValue: Value, key: String) + { + self.wrappedValue = wrappedValue + self.key = key + } +}