diff --git a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift index 8f184d5a..a44d6e8d 100644 --- a/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift +++ b/MVMCoreUI/Atomic/Extensions/VDS-Enums+Codable.swift @@ -60,22 +60,254 @@ extension DecodableDefault { public typealias Surface = Wrapper } -extension VDS.TileContainer.BackgroundColor: Codable { - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case .custom(let value): - try container.encode(value) - default: - try container.encode(String(reflecting: self)) +//extension VDS.TileContainer.BackgroundColor: Codable { +// public func encode(to encoder: Encoder) throws { +// var container = encoder.singleValueContainer() +// switch self { +// case .custom(let value): +// try container.encode(value) +// default: +// try container.encode(String(reflecting: self)) +// } +// } +// +// // Init from decoder to handle the decoding based on the type +// public init(from decoder: Decoder) throws { +// let container = try decoder.singleValueContainer() +// let type = try container.decode(String.self) +// switch type { +// case "primary": +// self = .primary +// case "secondary": +// self = .secondary +// case "white": +// self = .white +// case "black": +// self = .black +// default: +// self = .custom(type) +// } +// } +//} + +//extension VDS.TileContainer.Padding: Codable { +// enum PaddingError: Error { +// case valueNotFound(type: String) +// } +// public func encode(to encoder: Encoder) throws { +// var container = encoder.singleValueContainer() +// switch self { +// case .custom(let value): +// try container.encode(value) +// default: +// try container.encode(String(reflecting: self)) +// } +// } +// +// // Init from decoder to handle the decoding based on the type +// public init(from decoder: Decoder) throws { +// let container = try decoder.singleValueContainer() +// do { +// let type = try container.decode(String.self) +// switch type { +// case "padding2X": +// self = .padding2X +// case "padding4X": +// self = .padding4X +// case "padding6X": +// self = .padding6X +// case "padding8X": +// self = .padding8X +// case "padding12X": +// self = .padding12X +// default: +// throw PaddingError.valueNotFound(type: type) +// } +// } catch PaddingError.valueNotFound(let type) { +// throw PaddingError.valueNotFound(type: type) +// } catch { +// let type = try container.decode(CGFloat.self) +// self = .custom(type) +// } +// } +//} + +public protocol CustomEnumCodable: Codable { + associatedtype CustomValueType: Codable + init(custom value: CustomValueType) + var customValue: CustomValueType? { get } + var stringValue: String { get } + init?(stringValue: String) +} + +extension CustomEnumCodable { + public var stringValue: String { + let mirror = Mirror(reflecting: self) + if mirror.children.isEmpty { + return String(describing: self) + } else { + for child in mirror.children { + if let label = child.label, label == "custom" { + return label + } + } + return "custom" } } - - // Init from decoder to handle the decoding based on the type + public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() - let type = try container.decode(String.self) - switch type { + if let customValueType = try? container.decode(CustomValueType.self) { + self.init(custom: customValueType) + } else { + let stringValue = try container.decode(String.self) + if let instance = Self(stringValue: stringValue) { + self = instance + } else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unknown value") + } + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + if let customValue { + try container.encode(customValue) + } else { + try container.encode(self.stringValue) + } + } +} + +//extension VDS.TileContainer.Padding: CustomEnumCodable { +// +// public var customValue: CGFloat? { +// if case .custom(let value) = self { value } else { nil } +// } +// +// public init(custom value: CGFloat) { +// self = .custom(value) +// } +// +// public init?(stringValue: String) { +// switch stringValue { +// case "padding2X": +// self = .padding2X +// case "padding4X": +// self = .padding4X +// case "padding6X": +// self = .padding6X +// case "padding8X": +// self = .padding8X +// case "padding12X": +// self = .padding12X +// default: +// return nil +// } +// } +//} +// +//extension VDS.TileContainer.BackgroundColor: CustomEnumCodable { +// public var customValue: String? { +// if case .custom(let value) = self { value } else { nil } +// } +// +// public init(custom value: String) { +// self = .custom(value) +// } +// +// public init?(stringValue: String) { +// switch stringValue { +// case "primary": +// self = .primary +// case "secondary": +// self = .secondary +// case "white": +// self = .white +// case "black": +// self = .black +// default: +// return nil +// } +// } +//} + +public protocol EnumCodable: Codable { + associatedtype AssociatedValueType: Codable + var typeName: String { get } + var associatedValue: AssociatedValueType? { get } + init?(typeName: String, associatedValue: AssociatedValueType?) +} + +extension EnumCodable { + public var typeName: String { + let mirror = Mirror(reflecting: self) + if mirror.children.isEmpty { + return String(describing: self) + } else { + return mirror.children.first?.label ?? "custom" + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: EnumCodableCodingKeys.self) + try container.encode(typeName, forKey: .type) + + if let value = associatedValue { + try container.encode(value, forKey: .value) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: EnumCodableCodingKeys.self) + let typeName = try container.decode(String.self, forKey: .type) + let value = try container.decodeIfPresent(AssociatedValueType.self, forKey: .value) + + if let instance = Self(typeName: typeName, associatedValue: value) { + self = instance + } else { + throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Cannot initialize \(Self.self) from typeName \(typeName)") + } + } +} + +enum EnumCodableCodingKeys: String, CodingKey { + case type + case value +} + +extension VDS.TileContainer.Padding: EnumCodable { + public var associatedValue: CGFloat? { + if case .custom(let value) = self { value } else { nil } + } + + public init?(typeName: String, associatedValue: CGFloat?) { + switch typeName { + case "padding2X": + self = .padding2X + case "padding4X": + self = .padding4X + case "padding6X": + self = .padding6X + case "padding8X": + self = .padding8X + case "padding12X": + self = .padding12X + case "custom": + guard let associatedValue else { return nil } + self = .custom(associatedValue) + default: return nil + } + } +} + +extension VDS.TileContainer.BackgroundColor: EnumCodable { + public var associatedValue: String? { + if case .custom(let value) = self { value } else { nil } + } + + public init?(typeName: String, associatedValue: String?) { + switch typeName { case "primary": self = .primary case "secondary": @@ -84,50 +316,10 @@ extension VDS.TileContainer.BackgroundColor: Codable { self = .white case "black": self = .black - default: - self = .custom(type) - } - } -} - -extension VDS.TileContainer.Padding: Codable { - enum PaddingError: Error { - case valueNotFound(type: String) - } - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case .custom(let value): - try container.encode(value) - default: - try container.encode(String(reflecting: self)) - } - } - - // Init from decoder to handle the decoding based on the type - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - do { - let type = try container.decode(String.self) - switch type { - case "padding2X": - self = .padding2X - case "padding4X": - self = .padding4X - case "padding6X": - self = .padding6X - case "padding8X": - self = .padding8X - case "padding12X": - self = .padding12X - default: - throw PaddingError.valueNotFound(type: type) - } - } catch PaddingError.valueNotFound(let type) { - throw PaddingError.valueNotFound(type: type) - } catch { - let type = try container.decode(CGFloat.self) - self = .custom(type) + case "custom": + guard let associatedValue else { return nil } + self = .custom(associatedValue) + default: return nil } } }