diff --git a/Sources/FoundationEssentials/CodableWithConfiguration.swift b/Sources/FoundationEssentials/CodableWithConfiguration.swift index 4fcbe79c1..c4cb27e73 100644 --- a/Sources/FoundationEssentials/CodableWithConfiguration.swift +++ b/Sources/FoundationEssentials/CodableWithConfiguration.swift @@ -53,14 +53,9 @@ public extension KeyedEncodingContainer { @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) public extension KeyedDecodingContainer { func decode(_: CodableConfiguration.Type, forKey key: Self.Key) throws -> CodableConfiguration { - if self.contains(key) { - let wrapper = try self.decode(CodableConfiguration.self, forKey: key) - return CodableConfiguration(wrappedValue: wrapper.wrappedValue) - } else { - return CodableConfiguration(wrappedValue: nil) - } + let wrapper = try self.decodeIfPresent(CodableConfiguration.self, forKey: key) + return CodableConfiguration(wrappedValue: wrapper?.wrappedValue) } - } diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTestSupport.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTestSupport.swift index 3935af2e5..dea8102f2 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTestSupport.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTestSupport.swift @@ -112,6 +112,10 @@ extension AttributeScopes.TestAttributes { return NonCodableType(inner: inner) } } + + struct NonCodableType : Hashable { + var inner : Int + } } #if FOUNDATION_FRAMEWORK @@ -121,10 +125,6 @@ extension AttributeScopes.TestAttributes.TestBoolAttribute : MarkdownDecodableAt extension AttributeScopes.TestAttributes.TestDoubleAttribute : MarkdownDecodableAttributedStringKey {} #endif // FOUNDATION_FRAMEWORK -struct NonCodableType : Hashable { - var inner : Int -} - extension AttributeScopes { var test: TestAttributes.Type { TestAttributes.self } diff --git a/Tests/FoundationEssentialsTests/CodableConfiguration/CodableConfigurationTests.swift b/Tests/FoundationEssentialsTests/CodableConfiguration/CodableConfigurationTests.swift new file mode 100644 index 000000000..ded94ee38 --- /dev/null +++ b/Tests/FoundationEssentialsTests/CodableConfiguration/CodableConfigurationTests.swift @@ -0,0 +1,82 @@ +import Testing + +#if canImport(FoundationEssentials) +@testable import FoundationEssentials +#else +@testable import Foundation +#endif + +@Suite("CodableConfiguration") +struct CodableConfigurationTests { + + @Test + func decodingIndirectly_succeedsForNull() async throws { + let json = "{\"testObject\":null}" + let jsonData = try #require(json.data(using: .utf8)) + + let decoder = JSONDecoder() + let sut = try decoder.decode(UsingCodableConfiguration.self, from: jsonData) + #expect(sut.testObject == nil) + } + + @Test + func decodingIndirectly_succeedsForCorrectDate() async throws { + let json = "{\"testObject\":\"Hello There\"}" + let jsonData = try #require(json.data(using: .utf8)) + + let decoder = JSONDecoder() + let sut = try decoder.decode(UsingCodableConfiguration.self, from: jsonData) + #expect(sut.testObject?.value == "Hello There") + } + + @Test + func decodingIndirectly_succeedsForMissingKey() async throws { + let json = "{}" + let jsonData = try #require(json.data(using: .utf8)) + + let decoder = JSONDecoder() + let sut = try decoder.decode(UsingCodableConfiguration.self, from: jsonData) + #expect(sut.testObject == nil) + } +} + +private extension CodableConfigurationTests { + struct NonCodableType { + let value: String + } + + /// Type that uses CodableConfiguration for decoding Optional NonCodableType + struct UsingCodableConfiguration: Codable { + @CodableConfiguration(wrappedValue: nil, from: CustomConfig.self) + var testObject: NonCodableType? + } + + /// Helper object allowing to decode Date using ISO8601 scheme + struct CustomConfig: DecodingConfigurationProviding, EncodingConfigurationProviding, Sendable { + static let encodingConfiguration = CustomConfig() + static let decodingConfiguration = CustomConfig() + + func decode(from decoder: any Decoder) throws -> NonCodableType { + let container = try decoder.singleValueContainer() + let value = try container.decode(String.self) + return NonCodableType(value: value) + } + + func encode(object: NonCodableType, to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(object.value) + } + } +} + +extension CodableConfigurationTests.NonCodableType: CodableWithConfiguration { + typealias CustomConfig = CodableConfigurationTests.CustomConfig + + public init(from decoder: any Decoder, configuration: CustomConfig) throws { + self = try configuration.decode(from: decoder) + } + + public func encode(to encoder: any Encoder, configuration: CustomConfig) throws { + try configuration.encode(object: self, to: encoder) + } +}