Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/mvm_core into feature/testing

This commit is contained in:
Pfeil, Scott Robert 2021-06-30 10:53:20 -04:00
commit 5fe0466036
2 changed files with 167 additions and 64 deletions

View File

@ -10,93 +10,192 @@
public struct ModelRegistry {
/// Error object for the Model Registry.
public enum Error: Swift.Error {
case keyNotFound
case encoderError
case decoderError
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 duplicateRegistration(message: String)
case other(message: String)
case handlerNotMapped(identifer: String? = nil, categoryName: String? = nil)
}
private struct Category {
public struct Category {
var name: String
var codingKey: String
var instanceTypes: [String: ModelProtocol.Type] = [:]
var handlerTypes: [String: ModelHandlerProtocol.Type] = [:]
}
private 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 {
//register the type
try self.register(model)
public static var categories: [String: Category] = [:]
//--------------------------------------------------
// MARK: - Convenient Registration
//--------------------------------------------------
/// 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
//get the category for the ModelProtocol
// Get the category for the ModelProtocol
var category = getCategory(for: model)
// Check to ensure the Category/Container combination doesn't exist.
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 {
category.handlerTypes[key] = handler
}
categories[category.name] = category
}
/// Registers models for Atomic use.
public static func register<M: ModelProtocol>(_ type: M.Type) throws {
//get the category for the ModelProtocol
/// - Parameter type: Takes an object of ModelProtocol.self which is used to register
/// - 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)
// Check to ensure the Category/Type combination doesn't exist.
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
categories[M.categoryName] = category
}
//--------------------------------------------------
// MARK: - Functions
//--------------------------------------------------
public static func getHandler(_ model: ModelProtocol) throws -> ModelHandlerProtocol.Type {
//get the modelType
// Get the modelType
let modelType = type(of: model)
//get the category for the ModelProtocol
// Get the category for the ModelProtocol
guard let category = categories[modelType.categoryName] else {
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 {
throw ModelRegistry.Error.handlerNotMapped(identifer: modelType.identifier, categoryName: category.name)
}
return handlerType
}
private static func getCategory<M: ModelProtocol>(for type: M.Type) -> Category {
return categories[type.categoryName] ?? Category(name: type.categoryName, codingKey: type.categoryCodingKey)
public static func getCategory<M: ModelProtocol>(for type: M.Type) -> Category {
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.
//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)}
// 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)}
return protocols.compactMap({ (p) -> Category? in
guard let c = categories[p] else {
return nil
}
return c
categories[p] ?? nil
}).first
}
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 {
@ -108,12 +207,13 @@ public struct ModelRegistry {
}
}
extension KeyedDecodingContainer where Key: CodingKey {
public extension KeyedDecodingContainer where Key: CodingKey {
//--------------------------------------------------
// MARK: - Decode
//--------------------------------------------------
/// 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 {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey.stringValue)")
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.
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 {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey.stringValue)")
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.
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 {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey.stringValue)")
throw ModelRegistry.Error.decoderErrorObjectNotPresent(codingKey: codingKey, codingPath: codingPath)
@ -139,25 +239,27 @@ extension KeyedDecodingContainer where Key: CodingKey {
return models
}
//--------------------------------------------------
// MARK: - DecodeIfPresent
//--------------------------------------------------
/// Decodes to a registered model based on the identifier, optional.
public func decodeModelIfPresent<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> T? {
//create coding key
func decodeModelIfPresent<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> T? {
// Create coding key
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),
let container = try? self.nestedContainer(keyedBy: AnyCodingKey.self, forKey: codingKey),
let identifier = try container.decodeIfPresent(String.self, forKey: typeCodingKey) else { return nil }
let container = try? self.nestedContainer(keyedBy: AnyCodingKey.self, forKey: codingKey),
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 {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelProtocol not mapped: \(identifier)")
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)
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.
public func decodeModelsIfPresent<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> [T]? {
func decodeModelsIfPresent<T>(codingKey: KeyedDecodingContainer<K>.Key) throws -> [T]? {
guard contains(codingKey),
var container = try? self.nestedUnkeyedContainer(forKey: codingKey)
else { return nil }
var container = try? self.nestedUnkeyedContainer(forKey: codingKey)
else { return nil }
return try container.decodeModelsIfPresent()
}
/// 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),
var container = try? nestedUnkeyedContainer(forKey: codingKey)
else { return nil }
var container = try? nestedUnkeyedContainer(forKey: codingKey)
else { return nil }
return try container.decodeModels2DIfPresent()
}
@ -259,6 +361,7 @@ public extension UnkeyedDecodingContainer {
throw ModelRegistry.Error.decoderError
}
}
return models
}
@ -277,6 +380,7 @@ public extension UnkeyedDecodingContainer {
}
modelLists.append(models)
}
return modelLists
}
}

View File

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