Updating registry for logging

This commit is contained in:
Christiano, Kevin 2021-06-29 20:04:24 +00:00 committed by Suresh, Kamlesh Jain
parent 856b55b48b
commit 6e8c464097
2 changed files with 167 additions and 64 deletions

View File

@ -10,93 +10,192 @@
public struct ModelRegistry { public struct ModelRegistry {
/// Error object for the Model Registry.
public enum Error: Swift.Error { public enum Error: Swift.Error {
case keyNotFound case keyNotFound
case encoderError case encoderError
case decoderError case decoderError
case decoderOther(message: String) case decoderOther(message: String)
case decoderErrorObjectNotPresent(codingKey: CodingKey, codingPath: [CodingKey]) case decoderErrorObjectNotPresent(codingKey: CodingKey, codingPath: [CodingKey])
case decoderErrorModelNotMapped(identifer: String? = nil, codingKey: CodingKey? = nil, codingPath: [CodingKey]? = nil) case decoderErrorModelNotMapped(identifer: String? = nil, codingKey: CodingKey? = nil, codingPath: [CodingKey]? = nil)
case duplicateRegistration(message: String)
case other(message: String) case other(message: String)
case handlerNotMapped(identifer: String? = nil, categoryName: String? = nil) case handlerNotMapped(identifer: String? = nil, categoryName: String? = nil)
} }
private struct Category { public struct Category {
var name: String var name: String
var codingKey: String var codingKey: String
var instanceTypes: [String: ModelProtocol.Type] = [:] var instanceTypes: [String: ModelProtocol.Type] = [:]
var handlerTypes: [String: ModelHandlerProtocol.Type] = [:] var handlerTypes: [String: ModelHandlerProtocol.Type] = [:]
} }
private static var categories: [String: Category] = [:] public static var categories: [String: Category] = [:]
/// Registers models for Atomic use. //--------------------------------------------------
public static func register<H: ModelHandlerProtocol, M: ModelProtocol>(handler: H.Type, for model: M.Type) throws { // MARK: - Convenient Registration
//register the type //--------------------------------------------------
try self.register(model)
/// A convenience wrapping function where error handling is managed within the class.
/// - Parameters:
/// - handler: The handling class taking an object of ModelHandlerProtocol.self which is used to register
/// - model: The data object of ModelProtocol.self which is used to register
public static func register<H: ModelHandlerProtocol, M: ModelProtocol>(handler: H.Type, for model: M.Type) {
do {
try throwable_register(handler: handler, model: model)
} catch {
handleError(error)
}
}
/// 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) {
do {
try throwable_register(type: type)
} catch {
handleError(error)
}
}
//--------------------------------------------------
// MARK: - Error Handling
//--------------------------------------------------
/// Handles an error if thrown when registering a ModelProtocol object.
/// - Parameter error: The error object produced by a thrown exception.
public static func handleError(_ error: Swift.Error) {
//get the key for the handler guard let errorObject = MVMCoreErrorObject(title: nil,
messageToLog: nil,
code: (error as NSError).code,
domain: ErrorDomainNative,
location: #file) else { return }
switch error as? ModelRegistry.Error {
case .keyNotFound:
errorObject.title = "Key Not Found"
case .encoderError:
errorObject.title = "Encoder Error"
case .decoderError:
errorObject.title = "Decoder Error"
case .decoderOther(let message):
errorObject.title = "Decoder Error: Other"
errorObject.messageToLog = message
case .decoderErrorObjectNotPresent(let codingKey, let codingPath):
errorObject.title = "Decoder Error: Object Not Present"
let codingPathConcat = codingPath.compactMap { $0.stringValue }.joined(separator: " ")
errorObject.messageToLog = codingKey.stringValue + codingPathConcat
case .decoderErrorModelNotMapped(let identifer, let codingKey, let codingPath):
errorObject.title = "Decoder Error: Model Not Mapped"
let codingPathConcat = codingPath?.compactMap { $0.stringValue }.joined(separator: " ") ?? ""
errorObject.messageToLog = (identifer ?? "") + (codingKey?.stringValue ?? "") + codingPathConcat
case .handlerNotMapped(let identifer, let categoryName):
errorObject.title = "Handler Not Mapped"
errorObject.messageToLog = (identifer ?? "") + (categoryName ?? "")
case .other(let message):
errorObject.title = "Other Registry Error"
errorObject.messageToLog = message
case .duplicateRegistration(let message):
errorObject.title = "Duplicate Registration"
errorObject.messageToLog = message
default:
errorObject.title = "Unknown Model Registry Error"
}
MVMCoreLoggingHandler.addError(toLog: errorObject)
#if DEBUG
triggerCrashInDebug()
#endif
}
#if DEBUG
private static func triggerCrashInDebug() {
fatalError()
}
#endif
//--------------------------------------------------
// MARK: - Throwable Registration
//--------------------------------------------------
/// Registers models for Atomic use.
/// - Parameters:
/// - handler: The handling class taking an object of ModelHandlerProtocol.self which is used to register
/// - model: The data object of ModelProtocol.self which is used to register
/// - Throws: An Error object of `ModelRegistry.Error`
public static func throwable_register<H: ModelHandlerProtocol, M: ModelProtocol>(handler: H.Type, model: M.Type) throws {
// Register the type
try self.throwable_register(type: model)
// Get the key for the handler
let key = model.identifier let key = model.identifier
//get the category for the ModelProtocol // Get the category for the ModelProtocol
var category = getCategory(for: model) var category = getCategory(for: model)
// Check to ensure the Category/Container combination doesn't exist. // Check to ensure the Category/Container combination doesn't exist.
if category.handlerTypes[key] != nil { if category.handlerTypes[key] != nil {
throw ModelRegistry.Error.other(message: "ModelHandlerProtocol: \(String(describing: handler)) already exists in Category: \(category.name)") throw ModelRegistry.Error.duplicateRegistration(message: "ModelHandlerProtocol: \(String(describing: handler)) already exists in Category: \(category.name)")
} else { } else {
category.handlerTypes[key] = handler category.handlerTypes[key] = handler
} }
categories[category.name] = category categories[category.name] = category
} }
/// Registers models for Atomic use. /// Registers models for Atomic use.
public static func register<M: ModelProtocol>(_ type: M.Type) throws { /// - Parameter type: Takes an object of ModelProtocol.self which is used to register
//get the category for the ModelProtocol /// - Throws: An Error object of `ModelRegistry.Error`
public static func throwable_register<M: ModelProtocol>(type: M.Type) throws {
// Get the category for the ModelProtocol
var category = getCategory(for: type) var category = getCategory(for: type)
// Check to ensure the Category/Type combination doesn't exist. // Check to ensure the Category/Type combination doesn't exist.
if category.instanceTypes[M.identifier] != nil { if category.instanceTypes[M.identifier] != nil {
throw ModelRegistry.Error.other(message: "ModelProtocol: \(M.identifier) already exists in Category: \(M.categoryName)") throw ModelRegistry.Error.duplicateRegistration(message: "ModelProtocol: \(M.identifier) already exists in Category: \(M.categoryName)")
} }
category.instanceTypes[M.identifier] = type category.instanceTypes[M.identifier] = type
categories[M.categoryName] = category categories[M.categoryName] = category
} }
//--------------------------------------------------
// MARK: - Functions
//--------------------------------------------------
public static func getHandler(_ model: ModelProtocol) throws -> ModelHandlerProtocol.Type { public static func getHandler(_ model: ModelProtocol) throws -> ModelHandlerProtocol.Type {
//get the modelType // Get the modelType
let modelType = type(of: model) let modelType = type(of: model)
//get the category for the ModelProtocol // Get the category for the ModelProtocol
guard let category = categories[modelType.categoryName] else { guard let category = categories[modelType.categoryName] else {
throw ModelRegistry.Error.other(message: "Category not found: \(modelType.categoryName)") throw ModelRegistry.Error.other(message: "Category not found: \(modelType.categoryName)")
} }
//get the containerProtocol for this ModelProtocol you had registered // Get the containerProtocol for this ModelProtocol you had registered
guard let handlerType = category.handlerTypes[modelType.identifier] else { guard let handlerType = category.handlerTypes[modelType.identifier] else {
throw ModelRegistry.Error.handlerNotMapped(identifer: modelType.identifier, categoryName: category.name) throw ModelRegistry.Error.handlerNotMapped(identifer: modelType.identifier, categoryName: category.name)
} }
return handlerType return handlerType
} }
private static func getCategory<M: ModelProtocol>(for type: M.Type) -> Category { public static func getCategory<M: ModelProtocol>(for type: M.Type) -> Category {
return categories[type.categoryName] ?? Category(name: type.categoryName, codingKey: type.categoryCodingKey) categories[type.categoryName] ?? Category(name: type.categoryName, codingKey: type.categoryCodingKey)
} }
private static func getCategory<T>(for type: T.Type) -> Category? { public static func getCategory<T>(for type: T.Type) -> Category? {
// Temporary code till we find a better solution. // Temporary code till we find a better solution.
//if this is a protocol composotion, loop through each protocol for a category lookup // if this is a protocol composotion, loop through each protocol for a category lookup
let protocols = String(describing: T.self).components(separatedBy: "&").compactMap{$0.trimmingCharacters(in: .whitespaces)} let protocols = String(describing: T.self).components(separatedBy: "&").compactMap{ $0.trimmingCharacters(in: .whitespaces)}
return protocols.compactMap({ (p) -> Category? in return protocols.compactMap({ (p) -> Category? in
guard let c = categories[p] else { categories[p] ?? nil
return nil
}
return c
}).first }).first
} }
public static func getType<T>(for name: String, with type: T.Type) -> ModelProtocol.Type? { public static func getType<T>(for name: String, with type: T.Type) -> ModelProtocol.Type? {
return getCategory(for: type)?.instanceTypes[name] getCategory(for: type)?.instanceTypes[name]
} }
public static func getCodingKey<T>(for type: T.Type) throws -> AnyCodingKey { public static func getCodingKey<T>(for type: T.Type) throws -> AnyCodingKey {
@ -108,12 +207,13 @@ public struct ModelRegistry {
} }
} }
extension KeyedDecodingContainer where Key: CodingKey { public extension KeyedDecodingContainer where Key: CodingKey {
//--------------------------------------------------
// MARK: - Decode // MARK: - Decode
//--------------------------------------------------
/// Decodes to a registered model based on the identifier /// Decodes to a registered model based on the identifier
public func decodeModel<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> T { func decodeModel<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> T {
guard let model: T = try decodeModelIfPresent(codingKey: codingKey) else { guard let model: T = try decodeModelIfPresent(codingKey: codingKey) else {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey.stringValue)") MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey.stringValue)")
throw ModelRegistry.Error.decoderErrorObjectNotPresent(codingKey: codingKey, codingPath: codingPath) throw ModelRegistry.Error.decoderErrorObjectNotPresent(codingKey: codingKey, codingPath: codingPath)
@ -122,7 +222,7 @@ extension KeyedDecodingContainer where Key: CodingKey {
} }
/// Decodes an array of registered model based on the identifiers. /// Decodes an array of registered model based on the identifiers.
public func decodeModels<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> [T] { func decodeModels<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> [T] {
guard let model: [T] = try decodeModelsIfPresent(codingKey: codingKey) else { guard let model: [T] = try decodeModelsIfPresent(codingKey: codingKey) else {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey.stringValue)") MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey.stringValue)")
throw ModelRegistry.Error.decoderErrorObjectNotPresent(codingKey: codingKey, codingPath: codingPath) throw ModelRegistry.Error.decoderErrorObjectNotPresent(codingKey: codingKey, codingPath: codingPath)
@ -131,7 +231,7 @@ extension KeyedDecodingContainer where Key: CodingKey {
} }
/// Decodes an array with arrays of models based on the identifiers. /// Decodes an array with arrays of models based on the identifiers.
public func decodeModels2D<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> [[T]] { func decodeModels2D<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> [[T]] {
guard let models: [[T]] = try decodeModels2DIfPresent(codingKey: codingKey) else { guard let models: [[T]] = try decodeModels2DIfPresent(codingKey: codingKey) else {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey.stringValue)") MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey.stringValue)")
throw ModelRegistry.Error.decoderErrorObjectNotPresent(codingKey: codingKey, codingPath: codingPath) throw ModelRegistry.Error.decoderErrorObjectNotPresent(codingKey: codingKey, codingPath: codingPath)
@ -139,25 +239,27 @@ extension KeyedDecodingContainer where Key: CodingKey {
return models return models
} }
//--------------------------------------------------
// MARK: - DecodeIfPresent // MARK: - DecodeIfPresent
//--------------------------------------------------
/// Decodes to a registered model based on the identifier, optional. /// Decodes to a registered model based on the identifier, optional.
public func decodeModelIfPresent<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> T? { func decodeModelIfPresent<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> T? {
//create coding key // Create coding key
let typeCodingKey = try ModelRegistry.getCodingKey(for: T.self) let typeCodingKey = try ModelRegistry.getCodingKey(for: T.self)
//get the container that holds the identifier value // Get the container that holds the identifier value
guard contains(codingKey), guard contains(codingKey),
let container = try? self.nestedContainer(keyedBy: AnyCodingKey.self, forKey: codingKey), let container = try? self.nestedContainer(keyedBy: AnyCodingKey.self, forKey: codingKey),
let identifier = try container.decodeIfPresent(String.self, forKey: typeCodingKey) else { return nil } let identifier = try container.decodeIfPresent(String.self, forKey: typeCodingKey) else { return nil }
//get the type from the identifier value in the Registry // Get the type from the identifier value in the Registry
guard let type = ModelRegistry.getType(for: identifier, with: T.self) else { guard let type = ModelRegistry.getType(for: identifier, with: T.self) else {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelProtocol not mapped: \(identifier)") MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelProtocol not mapped: \(identifier)")
throw ModelRegistry.Error.decoderErrorModelNotMapped(identifer: identifier, codingKey: typeCodingKey, codingPath: container.codingPath) throw ModelRegistry.Error.decoderErrorModelNotMapped(identifer: identifier, codingKey: typeCodingKey, codingPath: container.codingPath)
} }
//decode the type using the decoder // Decode the type using the decoder
let model = try type.decode(keyedContainer: self, codingKey: codingKey) let model = try type.decode(keyedContainer: self, codingKey: codingKey)
if let model = model as? T { if let model = model as? T {
@ -169,19 +271,19 @@ extension KeyedDecodingContainer where Key: CodingKey {
} }
/// Decodes an array of registered model based on the identifiers, optional. /// Decodes an array of registered model based on the identifiers, optional.
public func decodeModelsIfPresent<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> [T]? { func decodeModelsIfPresent<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> [T]? {
guard contains(codingKey), guard contains(codingKey),
var container = try? self.nestedUnkeyedContainer(forKey: codingKey) var container = try? self.nestedUnkeyedContainer(forKey: codingKey)
else { return nil } else { return nil }
return try container.decodeModelsIfPresent() return try container.decodeModelsIfPresent()
} }
/// Decodes an array with arrays of models based on the identifiers, optional. /// Decodes an array with arrays of models based on the identifiers, optional.
public func decodeModels2DIfPresent<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> [[T]]? { func decodeModels2DIfPresent<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> [[T]]? {
guard contains(codingKey), guard contains(codingKey),
var container = try? nestedUnkeyedContainer(forKey: codingKey) var container = try? nestedUnkeyedContainer(forKey: codingKey)
else { return nil } else { return nil }
return try container.decodeModels2DIfPresent() return try container.decodeModels2DIfPresent()
} }
@ -259,6 +361,7 @@ public extension UnkeyedDecodingContainer {
throw ModelRegistry.Error.decoderError throw ModelRegistry.Error.decoderError
} }
} }
return models return models
} }
@ -277,6 +380,7 @@ public extension UnkeyedDecodingContainer {
} }
modelLists.append(models) modelLists.append(models)
} }
return modelLists return modelLists
} }
} }

View File

@ -8,22 +8,21 @@
@objcMembers open class ModelMapping: NSObject { @objcMembers open class ModelMapping: NSObject {
open class func registerObjects() {} open class func registerObjects() { }
open class func registerActions() { open class func registerActions() {
try? ModelRegistry.register(ActionOpenPageModel.self) ModelRegistry.register(ActionOpenPageModel.self)
try? ModelRegistry.register(ActionOpenUrlModel.self) ModelRegistry.register(ActionOpenUrlModel.self)
try? ModelRegistry.register(ActionCallModel.self) ModelRegistry.register(ActionCallModel.self)
try? ModelRegistry.register(ActionBackModel.self) ModelRegistry.register(ActionBackModel.self)
try? ModelRegistry.register(ActionShareModel.self) ModelRegistry.register(ActionShareModel.self)
try? ModelRegistry.register(ActionRestartModel.self) ModelRegistry.register(ActionRestartModel.self)
try? ModelRegistry.register(ActionPreviousSubmitModel.self) ModelRegistry.register(ActionPreviousSubmitModel.self)
try? ModelRegistry.register(ActionCancelModel.self) ModelRegistry.register(ActionCancelModel.self)
try? ModelRegistry.register(ActionSettingModel.self) ModelRegistry.register(ActionSettingModel.self)
try? ModelRegistry.register(ActionNoopModel.self) ModelRegistry.register(ActionNoopModel.self)
try? ModelRegistry.register(ActionActionsModel.self) ModelRegistry.register(ActionActionsModel.self)
try? ModelRegistry.register(ActionOpenSMSModel.self) ModelRegistry.register(ActionOpenSMSModel.self)
try? ModelRegistry.register(ActionContactModel.self) ModelRegistry.register(ActionContactModel.self)
} }
} }