Compare commits
54 Commits
bugfix/mod
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e1581e28e | ||
|
|
8f2483eb1b | ||
|
|
12d17dbca8 | ||
|
|
c360c75512 | ||
|
|
d57a86e140 | ||
|
|
214ccfd8f6 | ||
|
|
31aa1d6df7 | ||
|
|
817d7900f4 | ||
|
|
4b35a8bb2d | ||
|
|
a29fa60e5e | ||
|
|
3103f8dd89 | ||
|
|
2b36711bca | ||
|
|
a570f3c0e6 | ||
|
|
038fb8cdf7 | ||
|
|
dd2b0f9de5 | ||
|
|
d61cb6cfb9 | ||
|
|
cf3d7810d4 | ||
|
|
3c8af09752 | ||
|
|
b219f3f75d | ||
|
|
fba2e80b8a | ||
|
|
460aa06678 | ||
|
|
c18ce5e811 | ||
|
|
4bf30550ef | ||
|
|
317815d44a | ||
|
|
3054f7e62d | ||
|
|
25e05c6c53 | ||
|
|
a1c6332131 | ||
|
|
9744fc5333 | ||
|
|
8a92c9cdd3 | ||
|
|
a00eff7cf1 | ||
|
|
0104570bc9 | ||
|
|
134ce14e5a | ||
|
|
220b8530da | ||
|
|
05df4ad6ef | ||
|
|
8f88e94ed8 | ||
|
|
d2572c4543 | ||
|
|
71689b957b | ||
|
|
009bb01e1b | ||
|
|
4bded0a433 | ||
|
|
20d4d323e0 | ||
|
|
62a5799312 | ||
|
|
e8552b0aa2 | ||
|
|
dde3e565a3 | ||
|
|
62b7955406 | ||
|
|
a5763d4516 | ||
|
|
9ff641060e | ||
|
|
85747b146e | ||
|
|
9b718ce0d4 | ||
|
|
39a8451314 | ||
|
|
c38030bdb8 | ||
|
|
09ff3b6457 | ||
|
|
a428d5f7ce | ||
|
|
fe7e98f615 | ||
|
|
f7a348a8b0 |
@ -42,6 +42,7 @@
|
||||
2723337B28BD534D004EAEE0 /* MVMCoreEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723337A28BD534D004EAEE0 /* MVMCoreEvent.swift */; };
|
||||
2723337D28BD53C2004EAEE0 /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723337C28BD53C2004EAEE0 /* Date+Extension.swift */; };
|
||||
5846ABF42B44BB9000FA6C76 /* Collection+Safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846ABF32B44BB9000FA6C76 /* Collection+Safe.swift */; };
|
||||
5878F0B22BDAA63E00ADE23D /* ReadableDecodingErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878F0B12BDAA63E00ADE23D /* ReadableDecodingErrors.swift */; };
|
||||
6042E8FC2B317B190031644B /* MVMCoreLoggingHandlerHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 6042E8FB2B3094680031644B /* MVMCoreLoggingHandlerHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
605A9A2A2ABD712F00487E47 /* MVMCoreLoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 605A9A292ABD712F00487E47 /* MVMCoreLoggingHandler.swift */; };
|
||||
6079EDCE2AD97AA5004B7A85 /* MVMCoreLoggingDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6079EDCD2AD97AA5004B7A85 /* MVMCoreLoggingDelegateProtocol.swift */; };
|
||||
@ -194,6 +195,7 @@
|
||||
581FABEE2A71D0E6003A8508 /* mvmcore_dev.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcore_dev.xcconfig; sourceTree = "<group>"; };
|
||||
5836B8E22A4338DF002553D9 /* mvmcore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcore.xcconfig; sourceTree = "<group>"; };
|
||||
5846ABF32B44BB9000FA6C76 /* Collection+Safe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection+Safe.swift"; sourceTree = "<group>"; };
|
||||
5878F0B12BDAA63E00ADE23D /* ReadableDecodingErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadableDecodingErrors.swift; sourceTree = "<group>"; };
|
||||
6042E8FB2B3094680031644B /* MVMCoreLoggingHandlerHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreLoggingHandlerHelper.h; sourceTree = "<group>"; };
|
||||
605A9A292ABD712F00487E47 /* MVMCoreLoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreLoggingHandler.swift; sourceTree = "<group>"; };
|
||||
6079EDCD2AD97AA5004B7A85 /* MVMCoreLoggingDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreLoggingDelegateProtocol.swift; sourceTree = "<group>"; };
|
||||
@ -291,7 +293,6 @@
|
||||
AFBB96AE1FBA3B590008D868 /* MVMCoreGetterUtility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreGetterUtility.h; sourceTree = "<group>"; };
|
||||
AFBB96AF1FBA3B590008D868 /* MVMCoreGetterUtility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreGetterUtility.m; sourceTree = "<group>"; };
|
||||
AFBB96B51FBA3CEC0008D868 /* MVMCoreActionDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreActionDelegateProtocol.h; sourceTree = "<group>"; };
|
||||
AFBB96D21FBA44420008D868 /* VZWAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VZWAuthentication.framework; path = ../../SharedFrameworks/VZWAuthentication.framework; sourceTree = "<group>"; };
|
||||
AFBB96E91FBA4A260008D868 /* MFHardCodedServerResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFHardCodedServerResponse.h; sourceTree = "<group>"; };
|
||||
AFBB96EA1FBA4A260008D868 /* MFHardCodedServerResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFHardCodedServerResponse.m; sourceTree = "<group>"; };
|
||||
AFEA17A6209B6A1C00BC6740 /* MVMCoreBlockOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreBlockOperation.h; sourceTree = "<group>"; };
|
||||
@ -396,6 +397,7 @@
|
||||
children = (
|
||||
AF60A7F1289212CA00919EEB /* MVMError.swift */,
|
||||
AF60A7F3289212EB00919EEB /* MVMCoreError.swift */,
|
||||
5878F0B12BDAA63E00ADE23D /* ReadableDecodingErrors.swift */,
|
||||
881D26911FCC9D180079C521 /* MVMCoreErrorObject.h */,
|
||||
881D268F1FCC9D180079C521 /* MVMCoreErrorObject.m */,
|
||||
881D26921FCC9D180079C521 /* MVMCoreOperation.h */,
|
||||
@ -648,7 +650,6 @@
|
||||
AF43A6FC1FBE2F2A008E9347 /* Reachability */,
|
||||
AF43A5C01FBB76D5008E9347 /* CoreGraphics.framework */,
|
||||
AF43A5BF1FBB76C3008E9347 /* UIKit.framework */,
|
||||
AFBB96D21FBA44420008D868 /* VZWAuthentication.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@ -877,6 +878,7 @@
|
||||
AFA4931E29E5C988001A9663 /* MVMCoreActionUtility+Extension.swift in Sources */,
|
||||
1DAD0FFE26AAB40000216E83 /* ActionRunJavaScriptModel.swift in Sources */,
|
||||
946EE1AB237B5C940036751F /* Decoder.swift in Sources */,
|
||||
5878F0B22BDAA63E00ADE23D /* ReadableDecodingErrors.swift in Sources */,
|
||||
2723337D28BD53C2004EAEE0 /* Date+Extension.swift in Sources */,
|
||||
AF70699A287DD02400077CF6 /* ActionContactHandler.swift in Sources */,
|
||||
AF69D4F3286E9DCE00BC6862 /* ActionActionsHandler.swift in Sources */,
|
||||
@ -1111,7 +1113,7 @@
|
||||
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../../SharedFrameworks";
|
||||
INFOPLIST_FILE = MVMCore/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
MARKETING_VERSION = 3.1;
|
||||
@ -1138,7 +1140,7 @@
|
||||
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../../SharedFrameworks";
|
||||
INFOPLIST_FILE = MVMCore/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
MARKETING_VERSION = 3.1;
|
||||
|
||||
@ -64,4 +64,19 @@ public struct ActionActionsModel: ActionModelProtocol {
|
||||
try container.encodeIfPresent(extraParameters, forKey: .extraParameters)
|
||||
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return actions.isEqual(to: model.actions)
|
||||
&& concurrent == model.concurrent
|
||||
&& extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
extension ActionActionsModel: CustomDebugStringConvertible {
|
||||
|
||||
public var debugDescription: String {
|
||||
return "\(Self.self) [\(actions)]"
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,4 +25,12 @@ public struct ActionBackModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
// Default
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.actionType == actionType
|
||||
&& model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,4 +28,11 @@ public struct ActionCallModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
&& callNumber == model.callNumber
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,4 +25,12 @@ public struct ActionCancelModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
// Default
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.actionType == actionType
|
||||
&& model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,4 +42,14 @@ public struct ActionContactModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
&& phoneNumber == model.phoneNumber
|
||||
&& firstName == model.firstName
|
||||
&& lastName == model.lastName
|
||||
&& approach == model.approach
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
public struct ActionNoopModel: ActionModelProtocol {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -24,4 +25,12 @@ public struct ActionNoopModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
// Default
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.actionType == actionType
|
||||
&& model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol {
|
||||
let fetchedParameters = await ClientParameterHandler().getClientParameters(
|
||||
with: parametersToFetch,
|
||||
requestParameters: requestParameters.parameters as? [String : Any] ?? [:],
|
||||
actionId: MVMCoreActionHandler.getUUID(additionalData: additionalData) ?? "unknown",
|
||||
actionId: actionUUID,
|
||||
showLoadingOverlay: !requestParameters.backgroundRequest) {
|
||||
requestParameters.add(fetchedParameters)
|
||||
}
|
||||
|
||||
@ -115,4 +115,31 @@ public struct ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol,
|
||||
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
|
||||
try container.encodeIfPresent(fallbackResponse, forKey: .fallbackResponse)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return pageType == model.pageType
|
||||
&& baseURL == model.baseURL
|
||||
&& appContext == model.appContext
|
||||
&& requestURL == model.requestURL
|
||||
&& modules == model.modules
|
||||
&& presentationStyle == model.presentationStyle
|
||||
&& tabBarIndex == model.tabBarIndex
|
||||
&& background == model.background
|
||||
&& clientParameters == model.clientParameters
|
||||
&& customTimeoutTime == model.customTimeoutTime
|
||||
&& extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
&& fallbackResponse == model.fallbackResponse
|
||||
}
|
||||
}
|
||||
|
||||
extension ActionOpenPageModel: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
if let requestURL {
|
||||
return "\(Self.self) for \(pageType) @ \(requestURL)"
|
||||
} else {
|
||||
return "\(Self.self) for \(pageType)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,4 +30,13 @@ public struct ActionOpenSMSModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
// Default
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
&& model.phoneNumber == phoneNumber
|
||||
&& model.message == message
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,4 +60,13 @@ open class ActionOpenUrlModel: ActionModelProtocol {
|
||||
try container.encodeIfPresent(extraParameters, forKey: .extraParameters)
|
||||
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
&& browserUrl == model.browserUrl
|
||||
&& appURL == model.appURL
|
||||
&& appURLOptions == model.appURLOptions
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,4 +25,12 @@ public struct ActionPreviousSubmitModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
// Default
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.actionType == actionType
|
||||
&& model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,11 +25,16 @@ open class ActionRestartHandler: MVMCoreActionHandlerProtocol {
|
||||
return
|
||||
}
|
||||
continuation.resume(throwing: MVMCoreError.errorObject(error))
|
||||
} else {
|
||||
// Restarts the app (forcing any passed in page types).
|
||||
MVMCoreSessionObject.sharedGlobal()?.restartSession(withPageType: model.pageType, request: model.requestURL, parameters: model.extraParameters?.toJSON(), clearAllVariables: true)
|
||||
continuation.resume()
|
||||
return
|
||||
}
|
||||
if let sessionObject = MVMCoreSessionObject.sharedGlobal() {
|
||||
if model.hardReset {
|
||||
sessionObject.clearPersistentCache()
|
||||
}
|
||||
// Restarts the app (forcing any passed in page types).
|
||||
sessionObject.restartSession(withPageType: model.pageType, request: model.requestURL, parameters: model.extraParameters?.toJSON(), clearAllVariables: true)
|
||||
}
|
||||
continuation.resume()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ public struct ActionRestartModel: ActionModelProtocol {
|
||||
public static var identifier: String = "restart"
|
||||
public var actionType: String = ActionRestartModel.identifier
|
||||
public var requestURL: URL?
|
||||
@DecodableDefault.True public var hardReset: Bool
|
||||
public var extraParameters: JSONValueDictionary?
|
||||
public var analyticsData: JSONValueDictionary?
|
||||
|
||||
@ -26,10 +27,20 @@ public struct ActionRestartModel: ActionModelProtocol {
|
||||
// MARK: - Initializer
|
||||
//--------------------------------------------------
|
||||
|
||||
public init(_ pageType: String? = nil, _ requestUrl: URL? = nil, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
|
||||
public init(_ pageType: String? = nil, _ requestUrl: URL? = nil, hardReset: Bool? = nil, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
|
||||
self.pageType = pageType
|
||||
self.requestURL = requestUrl
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
self.hardReset = hardReset ?? true
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
&& requestURL == model.requestURL
|
||||
&& pageType == model.pageType
|
||||
&& hardReset == model.hardReset
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,4 +25,10 @@ public struct ActionSettingModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
// Copyright © 2020 myverizon. All rights reserved.
|
||||
//
|
||||
|
||||
public struct ActionShareItemModel: Codable {
|
||||
public struct ActionShareItemModel: Codable, Equatable {
|
||||
|
||||
public enum SharedType: String, Codable {
|
||||
case text
|
||||
@ -14,14 +14,14 @@ public struct ActionShareItemModel: Codable {
|
||||
}
|
||||
|
||||
public var type: SharedType
|
||||
public var value: Any
|
||||
public var value: AnyHashable // Common Equatable type between String and URL.
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case value
|
||||
}
|
||||
|
||||
public init(type: SharedType, value: Any) {
|
||||
public init(type: SharedType, value: AnyHashable) {
|
||||
self.type = type
|
||||
self.value = value
|
||||
}
|
||||
@ -47,6 +47,11 @@ public struct ActionShareItemModel: Codable {
|
||||
try container.encode(value as! URL, forKey: .value)
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: ActionShareItemModel, rhs: ActionShareItemModel) -> Bool {
|
||||
return lhs.type == rhs.type
|
||||
&& lhs.value == rhs.value
|
||||
}
|
||||
}
|
||||
|
||||
public struct ActionShareModel: ActionModelProtocol {
|
||||
@ -101,7 +106,7 @@ public struct ActionShareModel: ActionModelProtocol {
|
||||
private init(deprecatedFrom decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: DeprecatedCodingKeys.self)
|
||||
let type = try typeContainer.decode(ActionShareItemModel.SharedType.self, forKey: .sharedType)
|
||||
var value: Any
|
||||
var value: AnyHashable
|
||||
switch type {
|
||||
case .url:
|
||||
value = try typeContainer.decode(URL.self, forKey: .sharedText)
|
||||
@ -116,4 +121,11 @@ public struct ActionShareModel: ActionModelProtocol {
|
||||
try container.encode(actionType, forKey: .actionType)
|
||||
try container.encode(items, forKey: .items)
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
&& items == model.items
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,9 +85,9 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol {
|
||||
open func handleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws {
|
||||
try Task.checkCancellation()
|
||||
var (additionalData, uuid) = MVMCoreActionHandler.setUUID(additionalData: additionalData)
|
||||
MVMCoreActionHandler.log(string: "Begin Action: \(model.actionType)", additionalData: additionalData)
|
||||
MVMCoreActionHandler.log(string: "Begin Action: \(model)", additionalData: additionalData)
|
||||
defer {
|
||||
MVMCoreActionHandler.log(string: "End Action: \(model.actionType)", additionalData: additionalData)
|
||||
MVMCoreActionHandler.log(string: "End Action: \(model)", additionalData: additionalData)
|
||||
}
|
||||
let json = try additionalData.removeValue(forKey: jsonKey) as? [AnyHashable : Any] ?? MVMCoreActionHandler.convertActionToJSON(model)
|
||||
|
||||
@ -148,12 +148,18 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol {
|
||||
}
|
||||
|
||||
static public func log(string: String, additionalData: [AnyHashable: Any]?) {
|
||||
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: UUID: \(String(describing: getUUID(additionalData: additionalData))), \(string)")
|
||||
MVMCoreLoggingHandler.shared()?.handleDebugMessage("ActionHandler: UUID: \(getUUID(additionalData: additionalData) ?? "untracked"), \(string)", category: String(describing: Self.self))
|
||||
}
|
||||
|
||||
fileprivate func logActionError(_ error: Error, _ actionType: String?, _ additionalData: [AnyHashable: Any]?, _ delegateObject: DelegateObject?) {
|
||||
MVMCoreActionHandler.log(string: "Failed Action: Error \(error)", additionalData: additionalData)
|
||||
if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: actionType ?? "noAction")) {
|
||||
if let fromBridge = additionalData?["fromBridge"] as? Bool, fromBridge, let browserUrl = additionalData?["browserUrl"] as? String {
|
||||
errorObject.requestUrl = browserUrl
|
||||
}
|
||||
if let humanReadableMessage = (error as? HumanReadableDecodingErrorProtocol)?.readableDescription {
|
||||
errorObject.messageToLog = "Failed to decode the \(actionType ?? "") action model. " + humanReadableMessage
|
||||
}
|
||||
defaultHandleActionError(errorObject, additionalData: additionalData)
|
||||
}
|
||||
}
|
||||
@ -176,7 +182,7 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol {
|
||||
} catch {
|
||||
let actionType = json?.optionalStringForKey(KeyActionType)
|
||||
switch error {
|
||||
case ModelRegistry.Error.decoderError, is DecodingError:
|
||||
case is ModelRegistry.Error, is DecodingError:
|
||||
MVMCoreLoggingHandler.shared()?.logCoreEvent(.actionFailedToDecode(pageType: pageType(from: delegateObject), error: error))
|
||||
logActionError(error, actionType, additionalData, delegateObject)
|
||||
case ModelRegistry.Error.decoderErrorModelNotMapped:
|
||||
|
||||
@ -9,7 +9,8 @@
|
||||
import Foundation
|
||||
|
||||
/// A model for UIApplication.OpenExternalURLOptionsKey
|
||||
open class OpenUrlOptionsModel: Codable {
|
||||
open class OpenUrlOptionsModel: Codable, Equatable {
|
||||
|
||||
public var options: [UIApplication.OpenExternalURLOptionsKey: Any]
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -42,4 +43,16 @@ open class OpenUrlOptionsModel: Codable {
|
||||
try container.encode(universalLinksValue, forKey: .universalLinksOnly)
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: OpenUrlOptionsModel, rhs: OpenUrlOptionsModel) -> Bool {
|
||||
return lhs.options.allSatisfy { pair in
|
||||
switch(pair.key) {
|
||||
case .universalLinksOnly:
|
||||
return rhs.options[pair.key] as? Bool == pair.value as? Bool
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
|
||||
- (void)getJsonData:(nonnull MVMCoreRequestParameters *)requestParameters forUrl:(nonnull NSURL *)url completion:(nonnull void (^)(NSData * _Nullable data, MVMCoreErrorObject *_Nullable error))completion;
|
||||
|
||||
- (void)getInitialParametersExcludingSections:(NSSet<NSString *> *_Nullable)excludeSections completion:(nonnull void (^)(NSDictionary *_Nullable parameters))completion NS_SWIFT_NAME(initialParameters(excludingSections:completion:));
|
||||
- (void)getInitialParametersExcludingSections:(NSSet<NSString *> *_Nullable)excludeSections actionId:(nonnull NSString *)actionId completion:(nonnull void (^)(NSDictionary *_Nullable parameters))completion NS_SWIFT_NAME(initialParameters(excludingSections:actionId:completion:));
|
||||
|
||||
// Creates a request object with the given parameters.
|
||||
- (void)transformToRequestWithParameters:(nonnull MVMCoreRequestParameters *)requestParameters completion:(nonnull void (^)(NSURLRequest * _Nullable request, MVMCoreErrorObject *_Nullable error))completion;
|
||||
|
||||
@ -190,7 +190,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)getInitialParametersExcludingSections:(NSSet<NSString *> *_Nullable)excludeSections completion:(nonnull void (^)(NSDictionary *_Nullable parameters))closure {}
|
||||
- (void)getInitialParametersExcludingSections:(NSSet<NSString *> *)excludeSections actionId:(NSString *)actionId completion:(void (^)(NSDictionary * _Nullable))completion {}
|
||||
|
||||
- (void)getJsonDictionary:(nonnull MVMCoreRequestParameters *)requestParameters completion:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completion {
|
||||
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
|
||||
@ -206,7 +206,7 @@
|
||||
}
|
||||
|
||||
// Sets up the Initial parameters.
|
||||
[self getInitialParametersExcludingSections:requestParameters.excludedInitialParameters completion:^(NSDictionary * _Nullable initialParameters) {
|
||||
[self getInitialParametersExcludingSections:requestParameters.excludedInitialParameters actionId:requestParameters.identifier ?: [NSUUID UUID].UUIDString completion:^(NSDictionary * _Nullable initialParameters) {
|
||||
if (initialParameters) {
|
||||
[parameters setObject:initialParameters forKey:@"InitialParams"];
|
||||
}
|
||||
@ -232,7 +232,7 @@
|
||||
return;
|
||||
}
|
||||
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
MVMCoreLog(@"Request Parameters for URL %@:\n%@", [url absoluteString], jsonString);
|
||||
MVMCoreNetworkLog(@"Request Parameters for URL %@:\n%@", [url absoluteString], jsonString);
|
||||
#endif
|
||||
|
||||
// Standard condensed to send to the server.
|
||||
@ -309,15 +309,15 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
MVMCoreLog(@"********************************* Cookie Sent *********************************");
|
||||
MVMCoreNetworkLog(@"********************************* Cookie Sent *********************************");
|
||||
[[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL] enumerateObjectsUsingBlock:^(NSHTTPCookie * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
MVMCoreLog(@"Cookie Name: %@, Cookie Value: %@, Domain: %@", obj.name, obj.value, obj.domain);
|
||||
MVMCoreNetworkLog(@"Cookie Name: %@, Cookie Value: %@, Domain: %@", obj.name, obj.value, obj.domain);
|
||||
}];
|
||||
|
||||
NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
|
||||
NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||
|
||||
MVMCoreLog(@"Request Time %f", [NSDate timeIntervalSinceReferenceDate] - startTime);
|
||||
MVMCoreNetworkLog(@"Request Time %f", [NSDate timeIntervalSinceReferenceDate] - startTime);
|
||||
|
||||
NSDate *startTimeDate = [NSDate dateWithTimeIntervalSinceReferenceDate:startTime];
|
||||
|
||||
@ -330,7 +330,7 @@
|
||||
[trackInfo setObject:error.localizedDescription forKey:@"error"];
|
||||
}
|
||||
|
||||
MVMCoreLog(@"Set-Cookie %@ Value: %@", requestParameters.pageType, [(NSHTTPURLResponse *)response allHeaderFields][@"Set-Cookie"]);
|
||||
MVMCoreNetworkLog(@"Set-Cookie %@ Value: %@", requestParameters.pageType, [(NSHTTPURLResponse *)response allHeaderFields][@"Set-Cookie"]);
|
||||
|
||||
id jsonObject = nil;
|
||||
MVMCoreErrorObject *errorObject = nil;
|
||||
@ -354,7 +354,7 @@
|
||||
// Log the response pretty.
|
||||
NSData *prettyData = [NSJSONSerialization dataWithJSONObject:jsonObject options:NSJSONWritingPrettyPrinted error:&error];
|
||||
NSString *responseString = [[NSString alloc] initWithData:prettyData encoding:NSUTF8StringEncoding];
|
||||
MVMCoreLog(@"Response for Request Page Type %@:\n%@",requestParameters.pageType, responseString);
|
||||
MVMCoreNetworkLog(@"Response for Request Page Type %@:\n%@",requestParameters.pageType, responseString);
|
||||
}
|
||||
} else {
|
||||
// Empty response.
|
||||
|
||||
@ -44,8 +44,9 @@ public extension MVMCoreLoadRequestOperation {
|
||||
@MainActor
|
||||
func goToViewController(loadObject: MVMCoreLoadObject) async -> UIViewController? {
|
||||
guard loadObject.requestParameters?.replaceViewIfOnStackElseLoadWithStyle == true,
|
||||
let pageType = loadObject.pageType else {
|
||||
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): A new controller should be made and any existing shouldn't be replaced. pageType:\(String(describing: loadObject.pageType))")
|
||||
loadObject.requestParameters?.replaceViewControllerIfOnStackGoToOnly == true,
|
||||
let pageType = loadObject.pageType else {
|
||||
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): A new controller should be made. pageType:\(String(describing: loadObject.pageType))")
|
||||
return nil
|
||||
}
|
||||
let template = loadObject.pageJSON?.optionalStringForKey("template")
|
||||
|
||||
@ -86,7 +86,7 @@
|
||||
// stop any loading animation we may have started
|
||||
[self stopLoadingAnimationIfNeeded];
|
||||
|
||||
MVMCoreLog(@"Load Operation finished for page type %@, background load %@", self.requestParameters.pageType, @(self.backgroundLoad));
|
||||
MVMCoreNetworkLog(@"Load Operation finished for page type %@, background load %@", self.requestParameters.pageType, @(self.backgroundLoad));
|
||||
[super markAsFinished];
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@
|
||||
}
|
||||
|
||||
- (void)main {
|
||||
MVMCoreLog(@"Load Operation begun for page type %@, background load %@, delegate %@", self.requestParameters.pageType, @(self.backgroundLoad),self.delegateObject.loadDelegate);
|
||||
MVMCoreNetworkLog(@"Load Operation begun for page type %@, background load %@, delegate %@", self.requestParameters.pageType, @(self.backgroundLoad),self.delegateObject.loadDelegate);
|
||||
|
||||
[self.requestParameters resolveURL:[MVMCoreSessionObject sharedGlobal]];
|
||||
|
||||
@ -139,10 +139,10 @@
|
||||
|
||||
// Log if loaded from cache.
|
||||
if (pageFromCache) {
|
||||
MVMCoreLog(@"loaded from cache page %@",[MVMCoreActionUtility formatDictionaryAsJSONString:pageFromCache]);
|
||||
MVMCoreNetworkLog(@"loaded from cache page %@",[MVMCoreActionUtility formatDictionaryAsJSONString:pageFromCache]);
|
||||
}
|
||||
if (modulesFromCache) {
|
||||
MVMCoreLog(@"loaded from cache modules %@",[MVMCoreActionUtility formatDictionaryAsJSONString:modulesFromCache]);
|
||||
MVMCoreNetworkLog(@"loaded from cache modules %@",[MVMCoreActionUtility formatDictionaryAsJSONString:modulesFromCache]);
|
||||
}
|
||||
|
||||
// Create a load object from any data we fetched.
|
||||
@ -686,7 +686,16 @@
|
||||
|
||||
if (obj && [obj isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *responseInfo = [obj dict:KeyResponseInfo];
|
||||
if (![ValueTypeSuccess isEqualToString:[responseInfo string:KeyType]]) {
|
||||
//Response Info is missing but errorObject should be created with generic message + code + domain
|
||||
if (responseInfo == nil) {
|
||||
errorObject = [[MVMCoreLoadHandler sharedGlobal]
|
||||
errorForLoadObject:loadObject
|
||||
withTitle:nil
|
||||
message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical]
|
||||
messageToLog:[NSString stringWithFormat:@"Module %@ is missing a %@ object.", key, KeyResponseInfo]
|
||||
code:ErrorCodeJSONNotDictionary
|
||||
domain:ErrorDomainServer];
|
||||
} else if (![ValueTypeSuccess isEqualToString:[responseInfo string:KeyType]]) {
|
||||
errorObject = [[MVMCoreLoadHandler sharedGlobal] attachLoadInformation:loadObject toError:
|
||||
[[MVMCoreErrorObject alloc]
|
||||
initWithTitle:[responseInfo stringForKey:KeyErrorHeading]
|
||||
@ -696,7 +705,6 @@
|
||||
domain:ErrorDomainServer
|
||||
location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]];
|
||||
}
|
||||
|
||||
// Caches each dictionary from the array.
|
||||
[[MVMCoreCache sharedCache] addModuleToCache:obj module:key queue:nil waitUntilFinished:YES completionBlock:NULL];
|
||||
} else {
|
||||
@ -822,7 +830,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
MVMCoreLog(@"Error: %@ %@ %@ %@ %@",[error stringErrorCode], error.domain, error.location,error.messageToDisplay, error.messageToLog);
|
||||
MVMCoreNetworkLog(@"Error: %@ %@ %@ %@ %@",[error stringErrorCode], error.domain, error.location,error.messageToDisplay, error.messageToLog);
|
||||
|
||||
if (showAlertForErrorIfApplicable && (!loadObject.operation.backgroundLoad || loadObject.requestParameters.allowAlertsIfBackgroundRequest) && !loadObject.requestParameters.handleErrorsSilently && !error.silentError && !error.errorScreenError) {
|
||||
|
||||
|
||||
@ -79,8 +79,13 @@ typedef NS_ENUM(NSInteger, MFLoadStyle) {
|
||||
|
||||
// Determines how it is loaded.
|
||||
@property (nonatomic) MFLoadStyle loadStyle;
|
||||
|
||||
/// Determines if we should search the stack for a controller with the same pageType or just load a new controller with style. Default true
|
||||
@property (nonatomic) BOOL replaceViewIfOnStackElseLoadWithStyle;
|
||||
|
||||
/// Determines if, when replaceViewIfOnStackElseLoadWithStyle is true, we should create a new controller or only pop back to the existing controller. Default true
|
||||
@property (nonatomic) BOOL replaceViewControllerIfOnStackGoToOnly;
|
||||
|
||||
// A flag for if a tab was pressed to cause this load. This will ensure that we do not load a new tab page.
|
||||
@property (nonatomic) BOOL tabWasPressed;
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
// Default load style.
|
||||
self.loadStyle = MFLoadStyleDefault;
|
||||
self.replaceViewIfOnStackElseLoadWithStyle = YES;
|
||||
self.replaceViewControllerIfOnStackGoToOnly = YES;
|
||||
self.identifier = [[NSUUID UUID] UUIDString];
|
||||
}
|
||||
return self;
|
||||
@ -189,6 +190,7 @@
|
||||
copyObject.contextRoot = [self.contextRoot copy];
|
||||
copyObject.loadStyle = self.loadStyle;
|
||||
copyObject.replaceViewIfOnStackElseLoadWithStyle = self.replaceViewIfOnStackElseLoadWithStyle;
|
||||
copyObject.replaceViewControllerIfOnStackGoToOnly = self.replaceViewControllerIfOnStackGoToOnly;
|
||||
copyObject.noViewControllerToLoad = self.noViewControllerToLoad;
|
||||
copyObject.dontDisplayViewController = self.dontDisplayViewController;
|
||||
copyObject.noloadingOverlay = self.noloadingOverlay;
|
||||
|
||||
@ -8,13 +8,16 @@
|
||||
|
||||
public protocol MVMCoreLoggingDelegateProtocol {
|
||||
|
||||
// Can be used to log different actions performed by the core.
|
||||
/// Can be used to log different actions performed by the core.
|
||||
func handleDebugMessage(_ message: String?)
|
||||
|
||||
/// Can be used to log a message under a particular cagetory.
|
||||
func handleDebugMessage(_ message: String, category: String?)
|
||||
|
||||
// Can be used to choose how to log error objects.
|
||||
/// Can be used to choose how to log error objects.
|
||||
func addError(toLog errorObject: MVMCoreErrorObject)
|
||||
|
||||
// Log that the load has finished.
|
||||
/// Log that the load has finished.
|
||||
func logLoadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: MVMCoreViewControllerProtocol?, error: MVMCoreErrorObject?)
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
|
||||
public protocol ActionModelProtocol: ModelProtocol {
|
||||
public protocol ActionModelProtocol: ModelProtocol, CustomDebugStringConvertible {
|
||||
|
||||
var actionType: String { get }
|
||||
var extraParameters: JSONValueDictionary? { get set }
|
||||
@ -33,4 +33,15 @@ public extension ActionModelProtocol {
|
||||
static var categoryName: String {
|
||||
return "\(ActionModelProtocol.self)"
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
return actionType
|
||||
}
|
||||
|
||||
func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return model.actionType == actionType
|
||||
&& model.extraParameters == extraParameters
|
||||
&& model.analyticsData == analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,4 +29,11 @@ public class ActionRunJavaScriptModel: ActionModelProtocol {
|
||||
self.extraParameters = extraParameters
|
||||
self.analyticsData = analyticsData
|
||||
}
|
||||
|
||||
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return jsCallback == model.jsCallback
|
||||
&& extraParameters == model.extraParameters
|
||||
&& analyticsData == model.analyticsData
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,8 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public class ClientParameterModel: Codable {
|
||||
public class ClientParameterModel: Equatable, Codable {
|
||||
|
||||
var timeout: Double?
|
||||
var list: [ClientParameterModelProtocol]
|
||||
|
||||
@ -33,4 +34,9 @@ public class ClientParameterModel: Codable {
|
||||
try container.encodeIfPresent(timeout, forKey: .timeout)
|
||||
try container.encodeModels(list, forKey: .list)
|
||||
}
|
||||
|
||||
public static func == (lhs: ClientParameterModel, rhs: ClientParameterModel) -> Bool {
|
||||
return lhs.list.isEqual(to: rhs.list)
|
||||
&& lhs.timeout == rhs.timeout
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,4 +30,9 @@ public extension ClientParameterModelProtocol {
|
||||
static var categoryName: String {
|
||||
return "\(ClientParameterModelProtocol.self)"
|
||||
}
|
||||
|
||||
func isEqual(to model: any ModelProtocol) -> Bool {
|
||||
guard let model = model as? Self else { return false }
|
||||
return type == model.type
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol ModelProtocol: Codable {
|
||||
public protocol ModelProtocol: ModelComparisonProtocol, Codable {
|
||||
|
||||
/// The key name of the molecule
|
||||
static var identifier: String { get }
|
||||
@ -50,3 +50,102 @@ extension ModelProtocol {
|
||||
try unkeyedContainer.encode(self)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ModelComparisonProtocol {
|
||||
/// Shallow checks if the current model is equal to another model. Defaults to false unless implemented otherwise.
|
||||
func isEqual(to model: ModelComparisonProtocol) -> Bool
|
||||
}
|
||||
|
||||
extension ModelComparisonProtocol {
|
||||
public func isEqual(to model: ModelComparisonProtocol) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public extension Optional {
|
||||
/// Checks if the current model is equal to another model.
|
||||
func isEqual(to model: ModelComparisonProtocol?) -> Bool {
|
||||
guard let self = self as? ModelComparisonProtocol else {
|
||||
return model == nil
|
||||
}
|
||||
guard let model = model else {
|
||||
return false
|
||||
}
|
||||
return self.isEqual(to: model)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Collection {
|
||||
/// Checks if all the models in the given collection match another given collection.
|
||||
func isEqual(to models: [ModelComparisonProtocol]) -> Bool {
|
||||
guard count == models.count, let self = self as? [ModelComparisonProtocol] else { return false }
|
||||
return models.indices.allSatisfy { index in
|
||||
self[index].isEqual(to: models[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Optional where Wrapped: Collection {
|
||||
/// Checks if the current model is equal to another model.
|
||||
func isEqual(to models: [ModelComparisonProtocol]?) -> Bool {
|
||||
guard let self = self as? [ModelComparisonProtocol] else {
|
||||
return models == nil
|
||||
}
|
||||
guard let models = models else {
|
||||
return false
|
||||
}
|
||||
return self.isEqual(to: models)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Optional {
|
||||
/// Checks if
|
||||
func matchExistence(with anotherOptional: Optional) -> Bool {
|
||||
switch(self) {
|
||||
case .none:
|
||||
return anotherOptional == nil
|
||||
case .some(_):
|
||||
return anotherOptional != nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func == (lhs: (any ModelComparisonProtocol)?, rhs: (any ModelComparisonProtocol)?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.some(let lhs), .some(let rhs)):
|
||||
return lhs.isEqual(to: rhs)
|
||||
case (.none, .none):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func != (lhs: (any ModelComparisonProtocol)?, rhs: (any ModelComparisonProtocol)?) -> Bool {
|
||||
return !(lhs == rhs)
|
||||
}
|
||||
|
||||
func == (lhs: [any ModelComparisonProtocol]?, rhs: [any ModelComparisonProtocol]?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.some(let lhs), .some(let rhs)):
|
||||
return lhs == rhs
|
||||
case (.none, .none):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func != (lhs: [any ModelComparisonProtocol]?, rhs: [any ModelComparisonProtocol]?) -> Bool {
|
||||
return !(lhs == rhs)
|
||||
}
|
||||
|
||||
func == (lhs: [any ModelComparisonProtocol], rhs: [any ModelComparisonProtocol]) -> Bool {
|
||||
return lhs.elementsEqual(rhs, by: { (lhsElement, rhsElement) -> Bool in
|
||||
return lhsElement == rhsElement
|
||||
})
|
||||
}
|
||||
|
||||
func != (lhs: [any ModelComparisonProtocol], rhs: [any ModelComparisonProtocol]) -> Bool {
|
||||
return !(lhs == rhs)
|
||||
}
|
||||
|
||||
@ -50,9 +50,9 @@ public struct ModelRegistry {
|
||||
|
||||
/// A convenience wrapping function where error handling is managed within the class.
|
||||
/// - Parameter type: Takes an object of ModelProtocol.self which is used to register
|
||||
public static func register<M: ModelProtocol>(_ type: M.Type) {
|
||||
public static func register<M: ModelProtocol>(_ type: M.Type, allowsReplace: Bool = false) {
|
||||
do {
|
||||
try throwable_register(type: type)
|
||||
try throwable_register(type: type, allowsReplace: allowsReplace)
|
||||
} catch {
|
||||
handleError(error)
|
||||
}
|
||||
@ -211,7 +211,7 @@ public struct ModelRegistry {
|
||||
|
||||
public static func getCodingKey<T>(for type: T.Type) throws -> AnyCodingKey {
|
||||
guard let category = getCategory(for: type) else {
|
||||
throw ModelRegistry.Error.decoderOther(message: "decodeModelsIfPresent only works for objects implementing the ModelProtocol protocol")
|
||||
throw ModelRegistry.Error.decoderOther(message: "Category hasn’t been registered for the CodingKey for this type: \(String(describing: type.self))")
|
||||
}
|
||||
|
||||
return AnyCodingKey(category.codingKey)
|
||||
|
||||
@ -147,6 +147,12 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO
|
||||
/// Clears the persistent JSON cache
|
||||
- (void)clearPersistentJSONCache;
|
||||
|
||||
// Removes a json dictionary from the cache by pageType.
|
||||
- (void)removePersistentJSONCacheForPageType:(nonnull NSString *)pageType pageJSON:(nonnull NSDictionary *)jsonDictionary;
|
||||
|
||||
// Removes a json dictionary from the cache by moduleType.
|
||||
- (void)removePersistentModuleCacheForModule:(nonnull NSString *)moduleType moduleJSON:(nonnull NSDictionary *)jsonDictionary;
|
||||
|
||||
#pragma mark Image Functions
|
||||
|
||||
/// Register a bundle as one to search for images in.
|
||||
|
||||
@ -181,6 +181,14 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
return [[PersistentCacheManager shared] loadForKey:moduleName path:[self getPathForPersistentCacheModule:moduleName] error:&error];
|
||||
}
|
||||
|
||||
- (void)removePersistentJSONCacheForPageType:(nonnull NSString *)pageType pageJSON:(nonnull NSDictionary *)jsonDictionary {
|
||||
[[PersistentCacheManager shared] removeForKey:pageType error:nil];
|
||||
}
|
||||
|
||||
- (void)removePersistentModuleCacheForModule:(nonnull NSString *)moduleType moduleJSON:(nonnull NSDictionary *)jsonDictionary {
|
||||
[[PersistentCacheManager shared] removeForKey:moduleType error:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Advanced Fetch
|
||||
|
||||
- (void)fetchJSONForPageType:(nullable NSString *)pageType queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler {
|
||||
@ -315,7 +323,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
[weakSelf.pageTypeCache setObject:jsonDictionary forKey:pageType];
|
||||
|
||||
if (![self shouldPersistentlyCachePage:jsonDictionary pageType:pageType]) {
|
||||
[[PersistentCacheManager shared] removeForKey:pageType error:nil];
|
||||
[self removePersistentJSONCacheForPageType:pageType pageJSON:jsonDictionary];
|
||||
return;
|
||||
}
|
||||
[self addPageToPersistentCache:jsonDictionary pageType:pageType expirationDate:[self getExpirationDateForJSON:jsonDictionary]];
|
||||
@ -351,7 +359,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
[weakSelf.moduleCache setObject:jsonDictionary forKey:module];
|
||||
|
||||
if (![self shouldPersistentlyCacheModule:jsonDictionary module:module]) {
|
||||
[[PersistentCacheManager shared] removeForKey:module error:nil];
|
||||
[self removePersistentModuleCacheForModule:module moduleJSON:jsonDictionary];
|
||||
return;
|
||||
}
|
||||
[self addModuleToPersistentCache:jsonDictionary moduleName:module expirationDate:[self getExpirationDateForJSON:jsonDictionary]];
|
||||
@ -365,6 +373,16 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
|
||||
|
||||
- (void)addModulesToCache:(nonnull NSDictionary *)jsonDictionary queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock {
|
||||
[jsonDictionary enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
|
||||
if (![obj isKindOfClass:[NSDictionary class]]) {
|
||||
MVMCoreErrorObject *error = [[MVMCoreErrorObject alloc]
|
||||
initWithTitle:nil
|
||||
messageToLog:[NSString stringWithFormat:@"Invalid module format. Cannot cache %@ as it's not an object", key]
|
||||
code:ErrorCodeJSONNotDictionary
|
||||
domain:ErrorDomainSystem
|
||||
location:NSStringFromClass([self class])];
|
||||
[MVMCoreLoggingHandler.sharedLoggingHandler addErrorToLog:error];
|
||||
return;
|
||||
}
|
||||
[self addModuleToCache:obj module:key];
|
||||
}];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
@ -7,9 +7,31 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os
|
||||
|
||||
@objc public extension MVMCoreLoggingHandler {
|
||||
@objc func print(with message: String) {
|
||||
Swift.print(message)
|
||||
public protocol CoreLogging {
|
||||
static var loggingCategory: String? { get }
|
||||
|
||||
var loggingPrefix: String { get }
|
||||
}
|
||||
|
||||
public extension CoreLogging {
|
||||
|
||||
static var loggingCategory: String? { return nil }
|
||||
|
||||
var loggingPrefix: String {
|
||||
return ""
|
||||
}
|
||||
|
||||
static func debugLog(_ string: String) {
|
||||
#if LOGGING
|
||||
MVMCoreLoggingHandler.shared()?.handleDebugMessage(string, category: loggingCategory)
|
||||
#endif
|
||||
}
|
||||
|
||||
func debugLog(_ string: String) {
|
||||
#if LOGGING
|
||||
MVMCoreLoggingHandler.shared()?.handleDebugMessage("\(loggingPrefix)\(string)", category: Self.loggingCategory)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,9 +7,28 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os
|
||||
|
||||
@objc open class MVMCoreLoggingHandler: NSObject, MVMCoreLoggingDelegateProtocol {
|
||||
|
||||
public static let standardCategory = "General"
|
||||
|
||||
private let logger = Logger(subsystem: "MVMCoreLogging", category: standardCategory)
|
||||
private var loggerCache = [String: Logger]()
|
||||
|
||||
open func getLogger(category: String?) -> Logger {
|
||||
if let category {
|
||||
if let logger = loggerCache[category] {
|
||||
return logger
|
||||
} else {
|
||||
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: category)
|
||||
loggerCache[category] = logger
|
||||
return logger
|
||||
}
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
@objc(sharedLoggingHandler)
|
||||
public static func shared() -> Self? {
|
||||
return MVMCoreActionUtility.initializerClassCheck(MVMCoreObject.sharedInstance()?.loggingDelegate as? NSObject, classToVerify: self) as? Self
|
||||
@ -29,14 +48,57 @@ import Foundation
|
||||
// MARK: - logging delegate
|
||||
@objc open func handleDebugMessage(_ message: String?) {
|
||||
#if LOGGING
|
||||
guard let message = message else { return }
|
||||
self.print(with: message)
|
||||
guard let message = message else { return }
|
||||
guard message.count < 1024 else {
|
||||
logger.debug("\(message.prefix(300), privacy: .public)... <stdio>") // Send initial log to console.
|
||||
print(message) // Print the whole on stdout.
|
||||
return
|
||||
}
|
||||
logger.debug("\(message, privacy: .public)") // Assume that becaues this is a LOGGING build we want these messages to be unmasked.
|
||||
// TODO: How do we split the messaging by Library and Subsystem?
|
||||
#endif
|
||||
}
|
||||
|
||||
@objc open func handleDebugMessage(_ message: String, category: String?) {
|
||||
#if LOGGING
|
||||
guard message.count < 1024 else {
|
||||
getLogger(category: category).debug("\(message.prefix(300), privacy: .public)... <stdio>") // Send initial log to console.
|
||||
print(message) // Print the whole on stdout.
|
||||
return
|
||||
}
|
||||
getLogger(category: category).debug("\(message, privacy: .public)") // Assume that becaues this is a LOGGING build we want these messages to be unmasked.
|
||||
#endif
|
||||
}
|
||||
|
||||
open func handleWarningMessage(_ message: String, category: String?) {
|
||||
#if LOGGING
|
||||
guard message.count < 1024 else {
|
||||
getLogger(category: category).warning("\(message.prefix(300), privacy: .public)... <stdio>") // Send initial log to console.
|
||||
print(message) // Print the whole on stdout.
|
||||
return
|
||||
}
|
||||
getLogger(category: category).warning("\(message, privacy: .public)") // Assume that becaues this is a LOGGING build we want these messages to be unmasked.
|
||||
#endif
|
||||
}
|
||||
|
||||
open func handleErrorMessage(_ message: String, category: String?) {
|
||||
#if LOGGING
|
||||
guard message.count < 1024 else {
|
||||
getLogger(category: category).error("\(message.prefix(300), privacy: .public)... <stdio>") // Send initial log to console.
|
||||
print(message) // Print the whole on stdout.
|
||||
return
|
||||
}
|
||||
getLogger(category: category).error("\(message, privacy: .public)") // Assume that becaues this is a LOGGING build we want these messages to be unmasked.
|
||||
#endif
|
||||
}
|
||||
|
||||
@objc(addErrorToLog:)
|
||||
open func addError(toLog errorObject: MVMCoreErrorObject) {
|
||||
// Subclass to handle.
|
||||
if errorObject.silentError {
|
||||
handleWarningMessage(errorObject.messageToLog ?? errorObject.messageToDisplay ?? "Some error occurred.", category: "Handled Exception")
|
||||
} else {
|
||||
handleErrorMessage(errorObject.messageToLog ?? errorObject.messageToDisplay ?? "Some error occurred.", category: "Handled Exception")
|
||||
}
|
||||
}
|
||||
|
||||
open func logLoadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: MVMCoreViewControllerProtocol?, error: MVMCoreErrorObject?) {}
|
||||
|
||||
@ -8,7 +8,11 @@
|
||||
|
||||
#ifndef MVMCoreLoggingHandlerHelper_h
|
||||
#define MVMCoreLoggingHandlerHelper_h
|
||||
|
||||
#define MVMCoreLog(fmt, ...) \
|
||||
[MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:(@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__]];
|
||||
#endif
|
||||
|
||||
#define MVMCoreNetworkLog(fmt, ...) \
|
||||
[MVMCoreLoggingHandler.sharedLoggingHandler handleDebugMessage:[NSString stringWithFormat:(@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__] category: @"Network"];
|
||||
|
||||
#endif
|
||||
|
||||
@ -68,9 +68,9 @@ open class NavigationOperation: MVMCoreOperation, UINavigationControllerDelegate
|
||||
let index = navigationController.getIndexOfViewController(with: pageType) else {
|
||||
return false
|
||||
}
|
||||
var viewControllers = navigationController.viewControllers[...index]
|
||||
var viewControllers = Array(navigationController.viewControllers[...index])
|
||||
viewControllers[index] = viewController
|
||||
set(viewControllers: Array(viewControllers), navigationController: navigationController, animated: animated)
|
||||
set(viewControllers: viewControllers, navigationController: navigationController, animated: animated)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -110,7 +110,8 @@ open class NavigationOperation: MVMCoreOperation, UINavigationControllerDelegate
|
||||
*/
|
||||
@MainActor
|
||||
open func set(viewControllers: [UIViewController], navigationController: UINavigationController, animated: Bool) {
|
||||
guard viewControllers.count > 0 else {
|
||||
guard viewControllers.count > 0,
|
||||
navigationController.viewControllers != viewControllers else { // If the controller stack is the same, iOS will not call the delegate method of the change, causing the operation to hang.
|
||||
markAsFinished()
|
||||
return
|
||||
}
|
||||
|
||||
@ -38,6 +38,9 @@
|
||||
/// Clears the session singleton. Creates a new session NSURLSession also.
|
||||
- (void)clearSessionObject;
|
||||
|
||||
/// Clears any persistent cache related to the current session.
|
||||
- (void)clearPersistentCache;
|
||||
|
||||
/// Copys string to clipboard and assigns self.clipboardString for validation
|
||||
/// Should only be used when expected string is a secure string
|
||||
-(void)copyStringToClipboard :(nullable NSString *)clipboardString;
|
||||
|
||||
@ -53,4 +53,6 @@
|
||||
self.session = [self createNSURLSession];
|
||||
}
|
||||
|
||||
- (void)clearPersistentCache {}
|
||||
|
||||
@end
|
||||
|
||||
@ -89,21 +89,21 @@ public extension NavigationHandler {
|
||||
switch requestParameters.loadStyle {
|
||||
case .replaceCurrent:
|
||||
guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController else { return nil }
|
||||
return NavigationOperation(with: .replace(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
|
||||
return NavigationOperation(with: .replace(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
|
||||
case .onTopOfRoot:
|
||||
guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController,
|
||||
let root = navigationController.viewControllers.first else { return nil }
|
||||
let viewControllers = [root, viewController]
|
||||
return NavigationOperation(with: .set(viewControllers: viewControllers, navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
|
||||
return NavigationOperation(with: .set(viewControllers: viewControllers, navigationController: navigationController, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
|
||||
case .becomeRoot:
|
||||
guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController else { return nil }
|
||||
return NavigationOperation(with: .set(viewControllers: [viewController], navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
|
||||
return NavigationOperation(with: .set(viewControllers: [viewController], navigationController: navigationController, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
|
||||
case .present:
|
||||
guard let viewControllerToPresentOn = getViewControllerToPresentOn() else { return nil }
|
||||
return NavigationOperation(with: .present(viewController: viewController, onController: viewControllerToPresentOn, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
|
||||
return NavigationOperation(with: .present(viewController: viewController, onController: viewControllerToPresentOn, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
|
||||
default:
|
||||
guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController else { return nil }
|
||||
return NavigationOperation(with: .push(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
|
||||
return NavigationOperation(with: .push(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,8 +23,10 @@
|
||||
// Initialization code
|
||||
self.title = title;
|
||||
self.messageToDisplay = message;
|
||||
self.messageToLog = message;
|
||||
self.code = code;
|
||||
self.domain = domain;
|
||||
self.systemDomain = nil;
|
||||
self.location = location;
|
||||
self.date = [NSDate date];
|
||||
self.silentError = YES;
|
||||
@ -32,6 +34,11 @@
|
||||
[MVMCoreDispatchUtility performSyncBlockOnMainThread:^{
|
||||
self.applicationState = [UIApplication sharedApplication].applicationState;
|
||||
}];
|
||||
self.sessionId = nil;
|
||||
self.requestId = nil;
|
||||
self.requestUrl = nil;
|
||||
self.serverResponseInfo = nil;
|
||||
self.crashLog = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -41,9 +48,11 @@
|
||||
if (self = [super init]) {
|
||||
// Initialization code
|
||||
self.title = title;
|
||||
self.messageToDisplay = nil;
|
||||
self.messageToLog = messageToLog;
|
||||
self.code = code;
|
||||
self.domain = domain;
|
||||
self.systemDomain = nil;
|
||||
self.location = location;
|
||||
self.date = [NSDate date];
|
||||
self.silentError = YES;
|
||||
@ -51,6 +60,11 @@
|
||||
[MVMCoreDispatchUtility performSyncBlockOnMainThread:^{
|
||||
self.applicationState = [UIApplication sharedApplication].applicationState;
|
||||
}];
|
||||
self.sessionId = nil;
|
||||
self.requestId = nil;
|
||||
self.requestUrl = nil;
|
||||
self.serverResponseInfo = nil;
|
||||
self.crashLog = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
69
MVMCore/MVMCore/Utility/ReadableDecodingErrors.swift
Normal file
69
MVMCore/MVMCore/Utility/ReadableDecodingErrors.swift
Normal file
@ -0,0 +1,69 @@
|
||||
//
|
||||
// ReadableDecodingErrors.swift
|
||||
// MVMCore
|
||||
//
|
||||
// Created by Kyle Hedden on 10/5/23.
|
||||
// Copyright © 2023 myverizon. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol HumanReadableDecodingErrorProtocol {
|
||||
var readableDescription: String { get }
|
||||
}
|
||||
|
||||
extension JSONError: HumanReadableDecodingErrorProtocol {
|
||||
public var readableDescription: String {
|
||||
switch (self) {
|
||||
case .other(let other):
|
||||
if let other = other as? HumanReadableDecodingErrorProtocol {
|
||||
return other.readableDescription
|
||||
}
|
||||
return description
|
||||
default:
|
||||
return description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ModelRegistry.Error: HumanReadableDecodingErrorProtocol {
|
||||
public var readableDescription: String {
|
||||
switch (self) {
|
||||
case .decoderErrorModelNotMapped(let identifier, let codingKey, let codingPath) where identifier != nil && codingKey != nil && codingPath != nil:
|
||||
return "Model identifier \"\(identifier!)\" is not mapped for \"\(codingKey!.stringValue)\" @ \(codingPath!.map { return $0.stringValue })"
|
||||
|
||||
case .decoderErrorObjectNotPresent(let codingKey, let codingPath):
|
||||
return "Required model \"\(codingKey.stringValue)\" was not found @ \(codingPath.map { return $0.stringValue })"
|
||||
|
||||
case .decoderOther(let message):
|
||||
return "An issue occurred while decoding: \(message)"
|
||||
|
||||
case .other(let message):
|
||||
return "Registry error: \(message)"
|
||||
|
||||
default:
|
||||
return "Registry error: \((self as NSError).localizedFailureReason ?? self.localizedDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DecodingError: HumanReadableDecodingErrorProtocol {
|
||||
public var readableDescription: String {
|
||||
switch (self) {
|
||||
case .keyNotFound(let codingKey, let context):
|
||||
return "Required key \(codingKey.stringValue) was not found @ \(context.codingPath.map { return $0.stringValue })"
|
||||
|
||||
case .valueNotFound(_, let context):
|
||||
return "Value not found @ \(context.codingPath.map { return $0.stringValue })"
|
||||
|
||||
case .typeMismatch(_, let context):
|
||||
return "Value type mismatch @ \(context.codingPath.map { return $0.stringValue })"
|
||||
|
||||
case .dataCorrupted(let context):
|
||||
return "Data corrupted @ \(context.codingPath.map { return $0.stringValue })"
|
||||
|
||||
@unknown default:
|
||||
return (self as NSError).localizedFailureReason ?? self.localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user