diff --git a/MVMCore/MVMCore/Models/ActionType/ActionModelProtocol.swift b/MVMCore/MVMCore/Models/ActionType/ActionModelProtocol.swift index 27b62c2..66f8586 100644 --- a/MVMCore/MVMCore/Models/ActionType/ActionModelProtocol.swift +++ b/MVMCore/MVMCore/Models/ActionType/ActionModelProtocol.swift @@ -8,10 +8,6 @@ import Foundation -public enum ActionCodingKey: String, CodingKey { - case actionType -} - public protocol ActionModelProtocol: ModelProtocol { var actionType: String? { get set } diff --git a/MVMCore/MVMCore/Models/Model/ModelRegistry.swift b/MVMCore/MVMCore/Models/Model/ModelRegistry.swift index 057f9c1..54e1d89 100644 --- a/MVMCore/MVMCore/Models/Model/ModelRegistry.swift +++ b/MVMCore/MVMCore/Models/Model/ModelRegistry.swift @@ -9,7 +9,6 @@ import Foundation - public struct ModelRegistry { public enum Error: Swift.Error { @@ -45,24 +44,32 @@ public struct ModelRegistry { } private static func getCategory(for type: T.Type) -> Category? { - return categories["\(T.self)"] + // Temporary code till we find a better solution. + //create a string representation of the Type + let description = String(describing: T.self) + + //split the protocol composition (if there is one) + let protocols = description.components(separatedBy: "&").compactMap{$0.trimmingCharacters(in: .whitespaces)} + + //if this is a protocol composotion, loop through each protocol + //for a category lookup + if protocols.count > 1 { + return protocols.compactMap({ (p) -> Category? in + guard let c = categories[p] else { + return nil + } + return c + }).first + }//otherwise use the description of the type for the lookup + else { + return categories[description] + } } public static func getType(for name: String, with type: T.Type) -> ModelProtocol.Type? { return getCategory(for: type)?.instanceTypes[name] } - private static func getCategory(for typeString: String) -> Category? { - for (_, value) in categories where value.codingKey == typeString { - return value - } - return nil - } - - public static func getType(for name: String, with typeString: String) -> ModelProtocol.Type? { - return getCategory(for: typeString)?.instanceTypes[name] - } - public static func getCodingKey(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") @@ -77,32 +84,46 @@ extension KeyedDecodingContainer where Key: CodingKey { // MARK: - Decode /// Decodes to a registered model based on the identifier - public func decodeModel(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeKey) throws -> M { - - guard let model: ModelProtocol = try decodeModelIfPresent(codingKey: codingKey, typeCodingKey: typeCodingKey) else { + public func decodeModel(codingKey: KeyedDecodingContainer.Key) throws -> T { + guard let model: T = try decodeModelIfPresent(codingKey: codingKey) else { MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey)") throw ModelRegistry.Error.decoderErrorObjectNotPresent } - - if let model = model as? M { - return model - } else { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderError: \(codingKey)") - throw ModelRegistry.Error.decoderError + return model + } + + /// Decodes an array of registered model based on the identifiers. + public func decodeModels(codingKey: KeyedDecodingContainer.Key) throws -> [T] { + guard let model: [T] = try decodeModelsIfPresent(codingKey: codingKey) else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey)") + throw ModelRegistry.Error.decoderErrorObjectNotPresent } + return model + } + + /// Decodes an array with arrays of models based on the identifiers. + public func decodeModels2D(codingKey: KeyedDecodingContainer.Key) throws -> [[T]] { + guard let models: [[T]] = try decodeModels2DIfPresent(codingKey: codingKey) else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey)") + throw ModelRegistry.Error.decoderErrorObjectNotPresent + } + return models } // MARK: - DecodeIfPresent /// Decodes to a registered model based on the identifier, optional. - public func decodeModelIfPresent(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeKey) throws -> M? { - //get the identifier string - guard contains(codingKey), - let container = try? nestedContainer(keyedBy: TypeKey.self, forKey: codingKey), - let identifier = try? container.decodeIfPresent(String.self, forKey: typeCodingKey) - else { return nil } + public func decodeModelIfPresent(codingKey: KeyedDecodingContainer.Key) throws -> T? { + //create coding key + let typeCodingKey = try ModelRegistry.getCodingKey(for: T.self) - guard let type = ModelRegistry.getType(for: identifier, with: typeCodingKey.stringValue) else { + //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 } + + //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 } @@ -110,7 +131,7 @@ extension KeyedDecodingContainer where Key: CodingKey { //decode the type using the decoder let model = try type.decode(keyedContainer: self, codingKey: codingKey) - if let model = model as? M { + if let model = model as? T { return model } else { MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderError: \(codingKey)") @@ -119,39 +140,21 @@ extension KeyedDecodingContainer where Key: CodingKey { } /// Decodes an array of registered model based on the identifiers, optional. - public func decodeModelsIfPresent(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeKey) throws -> [ModelProtocol]? { + public func decodeModelsIfPresent(codingKey: KeyedDecodingContainer.Key) throws -> [T]? { guard contains(codingKey), var container = try? self.nestedUnkeyedContainer(forKey: codingKey) else { return nil } - return try container.decodeModelsIfPresent(typeCodingKey: typeCodingKey) - } - - /// Decodes an array of registered model based on the identifiers. - public func decodeModels(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeKey) throws -> [ModelProtocol] { - guard let models: [ModelProtocol] = try decodeModelsIfPresent(codingKey: codingKey, typeCodingKey: typeCodingKey) else { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey)") - throw ModelRegistry.Error.decoderErrorObjectNotPresent - } - return models + return try container.decodeModelsIfPresent() } /// Decodes an array with arrays of models based on the identifiers, optional. - public func decodeModels2DIfPresent(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeKey) throws -> [[ModelProtocol]]? { + public func decodeModels2DIfPresent(codingKey: KeyedDecodingContainer.Key) throws -> [[T]]? { guard contains(codingKey), var container = try? nestedUnkeyedContainer(forKey: codingKey) else { return nil } - return try container.decodeModels2DIfPresent(typeCodingKey: typeCodingKey) - } - - /// Decodes an array with arrays of models based on the identifiers. - public func decodeModels2D(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeKey) throws -> [[ModelProtocol]] { - guard let models: [[ModelProtocol]] = try decodeModels2DIfPresent(codingKey: codingKey, typeCodingKey: typeCodingKey) else { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorObjectNotPresent: \(codingKey)") - throw ModelRegistry.Error.decoderErrorObjectNotPresent - } - return models + return try container.decodeModels2DIfPresent() } } @@ -205,36 +208,42 @@ public extension KeyedEncodingContainer where Key: CodingKey { public extension UnkeyedDecodingContainer { /// Decodes the container into a list of Models. - mutating func decodeModelsIfPresent(typeCodingKey: TypeKey) throws -> [ModelProtocol]? { - - var models = [ModelProtocol]() + mutating func decodeModelsIfPresent() throws -> [T]? { + let typeCodingKey = try ModelRegistry.getCodingKey(for: T.self) + var models = [T]() var containerCopy = self + // Iterate and decode each. while !containerCopy.isAtEnd { - let nestedContainer = try containerCopy.nestedContainer(keyedBy: TypeKey.self) + let nestedContainer = try containerCopy.nestedContainer(keyedBy: AnyCodingKey.self) if let identifier = try nestedContainer.decodeIfPresent(String.self, forKey: typeCodingKey) { - guard let type = ModelRegistry.getType(for: identifier, with: typeCodingKey.stringValue) else { + guard let type = ModelRegistry.getType(for: identifier, with: T.self) else { MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorModelNotMapped: \(identifier)") throw ModelRegistry.Error.decoderErrorModelNotMapped } // Now get the decoder to use for the type let decoder = try self.superDecoder() - let model = try type.init(from: decoder) - models.append(model) + if let model = try type.init(from: decoder) as? T { + models.append(model) + } else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderError: \(typeCodingKey)") + throw ModelRegistry.Error.decoderError + } } } return models } /// Convenience function for decoding the container into a list of lists of Models. - mutating func decodeModels2DIfPresent(typeCodingKey: TypeKey) throws -> [[ModelProtocol]]? { - - var modelLists = [[ModelProtocol]]() + mutating func decodeModels2DIfPresent() throws -> [[T]]? { + let typeCodingKey = try ModelRegistry.getCodingKey(for: T.self) + var modelLists = [[T]]() var containerCopy = self + // Iterate and decode each. while !containerCopy.isAtEnd { var arraycontainerCopy = try containerCopy.nestedUnkeyedContainer() - guard let models = try arraycontainerCopy.decodeModelsIfPresent(typeCodingKey: typeCodingKey) else { + guard let models: [T] = try arraycontainerCopy.decodeModelsIfPresent() else { MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ModelRegistry Error decoderErrorModelNotMapped: \(typeCodingKey)") throw ModelRegistry.Error.decoderErrorModelNotMapped }