Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
import Foundation
struct Partial<Wrapped>: CustomStringConvertible, CustomDebugStringConvertible {
enum Error: Swift.Error {
case missingKey(PartialKeyPath<Wrapped>)
case invalidValueType(key: PartialKeyPath<Wrapped>, actualValue: Any)
}
private var values: [PartialKeyPath<Wrapped>: Any] = [:]
private var backingValue: Wrapped? = nil
var description: String {
let backingValueDescription: String
if let backingValue = backingValue as? CustomStringConvertible {
backingValueDescription = backingValue.description
} else {
backingValueDescription = String(describing: backingValue)
}
return "<\(type(of: self)) values=\(values.description); backingValue=\(backingValueDescription)>"
}
var debugDescription: String {
if let backingValue = backingValue {
return debugDescription(utilising: backingValue)
} else {
return "<\(type(of: self)) values=\(values.debugDescription); backingValue=\(backingValue.debugDescription))>"
}
}
init(backingValue: Wrapped? = nil) {
self.backingValue = backingValue
}
func value<ValueType>(for key: KeyPath<Wrapped, ValueType>) throws -> ValueType {
if let value = values[key] {
if let value = value as? ValueType {
return value
} else {
throw Error.invalidValueType(key: key, actualValue: value)
}
} else if let value = backingValue?[keyPath: key] {
return value
} else {
throw Error.missingKey(key)
}
}
func value<ValueType>(for key: KeyPath<Wrapped, ValueType?>) throws -> ValueType {
if let value = values[key] {
if let value = value as? ValueType {
return value
} else {
throw Error.invalidValueType(key: key, actualValue: value)
}
} else if let value = backingValue?[keyPath: key] {
return value
} else {
throw Error.missingKey(key)
}
}
func value<ValueType>(for key: KeyPath<Wrapped, ValueType>) throws -> ValueType where ValueType: PartialConvertible {
if let value = values[key] {
if let value = value as? ValueType {
return value
} else if let partial = value as? Partial<ValueType> {
return try ValueType(partial: partial)
} else {
throw Error.invalidValueType(key: key, actualValue: value)
}
} else if let value = backingValue?[keyPath: key] {
return value
} else {
throw Error.missingKey(key)
}
}
func value<ValueType>(for key: KeyPath<Wrapped, ValueType?>) throws -> ValueType where ValueType: PartialConvertible {
if let value = values[key] {
if let value = value as? ValueType {
return value
} else if let partial = value as? Partial<ValueType> {
return try ValueType(partial: partial)
} else {
throw Error.invalidValueType(key: key, actualValue: value)
}
} else if let value = backingValue?[keyPath: key] {
return value
} else {
throw Error.missingKey(key)
}
}
subscript<ValueType>(key: KeyPath<Wrapped, ValueType>) -> ValueType? {
get {
return try? value(for: key)
}
set {
values[key] = newValue
}
}
subscript<ValueType>(key: KeyPath<Wrapped, ValueType?>) -> ValueType? {
get {
return try? value(for: key)
}
set {
values[key] = newValue
}
}
subscript<ValueType>(key: KeyPath<Wrapped, ValueType>) -> Partial<ValueType> where ValueType: PartialConvertible {
get {
if let value = try? self.value(for: key) {
return Partial<ValueType>(backingValue: value)
} else if let partial = values[key] as? Partial<ValueType> {
return partial
} else {
return Partial<ValueType>()
}
}
set {
values[key] = newValue
}
}
subscript<ValueType>(key: KeyPath<Wrapped, ValueType?>) -> Partial<ValueType> where ValueType: PartialConvertible {
get {
if let value = try? self.value(for: key) {
return Partial<ValueType>(backingValue: value)
} else if let partial = values[key] as? Partial<ValueType> {
return partial
} else {
return Partial<ValueType>()
}
}
set {
values[key] = newValue
}
}
}
extension Partial {
func debugDescription(utilising instance: Wrapped) -> String {
var namedValues: [String: Any] = [:]
var unnamedValues: [PartialKeyPath<Wrapped>: Any] = [:]
let mirror = Mirror(reflecting: instance)
for (key, value) in self.values {
var foundKey = false
for child in mirror.children {
if let propertyName = child.label {
foundKey = (value as AnyObject) === (child.value as AnyObject)
if foundKey {
namedValues[propertyName] = value
break
}
}
}
if !foundKey {
unnamedValues[key] = value
}
}
return "<\(type(of: self)) values=\(namedValues.debugDescription), \(unnamedValues.debugDescription); backingValue=\(backingValue.debugDescription))>"
}
}
extension Partial where Wrapped: PartialConvertible {
var debugDescription: String {
if let instance = try? Wrapped(partial: self) {
return debugDescription(utilising: instance)
} else {
return "<\(type(of: self)) values=\(values.debugDescription); backingValue=\(backingValue.debugDescription))>"
}
}
}
protocol PartialConvertible {
init(partial: Partial<Self>) throws
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment