1 /** 2 Property wrapper for `Record`'s data members that takes part in the process of serialization to and deserialization from the dictionary. 3 */ 4 @propertyWrapper 5 public final class Field<Type>: AnyFieldInternal { 6 /** 7 The wrapped value. 8 */ 9 public var wrappedValue: Type 10 11 private let fieldType: AnyDynamicType = ~Type.self 12 13 /** 14 Field's key in the dictionary, which by default is a label of the wrapped property. 15 Sadly, property wrappers don't receive properties' label, so we must wait until it's assigned by `Record`. 16 */ 17 internal var key: String? { 18 return options.first { $0.rawValue == FieldOption.keyed("").rawValue }?.key 19 } 20 21 /** 22 Additional options of the field, such is if providing the value is required (`FieldOption.required`). 23 */ 24 internal var options: Set<FieldOption> = Set() 25 26 /** 27 Whether the generic field type accepts `nil` values. 28 */ 29 internal var isOptional: Bool { 30 return fieldType is DynamicOptionalType 31 } 32 33 internal var isRequired: Bool { 34 options.contains(.required) 35 } 36 37 /** 38 Initializes the field with given value and customized key. 39 */ 40 public init(wrappedValue: Type, _ options: FieldOption...) { 41 self.wrappedValue = wrappedValue 42 self.options = Set(options) 43 } 44 45 /** 46 Alternative default initializer implementation. It's not possible yet to pass an array 47 as variadic arguments, so we also need to pass an array as a single argument. 48 */ 49 public init(wrappedValue: Type, _ options: [FieldOption]) { 50 self.wrappedValue = wrappedValue 51 self.options = Set(options) 52 } 53 54 /** 55 A hacky way to accept optionals without explicit assignment to `nil`. Normally, we would have to do 56 `@Field var s: String? = nil` but this init with generic constraint allows us to just do `@Field var s: String?`. 57 */ 58 public init(wrappedValue: Type = nil) where Type: ExpressibleByNilLiteral { 59 self.wrappedValue = wrappedValue 60 } 61 62 public init(wrappedValue: Type = nil, _ options: FieldOption...) where Type: ExpressibleByNilLiteral { 63 self.wrappedValue = wrappedValue 64 self.options = Set(options) 65 } 66 67 /** 68 Returns wrapped value as `Any?` to conform to type-erased `AnyField` protocol. 69 */ getnull70 public func get() -> Any { 71 return wrappedValue 72 } 73 74 /** 75 Sets the wrapped value with a value of `Any` type. 76 */ setnull77 internal func set(_ newValue: Any?) throws { 78 if newValue == nil && (!isOptional || isRequired) { 79 throw FieldRequiredException(key!) 80 } 81 do { 82 if let value = try fieldType.cast(newValue) as? Type { 83 wrappedValue = value 84 } 85 } catch { 86 throw FieldInvalidTypeException((fieldKey: key!, value: newValue, desiredType: Type.self)).causedBy(error) 87 } 88 } 89 } 90 91 internal class FieldRequiredException: GenericException<String> { 92 override var reason: String { 93 "Value for field '\(param)' is required, got nil" 94 } 95 } 96 97 internal class FieldInvalidTypeException: GenericException<(fieldKey: String, value: Any?, desiredType: Any.Type)> { 98 override var reason: String { 99 let value = String(describing: param.value ?? "null") 100 let desiredType = String(describing: param.desiredType) 101 102 return "Cannot cast '\(value)' for field '\(param.fieldKey)' of type \(desiredType)" 103 } 104 } 105