Created
November 28, 2021 16:14
-
-
Save MahdiBM/95284bf1000206e08c72f3e59e9b5e92 to your computer and use it in GitHub Desktop.
Fluent ComputedField
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Use Case: | |
@ComputedField(key: "someKey", onSet: { transfromValue($0) }) | |
var someVar: Foo | |
*/ | |
import FluentKit | |
extension Fields { | |
typealias ComputedField<Value> = ComputedFieldProperty<Self, Value> | |
where Value: Codable | |
} | |
// MARK: Type | |
@propertyWrapper | |
final class ComputedFieldProperty<Model, Value> | |
where Model: FluentKit.Fields, Value: Codable | |
{ | |
public let key: FieldKey | |
var outputValue: Value? | |
var inputValue: DatabaseQuery.Value? | |
let onSet: (Value) -> Value | |
public var projectedValue: ComputedFieldProperty<Model, Value> { | |
self | |
} | |
public var wrappedValue: Value { | |
get { | |
guard let value = self.value else { | |
fatalError("Cannot access field before it is initialized or fetched: \(self.key)") | |
} | |
return value | |
} | |
set { | |
self.value = onSet(newValue) | |
} | |
} | |
public init(key: String, onSet: @escaping (Value) -> (Value)) { | |
self.key = FieldKey(key) | |
self.onSet = onSet | |
} | |
public init<T>(key: T, onSet: @escaping (Value) -> (Value)) | |
where T: RawRepresentable, T.RawValue == String { | |
self.key = .init(stringLiteral: key.rawValue) | |
self.onSet = onSet | |
} | |
} | |
extension ComputedFieldProperty: CustomStringConvertible { | |
public var description: String { | |
"@\(Model.self).Field<\(Value.self)>(key: \(self.key))" | |
} | |
} | |
// MARK: Property | |
extension ComputedFieldProperty: AnyProperty { } | |
extension ComputedFieldProperty: Property { | |
public var value: Value? { | |
get { | |
if let value = self.inputValue { | |
switch value { | |
case .bind(let bind): | |
return bind as? Value | |
case .enumCase(let string): | |
return string as? Value | |
case .default: | |
fatalError("Cannot access default field for '\(Model.self).\(key)' before it is initialized or fetched") | |
default: | |
fatalError("Unexpected input value type for '\(Model.self).\(key)': \(value)") | |
} | |
} else if let value = self.outputValue { | |
return value | |
} else { | |
return nil | |
} | |
} | |
set { | |
self.inputValue = newValue.map { .bind($0) } | |
} | |
} | |
} | |
// MARK: Queryable | |
extension ComputedFieldProperty: AnyQueryableProperty { | |
public var path: [FieldKey] { | |
[self.key] | |
} | |
} | |
extension ComputedFieldProperty: QueryableProperty { } | |
// MARK: Database | |
extension ComputedFieldProperty: AnyDatabaseProperty { | |
public var keys: [FieldKey] { | |
[self.key] | |
} | |
public func input(to input: DatabaseInput) { | |
if let inputValue = self.inputValue { | |
input.set(inputValue, at: self.key) | |
} | |
} | |
public func output(from output: DatabaseOutput) throws { | |
if output.contains(self.key) { | |
self.inputValue = nil | |
do { | |
self.outputValue = try output.decode(self.key, as: Value.self) | |
} catch { | |
throw FluentError.invalidField( | |
name: self.key.description, | |
valueType: Value.self, | |
error: error | |
) | |
} | |
} | |
} | |
} | |
// MARK: Codable | |
extension ComputedFieldProperty: AnyCodableProperty { | |
public func encode(to encoder: Encoder) throws { | |
var container = encoder.singleValueContainer() | |
try container.encode(self.wrappedValue) | |
} | |
public func decode(from decoder: Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
if let valueType = Value.self as? FluentKit.AnyOptionalType.Type { | |
// Hacks for supporting optionals in @Field. | |
// Using @OptionalField is preferred moving forward. | |
if container.decodeNil() { | |
self.wrappedValue = (valueType.nil as! Value) | |
} else { | |
self.wrappedValue = try container.decode(Value.self) | |
} | |
} else { | |
self.wrappedValue = try container.decode(Value.self) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment