Skip to content

Commit 35deea5

Browse files
authored
Revise PostgresDataEncoder implementation to better handle various Codable conformances (#234)
Revise PostgresDataEncoder implementation to better handle various Codable conformances (no more fatalError()s!)
1 parent df9146a commit 35deea5

File tree

2 files changed

+161
-134
lines changed

2 files changed

+161
-134
lines changed

Sources/PostgresKit/PostgresDataEncoder.swift

Lines changed: 107 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -13,148 +13,122 @@ public final class PostgresDataEncoder {
1313
if let custom = value as? PostgresDataConvertible, let data = custom.postgresData {
1414
return data
1515
} else {
16-
let context = _Context()
17-
try value.encode(to: _Encoder(context: context))
18-
if let value = context.value {
19-
return value
20-
} else if let array = context.array {
21-
let elementType = array.first?.type ?? .jsonb
22-
assert(array.filter { $0.type != elementType }.isEmpty, "Array does not contain all: \(elementType)")
23-
return PostgresData(array: array, elementType: elementType)
24-
} else {
25-
return try PostgresData(jsonb: self.json.encode(_Wrapper(value)))
16+
let encoder = _Encoder(parent: self)
17+
do {
18+
try value.encode(to: encoder)
19+
switch encoder.value {
20+
case .invalid: throw _Encoder.AssociativeValueSentinel() // this is usually "nothing was encoded at all", not an associative value, but the desired action is the same
21+
case .scalar(let scalar): return scalar
22+
case .indexed(let indexed):
23+
let elementType = indexed.contents.first?.type ?? .jsonb
24+
assert(indexed.contents.allSatisfy { $0.type == elementType }, "Type \(type(of: value)) was encoded as a heterogenous array; this is unsupported.")
25+
return PostgresData(array: indexed.contents, elementType: elementType)
26+
}
27+
} catch is _Encoder.AssociativeValueSentinel {
28+
#if swift(<5.7)
29+
struct _Wrapper: Encodable {
30+
let encodable: Encodable
31+
init(_ encodable: Encodable) { self.encodable = encodable }
32+
func encode(to encoder: Encoder) throws { try self.encodable.encode(to: encoder) }
33+
}
34+
return try PostgresData(jsonb: self.json.encode(_Wrapper(value))) // Swift <5.7 will complain that "Encodable does not conform to Encodable" without the wrapper
35+
#else
36+
return try PostgresData(jsonb: self.json.encode(value))
37+
#endif
2638
}
2739
}
2840
}
2941

30-
final class _Context {
31-
var value: PostgresData?
32-
var array: [PostgresData]?
33-
34-
init() { }
35-
}
36-
37-
struct _Encoder: Encoder {
38-
var userInfo: [CodingUserInfoKey : Any] {
39-
[:]
40-
}
41-
var codingPath: [CodingKey] {
42-
[]
42+
private final class _Encoder: Encoder {
43+
struct AssociativeValueSentinel: Error {}
44+
enum Value {
45+
final class RefArray<T> { var contents: [T] = [] }
46+
case invalid, indexed(RefArray<PostgresData>), scalar(PostgresData)
47+
48+
var isValid: Bool { if case .invalid = self { return false }; return true }
49+
mutating func requestIndexed(for encoder: _Encoder) {
50+
switch self {
51+
case .scalar(_): preconditionFailure("Invalid request for both single-value and unkeyed containers from the same encoder.")
52+
case .invalid: self = .indexed(.init()) // no existing value, make new array
53+
case .indexed(_): break // existing array, adopt it for appending (support for superEncoder())
54+
}
55+
}
56+
mutating func storeScalar(_ scalar: PostgresData) {
57+
switch self {
58+
case .indexed(_), .scalar(_): preconditionFailure("Invalid request for multiple containers from the same encoder.")
59+
case .invalid: self = .scalar(scalar) // no existing value, store the incoming
60+
}
61+
}
62+
var indexedCount: Int {
63+
switch self {
64+
case .invalid, .scalar(_): preconditionFailure("Internal error in encoder (requested indexed count from non-indexed state)")
65+
case .indexed(let ref): return ref.contents.count
66+
}
67+
}
68+
mutating func addToIndexed(_ scalar: PostgresData) {
69+
switch self {
70+
case .invalid, .scalar(_): preconditionFailure("Internal error in encoder (attempted store to indexed in non-indexed state)")
71+
case .indexed(let ref): ref.contents.append(scalar)
72+
}
73+
}
4374
}
44-
let context: _Context
45-
46-
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key>
47-
where Key : CodingKey
48-
{
49-
.init(_KeyedEncoder<Key>())
75+
76+
var userInfo: [CodingUserInfoKey : Any] { [:] }; var codingPath: [CodingKey] { [] }
77+
var parent: PostgresDataEncoder, value: Value
78+
79+
init(parent: PostgresDataEncoder, value: Value = .invalid) { (self.parent, self.value) = (parent, value) }
80+
func container<K: CodingKey>(keyedBy: K.Type) -> KeyedEncodingContainer<K> {
81+
precondition(!self.value.isValid, "Requested multiple containers from the same encoder.")
82+
return .init(_FailingKeyedContainer())
5083
}
51-
5284
func unkeyedContainer() -> UnkeyedEncodingContainer {
53-
self.context.array = []
54-
return _UnkeyedEncoder(context: self.context)
85+
self.value.requestIndexed(for: self)
86+
return _UnkeyedValueContainer(encoder: self)
5587
}
56-
5788
func singleValueContainer() -> SingleValueEncodingContainer {
58-
_ValueEncoder(context: self.context)
59-
}
60-
}
61-
62-
struct _UnkeyedEncoder: UnkeyedEncodingContainer {
63-
var codingPath: [CodingKey] {
64-
[]
65-
}
66-
var count: Int {
67-
0
68-
}
69-
70-
var context: _Context
71-
72-
func encodeNil() throws {
73-
self.context.array!.append(.null)
74-
}
75-
76-
func encode<T>(_ value: T) throws where T : Encodable {
77-
try self.context.array!.append(PostgresDataEncoder().encode(value))
78-
}
79-
80-
func nestedContainer<NestedKey>(
81-
keyedBy keyType: NestedKey.Type
82-
) -> KeyedEncodingContainer<NestedKey>
83-
where NestedKey : CodingKey
84-
{
85-
fatalError()
86-
}
87-
88-
func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
89-
fatalError()
90-
}
91-
92-
func superEncoder() -> Encoder {
93-
fatalError()
94-
}
95-
}
96-
97-
struct _KeyedEncoder<Key>: KeyedEncodingContainerProtocol
98-
where Key: CodingKey
99-
{
100-
var codingPath: [CodingKey] {
101-
[]
102-
}
103-
104-
func encodeNil(forKey key: Key) throws {
105-
// do nothing
106-
}
107-
108-
func encode<T>(_ value: T, forKey key: Key) throws where T : Encodable {
109-
// do nothing
110-
}
111-
112-
func nestedContainer<NestedKey>(
113-
keyedBy keyType: NestedKey.Type,
114-
forKey key: Key
115-
) -> KeyedEncodingContainer<NestedKey>
116-
where NestedKey : CodingKey
117-
{
118-
fatalError()
119-
}
120-
121-
func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
122-
123-
fatalError()
124-
}
125-
126-
func superEncoder() -> Encoder {
127-
fatalError()
128-
}
129-
130-
func superEncoder(forKey key: Key) -> Encoder {
131-
fatalError()
132-
}
133-
}
134-
135-
136-
struct _ValueEncoder: SingleValueEncodingContainer {
137-
var codingPath: [CodingKey] {
138-
[]
139-
}
140-
let context: _Context
141-
142-
func encodeNil() throws {
143-
self.context.value = .null
144-
}
145-
146-
func encode<T>(_ value: T) throws where T : Encodable {
147-
self.context.value = try PostgresDataEncoder().encode(value)
148-
}
149-
}
150-
151-
struct _Wrapper: Encodable {
152-
let encodable: Encodable
153-
init(_ encodable: Encodable) {
154-
self.encodable = encodable
155-
}
156-
func encode(to encoder: Encoder) throws {
157-
try self.encodable.encode(to: encoder)
89+
precondition(!self.value.isValid, "Requested multiple containers from the same encoder.")
90+
return _SingleValueContainer(encoder: self)
91+
}
92+
93+
struct _UnkeyedValueContainer: UnkeyedEncodingContainer {
94+
let encoder: _Encoder; var codingPath: [CodingKey] { self.encoder.codingPath }
95+
var count: Int { self.encoder.value.indexedCount }
96+
mutating func encodeNil() throws { self.encoder.value.addToIndexed(.null) }
97+
mutating func encode<T: Encodable>(_ value: T) throws { self.encoder.value.addToIndexed(try self.encoder.parent.encode(value)) }
98+
mutating func nestedContainer<K: CodingKey>(keyedBy: K.Type) -> KeyedEncodingContainer<K> { self.superEncoder().container(keyedBy: K.self) }
99+
mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { self.superEncoder().unkeyedContainer() }
100+
mutating func superEncoder() -> Encoder { _Encoder(parent: self.encoder.parent, value: self.encoder.value) } // NOT the same as self.encoder
101+
}
102+
103+
struct _SingleValueContainer: SingleValueEncodingContainer {
104+
let encoder: _Encoder; var codingPath: [CodingKey] { self.encoder.codingPath }
105+
func encodeNil() throws { self.encoder.value.storeScalar(.null) }
106+
func encode<T: Encodable>(_ value: T) throws { self.encoder.value.storeScalar(try self.encoder.parent.encode(value)) }
107+
}
108+
109+
/// This pair of types is only necessary because we can't directly throw an error from various Encoder and
110+
/// encoding container methods. We define duplicate types rather than the old implementation's use of a
111+
/// no-action keyed container because it can save a significant amount of time otherwise spent uselessly calling
112+
/// nested methods in some cases.
113+
struct _TaintedEncoder: Encoder, UnkeyedEncodingContainer, SingleValueEncodingContainer {
114+
var userInfo: [CodingUserInfoKey : Any] { [:] }; var codingPath: [CodingKey] { [] }; var count: Int { 0 }
115+
func container<K: CodingKey>(keyedBy: K.Type) -> KeyedEncodingContainer<K> { .init(_FailingKeyedContainer()) }
116+
func nestedContainer<K: CodingKey>(keyedBy: K.Type) -> KeyedEncodingContainer<K> { .init(_FailingKeyedContainer()) }
117+
func unkeyedContainer() -> UnkeyedEncodingContainer { self }
118+
func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { self }
119+
func singleValueContainer() -> SingleValueEncodingContainer { self }
120+
func superEncoder() -> Encoder { self }
121+
func encodeNil() throws { throw AssociativeValueSentinel() }
122+
func encode<T: Encodable>(_: T) throws { throw AssociativeValueSentinel() }
123+
}
124+
struct _FailingKeyedContainer<K: CodingKey>: KeyedEncodingContainerProtocol {
125+
var codingPath: [CodingKey] { [] }
126+
func encodeNil(forKey: K) throws { throw AssociativeValueSentinel() }
127+
func encode<T: Encodable>(_: T, forKey: K) throws { throw AssociativeValueSentinel() }
128+
func nestedContainer<NK: CodingKey>(keyedBy: NK.Type, forKey: K) -> KeyedEncodingContainer<NK> { .init(_FailingKeyedContainer<NK>()) }
129+
func nestedUnkeyedContainer(forKey: K) -> UnkeyedEncodingContainer { _TaintedEncoder() }
130+
func superEncoder() -> Encoder { _TaintedEncoder() }
131+
func superEncoder(forKey: K) -> Encoder { _TaintedEncoder() }
158132
}
159133
}
160134
}

Tests/PostgresKitTests/PostgresKitTests.swift

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,61 @@ class PostgresKitTests: XCTestCase {
159159
defer { try! connection.close().wait() }
160160
try SQLBenchmarker(on: connection.sql()).testEnum()
161161
}
162+
163+
/// Tests dealing with encoding of values whose `encode(to:)` implementation calls one of the `superEncoder()`
164+
/// methods (most notably the implementation of `Codable` for Fluent's `Fields`, which we can't directly test
165+
/// at this layer).
166+
func testValuesThatUseSuperEncoder() throws {
167+
struct UnusualType: Codable {
168+
var prop1: String, prop2: [Bool], prop3: [[Bool]]
169+
170+
// This is intentionally contrived - Fluent's implementation does Codable this roundabout way as a
171+
// workaround for the interaction of property wrappers with optional properties; it serves no purpose
172+
// here other than to demonstrate that the encoder supports it.
173+
private enum CodingKeys: String, CodingKey { case prop1, prop2, prop3 }
174+
init(prop1: String, prop2: [Bool], prop3: [[Bool]]) { (self.prop1, self.prop2, self.prop3) = (prop1, prop2, prop3) }
175+
init(from decoder: Decoder) throws {
176+
let container = try decoder.container(keyedBy: CodingKeys.self)
177+
self.prop1 = try .init(from: container.superDecoder(forKey: .prop1))
178+
var acontainer = try container.nestedUnkeyedContainer(forKey: .prop2), ongoing: [Bool] = []
179+
while !acontainer.isAtEnd { ongoing.append(try Bool.init(from: acontainer.superDecoder())) }
180+
self.prop2 = ongoing
181+
var bcontainer = try container.nestedUnkeyedContainer(forKey: .prop3), bongoing: [[Bool]] = []
182+
while !bcontainer.isAtEnd {
183+
var ccontainer = try bcontainer.nestedUnkeyedContainer(), congoing: [Bool] = []
184+
while !ccontainer.isAtEnd { congoing.append(try Bool.init(from: ccontainer.superDecoder())) }
185+
bongoing.append(congoing)
186+
}
187+
self.prop3 = bongoing
188+
}
189+
func encode(to encoder: Encoder) throws {
190+
var container = encoder.container(keyedBy: CodingKeys.self)
191+
try self.prop1.encode(to: container.superEncoder(forKey: .prop1))
192+
var acontainer = container.nestedUnkeyedContainer(forKey: .prop2)
193+
for val in self.prop2 { try val.encode(to: acontainer.superEncoder()) }
194+
var bcontainer = container.nestedUnkeyedContainer(forKey: .prop3)
195+
for arr in self.prop3 {
196+
var ccontainer = bcontainer.nestedUnkeyedContainer()
197+
for val in arr { try val.encode(to: ccontainer.superEncoder()) }
198+
}
199+
}
200+
}
201+
202+
let instance = UnusualType(prop1: "hello", prop2: [true, false, false, true], prop3: [[true, true], [false], [true], []])
203+
let encoded1 = try PostgresDataEncoder().encode(instance)
204+
let encoded2 = try PostgresDataEncoder().encode([instance, instance])
205+
206+
XCTAssertEqual(encoded1.type, .jsonb)
207+
XCTAssertEqual(encoded2.type, .jsonbArray)
208+
209+
let decoded1 = try PostgresDataDecoder().decode(UnusualType.self, from: encoded1)
210+
let decoded2 = try PostgresDataDecoder().decode([UnusualType].self, from: encoded2)
211+
212+
XCTAssertEqual(decoded1.prop3, instance.prop3)
213+
XCTAssertEqual(decoded2.count, 2)
214+
}
162215

163-
var eventLoop: EventLoop { self.eventLoopGroup.next() }
216+
var eventLoop: EventLoop { self.eventLoopGroup.any() }
164217
var eventLoopGroup: EventLoopGroup!
165218

166219
override func setUpWithError() throws {

0 commit comments

Comments
 (0)