Skip to content

Commit

Permalink
fix: fixed optional value decoding failure with HelperCoder when va…
Browse files Browse the repository at this point in the history
…lue doesn't exist (#35)
  • Loading branch information
soumyamahunt committed Nov 6, 2023
1 parent b615251 commit ad19d4d
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 74 deletions.
20 changes: 3 additions & 17 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
{
"name": "Swift",
"image": "swiftlang/swift:nightly-5.9-jammy",
"name": "Swift 5.9-Ubuntu 22.04",
"image": "swift:5.9-jammy",
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": "false",
"username": "vscode",
"userUid": "1000",
"userGid": "1000",
"upgradePackages": "false"
},
"ghcr.io/devcontainers/features/git:1": {
Expand All @@ -19,15 +16,11 @@
"--security-opt",
"seccomp=unconfined"
],
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"lldb.library": "/usr/lib/liblldb.so"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"sswg.swift-lang",
"vadimcn.vscode-lldb",
Expand All @@ -36,12 +29,5 @@
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
8080
],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "swift --version",
// Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
"postCreateCommand": "swift --version"
}
12 changes: 6 additions & 6 deletions Sources/CodableMacroPlugin/Variables/HelperCodedVariable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ struct HelperCodedVariable<Var: BasicCodingVariable>: ComposedVariable {
"""
}
case .container(let container, let key):
let decoder: TokenSyntax = "\(container)_\(name.raw)Decoder"
return CodeBlockItemListSyntax {
"let \(decoder) = try \(container).superDecoder(forKey: \(key))"
"self.\(name) = try \(options.expr).\(method)(from: \(decoder))"
"""
self.\(name) = try \(options.expr).\(method)(from: \(container), forKey: \(key))
"""
}
}
}
Expand Down Expand Up @@ -121,10 +121,10 @@ struct HelperCodedVariable<Var: BasicCodingVariable>: ComposedVariable {
"""
}
case .container(let container, let key):
let encoder: TokenSyntax = "\(container)_\(name.raw)Encoder"
return CodeBlockItemListSyntax {
"let \(encoder) = \(container).superEncoder(forKey: \(key))"
"try \(options.expr).\(method)(self.\(name), to: \(encoder))"
"""
try \(options.expr).\(method)(self.\(name), to: &\(container), atKey: \(key))
"""
}
}
}
Expand Down
163 changes: 159 additions & 4 deletions Sources/MetaCodable/HelperCoders/HelperCoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,44 @@ public protocol HelperCoder {
/// - Throws: If decoding fails due to corrupted or invalid data.
func decodeIfPresent(from decoder: Decoder) throws -> Coded?

/// Decodes a value of the ``Coded`` type from the given `container`
/// and specified `key`.
///
/// Uses ``decode(from:)`` implementation by default
/// to get value from the `decoder` at the specified key.
///
/// - Parameters:
/// - container: The container to read data from.
/// - key: The key for the value decoded.
///
/// - Returns: A value of the ``Coded`` type.
/// - Throws: If decoding fails due to corrupted or invalid data.
func decode<DecodingContainer: KeyedDecodingContainerProtocol>(
from container: DecodingContainer,
forKey key: DecodingContainer.Key
) throws -> Coded
/// Decodes an optional value of the ``Coded`` type from
/// the given `container` and specified `key`, if present.
///
/// Uses ``decodeIfPresent(from:)`` implementation by default
/// to get value if any value exists at specified key,
/// otherwise returns `nil` if any error thrown.
///
/// - Parameters:
/// - container: The container to read data from.
/// - key: The key for the value decoded.
///
/// - Returns: An optional value of the ``Coded`` type.
/// - Throws: If decoding fails due to corrupted or invalid data.
func decodeIfPresent<DecodingContainer: KeyedDecodingContainerProtocol>(
from container: DecodingContainer,
forKey key: DecodingContainer.Key
) throws -> Coded?

/// Encodes given value of the ``Coded`` type to the provided `encoder`.
///
/// If the ``Coded`` value confirms to `Encodable`, then encoding is
/// performed. Otherwise no data written to the encoder.
/// By default, of the ``Coded`` value confirms to `Encodable`, then
/// encoding is performed. Otherwise no data written to the encoder.
///
/// - Parameters:
/// - value: The ``Coded`` value to encode.
Expand All @@ -46,15 +80,50 @@ public protocol HelperCoder {
/// Encodes given optional value of the ``Coded`` type to the provided
/// `encoder` if it is not `nil`.
///
/// If the ``Coded`` value confirms to `Encodable`, then encoding is
/// performed. Otherwise no data written to the encoder.
/// By default, of the ``Coded`` value confirms to `Encodable`, then
/// encoding is performed. Otherwise no data written to the encoder.
///
/// - Parameters:
/// - value: The optional ``Coded`` value to encode.
/// - encoder: The encoder to write data to.
///
/// - Throws: If any values are invalid for the given encoder’s format.
func encodeIfPresent(_ value: Coded?, to encoder: Encoder) throws

/// Encodes given value of the ``Coded`` type to the provided `container`
/// at the specified `key`.
///
/// By default, of the ``Coded`` value confirms to `Encodable`, then
/// encoding is performed. Otherwise no data written to the encoder.
///
/// - Parameters:
/// - value: The ``Coded`` value to encode.
/// - container: The container to write data to.
/// - key: The key to write data at.
///
/// - Throws: If any values are invalid for the given encoder’s format.
func encode<EncodingContainer: KeyedEncodingContainerProtocol>(
_ value: Coded,
to container: inout EncodingContainer,
atKey key: EncodingContainer.Key
) throws
/// Encodes given optional value of the ``Coded`` type to the provided
/// `container` at the specified `key`, if it is not `nil`.
///
/// By default, of the ``Coded`` value confirms to `Encodable`, then
/// encoding is performed. Otherwise no data written to the encoder.
///
/// - Parameters:
/// - value: The optional ``Coded`` value to encode.
/// - container: The container to write data to.
/// - key: The key to write data at.
///
/// - Throws: If any values are invalid for the given encoder’s format.
func encodeIfPresent<EncodingContainer: KeyedEncodingContainerProtocol>(
_ value: Coded?,
to container: inout EncodingContainer,
atKey key: EncodingContainer.Key
) throws
}

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

/// Decodes a value of the ``HelperCoder/Coded`` type from the given
/// `container` and specified `key`.
///
/// Uses ``decode(from:)`` implementation by default
/// to get value from the `decoder` at the specified key.
///
/// - Parameters:
/// - container: The container to read data from.
/// - key: The key for the value decoded.
///
/// - Returns: A value of the ``HelperCoder/Coded`` type.
/// - Throws: If decoding fails due to corrupted or invalid data.
@inlinable
func decode<DecodingContainer: KeyedDecodingContainerProtocol>(
from container: DecodingContainer,
forKey key: DecodingContainer.Key
) throws -> Coded {
return try self.decode(from: container.superDecoder(forKey: key))
}

/// Decodes an optional value of the ``HelperCoder/Coded`` type from
/// the given `container` and specified `key`, if present.
///
/// Uses ``decodeIfPresent(from:)`` implementation by default
/// to get value if any value exists at specified key,
/// otherwise returns `nil` if any error thrown.
///
/// - Parameters:
/// - container: The container to read data from.
/// - key: The key for the value decoded.
///
/// - Returns: An optional value of the ``HelperCoder/Coded`` type.
/// - Throws: If decoding fails due to corrupted or invalid data.
@inlinable
func decodeIfPresent<DecodingContainer: KeyedDecodingContainerProtocol>(
from container: DecodingContainer,
forKey key: DecodingContainer.Key
) throws -> Coded? {
guard let decoder = try? container.superDecoder(forKey: key)
else { return nil }
return try self.decodeIfPresent(from: decoder)
}

/// Encodes given value of the ``HelperCoder/Coded`` type
/// to the provided `encoder`.
///
Expand Down Expand Up @@ -105,6 +217,49 @@ public extension HelperCoder {
guard let value else { return }
try self.encode(value, to: encoder)
}

/// Encodes given value of the ``Coded`` type to the provided `container`
/// at the specified `key`.
///
/// By default, of the ``Coded`` value confirms to `Encodable`, then
/// encoding is performed. Otherwise no data written to the encoder.
///
/// - Parameters:
/// - value: The ``Coded`` value to encode.
/// - container: The container to write data to.
/// - key: The key to write data at.
///
/// - Throws: If any values are invalid for the given encoder’s format.
@inlinable
func encode<EncodingContainer: KeyedEncodingContainerProtocol>(
_ value: Coded,
to container: inout EncodingContainer,
atKey key: EncodingContainer.Key
) throws {
try self.encode(value, to: container.superEncoder(forKey: key))
}

/// Encodes given optional value of the ``Coded`` type to the provided
/// `container` at the specified `key`, if it is not `nil`.
///
/// By default, of the ``Coded`` value confirms to `Encodable`, then
/// encoding is performed. Otherwise no data written to the encoder.
///
/// - Parameters:
/// - value: The optional ``Coded`` value to encode.
/// - container: The container to write data to.
/// - key: The key to write data at.
///
/// - Throws: If any values are invalid for the given encoder’s format.
@inlinable
func encodeIfPresent<EncodingContainer: KeyedEncodingContainerProtocol>(
_ value: Coded?,
to container: inout EncodingContainer,
atKey key: EncodingContainer.Key
) throws {
guard let value else { return }
try self.encode(value, to: &container, atKey: key)
}
}

public extension HelperCoder where Coded: Encodable {
Expand Down
24 changes: 8 additions & 16 deletions Tests/MetaCodableTests/CodedAtTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -419,16 +419,14 @@ final class CodedAtTests: XCTestCase {
extension SomeCodable: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let container_valueDecoder = try container.superDecoder(forKey: CodingKeys.value)
self.value = try LossySequenceCoder<[String]>().decode(from: container_valueDecoder)
self.value = try LossySequenceCoder<[String]>().decode(from: container, forKey: CodingKeys.value)
}
}
extension SomeCodable: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let container_valueEncoder = container.superEncoder(forKey: CodingKeys.value)
try LossySequenceCoder<[String]>().encode(self.value, to: container_valueEncoder)
try LossySequenceCoder<[String]>().encode(self.value, to: &container, atKey: CodingKeys.value)
}
}
Expand Down Expand Up @@ -467,8 +465,7 @@ final class CodedAtTests: XCTestCase {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
let container_valueDecoder = try container.superDecoder(forKey: CodingKeys.value)
self.value = try LossySequenceCoder<[String]>().decode(from: container_valueDecoder)
self.value = try LossySequenceCoder<[String]>().decode(from: container, forKey: CodingKeys.value)
} catch {
self.value = ["some"]
}
Expand All @@ -478,8 +475,7 @@ final class CodedAtTests: XCTestCase {
extension SomeCodable: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let container_valueEncoder = container.superEncoder(forKey: CodingKeys.value)
try LossySequenceCoder<[String]>().encode(self.value, to: container_valueEncoder)
try LossySequenceCoder<[String]>().encode(self.value, to: &container, atKey: CodingKeys.value)
}
}
Expand Down Expand Up @@ -621,8 +617,7 @@ final class CodedAtTests: XCTestCase {
let container = try decoder.container(keyedBy: CodingKeys.self)
let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
let nested_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
let nested_deeply_container_valueDecoder = try nested_deeply_container.superDecoder(forKey: CodingKeys.value)
self.value = try LossySequenceCoder<[String]>().decode(from: nested_deeply_container_valueDecoder)
self.value = try LossySequenceCoder<[String]>().decode(from: nested_deeply_container, forKey: CodingKeys.value)
}
}
Expand All @@ -631,8 +626,7 @@ final class CodedAtTests: XCTestCase {
var container = encoder.container(keyedBy: CodingKeys.self)
var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
let nested_deeply_container_valueEncoder = nested_deeply_container.superEncoder(forKey: CodingKeys.value)
try LossySequenceCoder<[String]>().encode(self.value, to: nested_deeply_container_valueEncoder)
try LossySequenceCoder<[String]>().encode(self.value, to: &nested_deeply_container, atKey: CodingKeys.value)
}
}
Expand Down Expand Up @@ -675,8 +669,7 @@ final class CodedAtTests: XCTestCase {
let deeply_container = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
let nested_deeply_container = try deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
do {
let nested_deeply_container_valueDecoder = try nested_deeply_container.superDecoder(forKey: CodingKeys.value)
self.value = try LossySequenceCoder<[String]>().decode(from: nested_deeply_container_valueDecoder)
self.value = try LossySequenceCoder<[String]>().decode(from: nested_deeply_container, forKey: CodingKeys.value)
} catch {
self.value = ["some"]
}
Expand All @@ -688,8 +681,7 @@ final class CodedAtTests: XCTestCase {
var container = encoder.container(keyedBy: CodingKeys.self)
var deeply_container = container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.deeply)
var nested_deeply_container = deeply_container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.nested)
let nested_deeply_container_valueEncoder = nested_deeply_container.superEncoder(forKey: CodingKeys.value)
try LossySequenceCoder<[String]>().encode(self.value, to: nested_deeply_container_valueEncoder)
try LossySequenceCoder<[String]>().encode(self.value, to: &nested_deeply_container, atKey: CodingKeys.value)
}
}
Expand Down

0 comments on commit ad19d4d

Please sign in to comment.