Skip to content

Commit bd838fc

Browse files
committed
Add ModelDataEncoder
1 parent 93120f7 commit bd838fc

1 file changed

Lines changed: 374 additions & 0 deletions

File tree

Sources/CoreModel/Encoder.swift

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
//
2+
// Encoder.swift
3+
//
4+
//
5+
// Created by Alsey Coleman Miller on 8/18/23.
6+
//
7+
8+
import Foundation
9+
10+
extension Entity where Self: Encodable, Self.ID: Encodable, Self.ID: ObjectIDConvertible {
11+
12+
public func encode() throws -> ModelData {
13+
try encode(log: nil)
14+
}
15+
16+
internal func encode(
17+
log: ((String) -> ())?,
18+
userInfo: [CodingUserInfoKey : Any] = [:]
19+
) throws -> ModelData {
20+
let entity = EntityDescription(entity: Self.self)
21+
let id = ObjectID(rawValue: self.id.description)
22+
let encoder = ModelDataEncoder(
23+
entity: entity,
24+
id: id,
25+
userInfo: userInfo,
26+
log: log
27+
)
28+
try self.encode(to: encoder)
29+
return encoder.data
30+
}
31+
}
32+
33+
internal final class ModelDataEncoder: Encoder {
34+
35+
fileprivate(set) var codingPath: [CodingKey]
36+
37+
let userInfo: [CodingUserInfoKey : Any]
38+
39+
fileprivate(set) var data: ModelData
40+
41+
fileprivate let log: ((String) -> ())?
42+
43+
let attributes: [PropertyKey: Attribute]
44+
45+
let relationships: [PropertyKey: Relationship]
46+
47+
init(
48+
entity: EntityDescription,
49+
id: ObjectID,
50+
codingPath: [CodingKey] = [],
51+
userInfo: [CodingUserInfoKey : Any] = [:],
52+
log: ((String) -> ())? = nil
53+
) {
54+
self.codingPath = codingPath
55+
self.userInfo = userInfo
56+
self.log = log
57+
self.data = ModelData(entity: entity.id, id: id)
58+
self.attributes = .init(grouping: entity.attributes, by: { $0.id.rawValue })
59+
self.relationships = .init(grouping: entity.relationships, by: { $0.id.rawValue })
60+
}
61+
62+
func container<Key>(keyedBy type: Key.Type) -> Swift.KeyedEncodingContainer<Key> where Key : CodingKey {
63+
log?("Requested container keyed by \(type.sanitizedName) for path \"\(codingPath.path)\"")
64+
let container = ModelKeyedEncodingContainer<Key>(referencing: self)
65+
return Swift.KeyedEncodingContainer<Key>(container)
66+
}
67+
68+
func unkeyedContainer() -> Swift.UnkeyedEncodingContainer {
69+
log?("Requested unkeyed container for path \"\(codingPath.path)\"")
70+
return ModelUnkeyedEncodingContainer(encoder: self)
71+
}
72+
73+
func singleValueContainer() -> Swift.SingleValueEncodingContainer {
74+
log?("Requested single value container for path \"\(codingPath.path)\"")
75+
assert(self.codingPath.last != nil)
76+
return ModelSingleValueEncodingContainer(encoder: self)
77+
}
78+
}
79+
80+
fileprivate extension ModelDataEncoder {
81+
82+
func setAttribute(_ value: AttributeValue, forKey key: PropertyKey) throws {
83+
log?("Will set \(value) for attribute \"\(key)\"")
84+
guard attributes.keys.contains(key) else {
85+
// TODO: Determine if blacklisted key (e.g. _id)
86+
//throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "No attribute found for \"\(key)\""))
87+
return
88+
}
89+
data.attributes[key] = value
90+
}
91+
92+
func setRelationship(_ value: RelationshipValue, forKey key: PropertyKey) throws {
93+
log?("Will set \(value) for relationship \"\(key)\"")
94+
guard relationships.keys.contains(key) else {
95+
//throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "No relationship found for \"\(key)\""))
96+
return
97+
}
98+
data.relationships[key] = value
99+
}
100+
101+
func setNil(for key: PropertyKey) throws {
102+
log?("Will set nil for \"\(key)\"")
103+
if attributes.keys.contains(key) {
104+
data.attributes[key] = .null
105+
} else if relationships.keys.contains(key) {
106+
data.relationships[key] = .null
107+
} else {
108+
return
109+
//throw EncodingError.invalidValue(Optional<Any>.self, EncodingError.Context(codingPath: codingPath, debugDescription: "No property found for \"\(key)\""))
110+
}
111+
}
112+
113+
func setEncodable <T: Encodable> (_ value: T, forKey key: PropertyKey) throws {
114+
115+
if let data = value as? Data {
116+
try setAttribute(data.attributeValue, forKey: key)
117+
} else if let date = value as? Date {
118+
try setAttribute(date.attributeValue, forKey: key)
119+
} else if let uuid = value as? UUID {
120+
try setAttribute(uuid.attributeValue, forKey: key)
121+
} else if let url = value as? URL {
122+
try setAttribute(url.attributeValue, forKey: key)
123+
} else {
124+
// encode using Encodable, container should write directly.
125+
try value.encode(to: self)
126+
}
127+
}
128+
129+
func setString(_ string: String, forKey key: PropertyKey) throws {
130+
log?("Will set \"\(string)\" for \"\(key)\"")
131+
if attributes.keys.contains(key) {
132+
data.attributes[key] = .string(string)
133+
} else if relationships.keys.contains(key) {
134+
data.relationships[key] = .toOne(ObjectID(rawValue: string))
135+
} else {
136+
return
137+
//throw EncodingError.invalidValue(Optional<Any>.self, EncodingError.Context(codingPath: codingPath, debugDescription: "No property found for \"\(key)\""))
138+
}
139+
}
140+
}
141+
142+
// MARK: - KeyedEncodingContainer
143+
144+
struct ModelKeyedEncodingContainer<K : CodingKey> : KeyedEncodingContainerProtocol {
145+
146+
public typealias Key = K
147+
148+
// MARK: Properties
149+
150+
/// A reference to the encoder we're writing to.
151+
let encoder: ModelDataEncoder
152+
153+
/// The path of coding keys taken to get to this point in encoding.
154+
let codingPath: [CodingKey]
155+
156+
// MARK: Initialization
157+
158+
init(referencing encoder: ModelDataEncoder) {
159+
self.encoder = encoder
160+
self.codingPath = encoder.codingPath
161+
}
162+
163+
// MARK: Methods
164+
165+
func encodeNil(forKey key: Key) throws {
166+
// set coding path
167+
self.encoder.codingPath.append(key)
168+
defer { self.encoder.codingPath.removeLast() }
169+
// set value
170+
try encoder.setNil(for: PropertyKey(key))
171+
}
172+
173+
func encode(_ value: String, forKey key: Self.Key) throws {
174+
// Determine if attribute or relationship
175+
// set coding path
176+
self.encoder.codingPath.append(key)
177+
defer { self.encoder.codingPath.removeLast() }
178+
// set value
179+
try encoder.setString(value, forKey: PropertyKey(key))
180+
}
181+
182+
func encode(_ value: Bool, forKey key: Key) throws {
183+
try writeAttribute(value, forKey: key)
184+
}
185+
186+
func encode(_ value: Double, forKey key: Self.Key) throws {
187+
try writeAttribute(value, forKey: key)
188+
}
189+
190+
func encode(_ value: Float, forKey key: Self.Key) throws {
191+
try writeAttribute(value, forKey: key)
192+
}
193+
194+
func encode(_ value: Int, forKey key: Self.Key) throws {
195+
try writeAttribute(value, forKey: key)
196+
}
197+
198+
func encode(_ value: Int8, forKey key: Self.Key) throws {
199+
try writeAttribute(value, forKey: key)
200+
}
201+
202+
func encode(_ value: Int16, forKey key: Self.Key) throws {
203+
try writeAttribute(value, forKey: key)
204+
}
205+
206+
func encode(_ value: Int32, forKey key: Self.Key) throws {
207+
try writeAttribute(value, forKey: key)
208+
}
209+
210+
func encode(_ value: Int64, forKey key: Self.Key) throws {
211+
try writeAttribute(value, forKey: key)
212+
}
213+
214+
func encode(_ value: UInt, forKey key: Self.Key) throws {
215+
try writeAttribute(value, forKey: key)
216+
}
217+
218+
func encode(_ value: UInt8, forKey key: Self.Key) throws {
219+
try writeAttribute(value, forKey: key)
220+
}
221+
222+
func encode(_ value: UInt16, forKey key: Self.Key) throws {
223+
try writeAttribute(value, forKey: key)
224+
}
225+
226+
func encode(_ value: UInt32, forKey key: Self.Key) throws {
227+
try writeAttribute(value, forKey: key)
228+
}
229+
230+
func encode(_ value: UInt64, forKey key: Self.Key) throws {
231+
try writeAttribute(value, forKey: key)
232+
}
233+
234+
func encode<T>(_ value: T, forKey key: Self.Key) throws where T : Encodable {
235+
236+
// set coding key context
237+
encoder.codingPath.append(key)
238+
defer { encoder.codingPath.removeLast() }
239+
240+
// set value
241+
try encoder.setEncodable(value, forKey: PropertyKey(key))
242+
}
243+
244+
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: K) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
245+
fatalError()
246+
}
247+
248+
func nestedUnkeyedContainer(forKey key: K) -> UnkeyedEncodingContainer {
249+
fatalError()
250+
}
251+
252+
func superEncoder() -> Encoder {
253+
fatalError()
254+
}
255+
256+
func superEncoder(forKey key: K) -> Encoder {
257+
fatalError()
258+
}
259+
260+
// MARK: Private Methods
261+
262+
private func writeAttribute<T: AttributeEncodable>(_ value: T, forKey key: Key) throws {
263+
264+
// set coding key context
265+
encoder.codingPath.append(key)
266+
defer { encoder.codingPath.removeLast() }
267+
268+
// set value
269+
try encoder.setAttribute(value.attributeValue, forKey: PropertyKey(key))
270+
}
271+
}
272+
273+
// MARK: - SingleValueEncodingContainer
274+
275+
internal final class ModelSingleValueEncodingContainer: SingleValueEncodingContainer {
276+
277+
// MARK: Properties
278+
279+
/// A reference to the encoder we're writing to.
280+
let encoder: ModelDataEncoder
281+
282+
/// The path of coding keys taken to get to this point in encoding.
283+
let codingPath: [CodingKey]
284+
285+
// MARK: Initialization
286+
287+
init(referencing encoder: ModelDataEncoder) {
288+
self.encoder = encoder
289+
self.codingPath = encoder.codingPath
290+
}
291+
292+
// MARK: - Methods
293+
294+
func encodeNil() throws {
295+
let key = try propertyKey()
296+
try encoder.setNil(for: key)
297+
}
298+
299+
func encode(_ value: String) throws {
300+
let key = try propertyKey()
301+
try encoder.setString(value, forKey: key)
302+
}
303+
304+
func encode(_ value: Bool) throws {
305+
try writeAttribute(value)
306+
}
307+
308+
func encode(_ value: Double) throws {
309+
try writeAttribute(value)
310+
}
311+
312+
func encode(_ value: Float) throws {
313+
try writeAttribute(value)
314+
}
315+
316+
func encode(_ value: Int) throws {
317+
try writeAttribute(value)
318+
}
319+
320+
func encode(_ value: Int8) throws {
321+
try writeAttribute(value)
322+
}
323+
324+
func encode(_ value: Int16) throws {
325+
try writeAttribute(value)
326+
}
327+
328+
func encode(_ value: Int32) throws {
329+
try writeAttribute(value)
330+
}
331+
332+
func encode(_ value: Int64) throws {
333+
try writeAttribute(value)
334+
}
335+
336+
func encode(_ value: UInt) throws {
337+
try writeAttribute(value)
338+
}
339+
340+
func encode(_ value: UInt8) throws{
341+
try writeAttribute(value)
342+
}
343+
344+
func encode(_ value: UInt16) throws{
345+
try writeAttribute(value)
346+
}
347+
348+
func encode(_ value: UInt32) throws{
349+
try writeAttribute(value)
350+
}
351+
352+
func encode(_ value: UInt64) throws {
353+
try writeAttribute(value)
354+
}
355+
356+
func encode <T: Encodable> (_ value: T) throws {
357+
let key = try self.propertyKey()
358+
try encoder.setEncodable(value, forKey: key)
359+
}
360+
361+
// MARK: Private Methods
362+
363+
private func propertyKey() throws -> PropertyKey {
364+
guard let key = codingPath.first else {
365+
throw EncodingError.invalidValue(Any.self, EncodingError.Context(codingPath: codingPath, debugDescription: "Invalid coding path"))
366+
}
367+
return PropertyKey(key)
368+
}
369+
370+
private func writeAttribute<T: AttributeEncodable>(_ value: T) throws {
371+
let key = try self.propertyKey()
372+
try encoder.setAttribute(value.attributeValue, forKey: key)
373+
}
374+
}

0 commit comments

Comments
 (0)