Skip to content

Commit ad19d4d

Browse files
authored
fix: fixed optional value decoding failure with HelperCoder when value doesn't exist (#35)
1 parent b615251 commit ad19d4d

File tree

7 files changed

+194
-74
lines changed

7 files changed

+194
-74
lines changed

.devcontainer/devcontainer.json

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
{
2-
"name": "Swift",
3-
"image": "swiftlang/swift:nightly-5.9-jammy",
2+
"name": "Swift 5.9-Ubuntu 22.04",
3+
"image": "swift:5.9-jammy",
44
"features": {
55
"ghcr.io/devcontainers/features/common-utils:2": {
66
"installZsh": "false",
7-
"username": "vscode",
8-
"userUid": "1000",
9-
"userGid": "1000",
107
"upgradePackages": "false"
118
},
129
"ghcr.io/devcontainers/features/git:1": {
@@ -19,15 +16,11 @@
1916
"--security-opt",
2017
"seccomp=unconfined"
2118
],
22-
// Configure tool-specific properties.
2319
"customizations": {
24-
// Configure properties specific to VS Code.
2520
"vscode": {
26-
// Set *default* container specific settings.json values on container create.
2721
"settings": {
2822
"lldb.library": "/usr/lib/liblldb.so"
2923
},
30-
// Add the IDs of extensions you want installed when the container is created.
3124
"extensions": [
3225
"sswg.swift-lang",
3326
"vadimcn.vscode-lldb",
@@ -36,12 +29,5 @@
3629
]
3730
}
3831
},
39-
// Use 'forwardPorts' to make a list of ports inside the container available locally.
40-
"forwardPorts": [
41-
8080
42-
],
43-
// Use 'postCreateCommand' to run commands after the container is created.
44-
"postCreateCommand": "swift --version",
45-
// Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
46-
"remoteUser": "vscode"
32+
"postCreateCommand": "swift --version"
4733
}

Sources/CodableMacroPlugin/Variables/HelperCodedVariable.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ struct HelperCodedVariable<Var: BasicCodingVariable>: ComposedVariable {
8484
"""
8585
}
8686
case .container(let container, let key):
87-
let decoder: TokenSyntax = "\(container)_\(name.raw)Decoder"
8887
return CodeBlockItemListSyntax {
89-
"let \(decoder) = try \(container).superDecoder(forKey: \(key))"
90-
"self.\(name) = try \(options.expr).\(method)(from: \(decoder))"
88+
"""
89+
self.\(name) = try \(options.expr).\(method)(from: \(container), forKey: \(key))
90+
"""
9191
}
9292
}
9393
}
@@ -121,10 +121,10 @@ struct HelperCodedVariable<Var: BasicCodingVariable>: ComposedVariable {
121121
"""
122122
}
123123
case .container(let container, let key):
124-
let encoder: TokenSyntax = "\(container)_\(name.raw)Encoder"
125124
return CodeBlockItemListSyntax {
126-
"let \(encoder) = \(container).superEncoder(forKey: \(key))"
127-
"try \(options.expr).\(method)(self.\(name), to: \(encoder))"
125+
"""
126+
try \(options.expr).\(method)(self.\(name), to: &\(container), atKey: \(key))
127+
"""
128128
}
129129
}
130130
}

Sources/MetaCodable/HelperCoders/HelperCoder.swift

Lines changed: 159 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,44 @@ public protocol HelperCoder {
3232
/// - Throws: If decoding fails due to corrupted or invalid data.
3333
func decodeIfPresent(from decoder: Decoder) throws -> Coded?
3434

35+
/// Decodes a value of the ``Coded`` type from the given `container`
36+
/// and specified `key`.
37+
///
38+
/// Uses ``decode(from:)`` implementation by default
39+
/// to get value from the `decoder` at the specified key.
40+
///
41+
/// - Parameters:
42+
/// - container: The container to read data from.
43+
/// - key: The key for the value decoded.
44+
///
45+
/// - Returns: A value of the ``Coded`` type.
46+
/// - Throws: If decoding fails due to corrupted or invalid data.
47+
func decode<DecodingContainer: KeyedDecodingContainerProtocol>(
48+
from container: DecodingContainer,
49+
forKey key: DecodingContainer.Key
50+
) throws -> Coded
51+
/// Decodes an optional value of the ``Coded`` type from
52+
/// the given `container` and specified `key`, if present.
53+
///
54+
/// Uses ``decodeIfPresent(from:)`` implementation by default
55+
/// to get value if any value exists at specified key,
56+
/// otherwise returns `nil` if any error thrown.
57+
///
58+
/// - Parameters:
59+
/// - container: The container to read data from.
60+
/// - key: The key for the value decoded.
61+
///
62+
/// - Returns: An optional value of the ``Coded`` type.
63+
/// - Throws: If decoding fails due to corrupted or invalid data.
64+
func decodeIfPresent<DecodingContainer: KeyedDecodingContainerProtocol>(
65+
from container: DecodingContainer,
66+
forKey key: DecodingContainer.Key
67+
) throws -> Coded?
68+
3569
/// Encodes given value of the ``Coded`` type to the provided `encoder`.
3670
///
37-
/// If the ``Coded`` value confirms to `Encodable`, then encoding is
38-
/// performed. Otherwise no data written to the encoder.
71+
/// By default, of the ``Coded`` value confirms to `Encodable`, then
72+
/// encoding is performed. Otherwise no data written to the encoder.
3973
///
4074
/// - Parameters:
4175
/// - value: The ``Coded`` value to encode.
@@ -46,15 +80,50 @@ public protocol HelperCoder {
4680
/// Encodes given optional value of the ``Coded`` type to the provided
4781
/// `encoder` if it is not `nil`.
4882
///
49-
/// If the ``Coded`` value confirms to `Encodable`, then encoding is
50-
/// performed. Otherwise no data written to the encoder.
83+
/// By default, of the ``Coded`` value confirms to `Encodable`, then
84+
/// encoding is performed. Otherwise no data written to the encoder.
5185
///
5286
/// - Parameters:
5387
/// - value: The optional ``Coded`` value to encode.
5488
/// - encoder: The encoder to write data to.
5589
///
5690
/// - Throws: If any values are invalid for the given encoder’s format.
5791
func encodeIfPresent(_ value: Coded?, to encoder: Encoder) throws
92+
93+
/// Encodes given value of the ``Coded`` type to the provided `container`
94+
/// at the specified `key`.
95+
///
96+
/// By default, of the ``Coded`` value confirms to `Encodable`, then
97+
/// encoding is performed. Otherwise no data written to the encoder.
98+
///
99+
/// - Parameters:
100+
/// - value: The ``Coded`` value to encode.
101+
/// - container: The container to write data to.
102+
/// - key: The key to write data at.
103+
///
104+
/// - Throws: If any values are invalid for the given encoder’s format.
105+
func encode<EncodingContainer: KeyedEncodingContainerProtocol>(
106+
_ value: Coded,
107+
to container: inout EncodingContainer,
108+
atKey key: EncodingContainer.Key
109+
) throws
110+
/// Encodes given optional value of the ``Coded`` type to the provided
111+
/// `container` at the specified `key`, if it is not `nil`.
112+
///
113+
/// By default, of the ``Coded`` value confirms to `Encodable`, then
114+
/// encoding is performed. Otherwise no data written to the encoder.
115+
///
116+
/// - Parameters:
117+
/// - value: The optional ``Coded`` value to encode.
118+
/// - container: The container to write data to.
119+
/// - key: The key to write data at.
120+
///
121+
/// - Throws: If any values are invalid for the given encoder’s format.
122+
func encodeIfPresent<EncodingContainer: KeyedEncodingContainerProtocol>(
123+
_ value: Coded?,
124+
to container: inout EncodingContainer,
125+
atKey key: EncodingContainer.Key
126+
) throws
58127
}
59128

60129
public extension HelperCoder {
@@ -73,6 +142,49 @@ public extension HelperCoder {
73142
return try? self.decode(from: decoder)
74143
}
75144

145+
/// Decodes a value of the ``HelperCoder/Coded`` type from the given
146+
/// `container` and specified `key`.
147+
///
148+
/// Uses ``decode(from:)`` implementation by default
149+
/// to get value from the `decoder` at the specified key.
150+
///
151+
/// - Parameters:
152+
/// - container: The container to read data from.
153+
/// - key: The key for the value decoded.
154+
///
155+
/// - Returns: A value of the ``HelperCoder/Coded`` type.
156+
/// - Throws: If decoding fails due to corrupted or invalid data.
157+
@inlinable
158+
func decode<DecodingContainer: KeyedDecodingContainerProtocol>(
159+
from container: DecodingContainer,
160+
forKey key: DecodingContainer.Key
161+
) throws -> Coded {
162+
return try self.decode(from: container.superDecoder(forKey: key))
163+
}
164+
165+
/// Decodes an optional value of the ``HelperCoder/Coded`` type from
166+
/// the given `container` and specified `key`, if present.
167+
///
168+
/// Uses ``decodeIfPresent(from:)`` implementation by default
169+
/// to get value if any value exists at specified key,
170+
/// otherwise returns `nil` if any error thrown.
171+
///
172+
/// - Parameters:
173+
/// - container: The container to read data from.
174+
/// - key: The key for the value decoded.
175+
///
176+
/// - Returns: An optional value of the ``HelperCoder/Coded`` type.
177+
/// - Throws: If decoding fails due to corrupted or invalid data.
178+
@inlinable
179+
func decodeIfPresent<DecodingContainer: KeyedDecodingContainerProtocol>(
180+
from container: DecodingContainer,
181+
forKey key: DecodingContainer.Key
182+
) throws -> Coded? {
183+
guard let decoder = try? container.superDecoder(forKey: key)
184+
else { return nil }
185+
return try self.decodeIfPresent(from: decoder)
186+
}
187+
76188
/// Encodes given value of the ``HelperCoder/Coded`` type
77189
/// to the provided `encoder`.
78190
///
@@ -105,6 +217,49 @@ public extension HelperCoder {
105217
guard let value else { return }
106218
try self.encode(value, to: encoder)
107219
}
220+
221+
/// Encodes given value of the ``Coded`` type to the provided `container`
222+
/// at the specified `key`.
223+
///
224+
/// By default, of the ``Coded`` value confirms to `Encodable`, then
225+
/// encoding is performed. Otherwise no data written to the encoder.
226+
///
227+
/// - Parameters:
228+
/// - value: The ``Coded`` value to encode.
229+
/// - container: The container to write data to.
230+
/// - key: The key to write data at.
231+
///
232+
/// - Throws: If any values are invalid for the given encoder’s format.
233+
@inlinable
234+
func encode<EncodingContainer: KeyedEncodingContainerProtocol>(
235+
_ value: Coded,
236+
to container: inout EncodingContainer,
237+
atKey key: EncodingContainer.Key
238+
) throws {
239+
try self.encode(value, to: container.superEncoder(forKey: key))
240+
}
241+
242+
/// Encodes given optional value of the ``Coded`` type to the provided
243+
/// `container` at the specified `key`, if it is not `nil`.
244+
///
245+
/// By default, of the ``Coded`` value confirms to `Encodable`, then
246+
/// encoding is performed. Otherwise no data written to the encoder.
247+
///
248+
/// - Parameters:
249+
/// - value: The optional ``Coded`` value to encode.
250+
/// - container: The container to write data to.
251+
/// - key: The key to write data at.
252+
///
253+
/// - Throws: If any values are invalid for the given encoder’s format.
254+
@inlinable
255+
func encodeIfPresent<EncodingContainer: KeyedEncodingContainerProtocol>(
256+
_ value: Coded?,
257+
to container: inout EncodingContainer,
258+
atKey key: EncodingContainer.Key
259+
) throws {
260+
guard let value else { return }
261+
try self.encode(value, to: &container, atKey: key)
262+
}
108263
}
109264

110265
public extension HelperCoder where Coded: Encodable {

Tests/MetaCodableTests/CodedAtTests.swift

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -419,16 +419,14 @@ final class CodedAtTests: XCTestCase {
419419
extension SomeCodable: Decodable {
420420
init(from decoder: Decoder) throws {
421421
let container = try decoder.container(keyedBy: CodingKeys.self)
422-
let container_valueDecoder = try container.superDecoder(forKey: CodingKeys.value)
423-
self.value = try LossySequenceCoder<[String]>().decode(from: container_valueDecoder)
422+
self.value = try LossySequenceCoder<[String]>().decode(from: container, forKey: CodingKeys.value)
424423
}
425424
}
426425
427426
extension SomeCodable: Encodable {
428427
func encode(to encoder: Encoder) throws {
429428
var container = encoder.container(keyedBy: CodingKeys.self)
430-
let container_valueEncoder = container.superEncoder(forKey: CodingKeys.value)
431-
try LossySequenceCoder<[String]>().encode(self.value, to: container_valueEncoder)
429+
try LossySequenceCoder<[String]>().encode(self.value, to: &container, atKey: CodingKeys.value)
432430
}
433431
}
434432
@@ -467,8 +465,7 @@ final class CodedAtTests: XCTestCase {
467465
init(from decoder: Decoder) throws {
468466
let container = try decoder.container(keyedBy: CodingKeys.self)
469467
do {
470-
let container_valueDecoder = try container.superDecoder(forKey: CodingKeys.value)
471-
self.value = try LossySequenceCoder<[String]>().decode(from: container_valueDecoder)
468+
self.value = try LossySequenceCoder<[String]>().decode(from: container, forKey: CodingKeys.value)
472469
} catch {
473470
self.value = ["some"]
474471
}
@@ -478,8 +475,7 @@ final class CodedAtTests: XCTestCase {
478475
extension SomeCodable: Encodable {
479476
func encode(to encoder: Encoder) throws {
480477
var container = encoder.container(keyedBy: CodingKeys.self)
481-
let container_valueEncoder = container.superEncoder(forKey: CodingKeys.value)
482-
try LossySequenceCoder<[String]>().encode(self.value, to: container_valueEncoder)
478+
try LossySequenceCoder<[String]>().encode(self.value, to: &container, atKey: CodingKeys.value)
483479
}
484480
}
485481
@@ -621,8 +617,7 @@ final class CodedAtTests: XCTestCase {
621617
let container = try decoder.container(keyedBy: CodingKeys.self)
622618
let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
623619
let nested_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
624-
let nested_deeply_container_valueDecoder = try nested_deeply_container.superDecoder(forKey: CodingKeys.value)
625-
self.value = try LossySequenceCoder<[String]>().decode(from: nested_deeply_container_valueDecoder)
620+
self.value = try LossySequenceCoder<[String]>().decode(from: nested_deeply_container, forKey: CodingKeys.value)
626621
}
627622
}
628623
@@ -631,8 +626,7 @@ final class CodedAtTests: XCTestCase {
631626
var container = encoder.container(keyedBy: CodingKeys.self)
632627
var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
633628
var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
634-
let nested_deeply_container_valueEncoder = nested_deeply_container.superEncoder(forKey: CodingKeys.value)
635-
try LossySequenceCoder<[String]>().encode(self.value, to: nested_deeply_container_valueEncoder)
629+
try LossySequenceCoder<[String]>().encode(self.value, to: &nested_deeply_container, atKey: CodingKeys.value)
636630
}
637631
}
638632
@@ -675,8 +669,7 @@ final class CodedAtTests: XCTestCase {
675669
let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
676670
let nested_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
677671
do {
678-
let nested_deeply_container_valueDecoder = try nested_deeply_container.superDecoder(forKey: CodingKeys.value)
679-
self.value = try LossySequenceCoder<[String]>().decode(from: nested_deeply_container_valueDecoder)
672+
self.value = try LossySequenceCoder<[String]>().decode(from: nested_deeply_container, forKey: CodingKeys.value)
680673
} catch {
681674
self.value = ["some"]
682675
}
@@ -688,8 +681,7 @@ final class CodedAtTests: XCTestCase {
688681
var container = encoder.container(keyedBy: CodingKeys.self)
689682
var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
690683
var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
691-
let nested_deeply_container_valueEncoder = nested_deeply_container.superEncoder(forKey: CodingKeys.value)
692-
try LossySequenceCoder<[String]>().encode(self.value, to: nested_deeply_container_valueEncoder)
684+
try LossySequenceCoder<[String]>().encode(self.value, to: &nested_deeply_container, atKey: CodingKeys.value)
693685
}
694686
}
695687

0 commit comments

Comments
 (0)