diff --git a/Sources/KeyValueDecoder.swift b/Sources/KeyValueDecoder.swift index 119fe9d..5fde717 100644 --- a/Sources/KeyValueDecoder.swift +++ b/Sources/KeyValueDecoder.swift @@ -33,7 +33,7 @@ import CoreFoundation import Foundation /// Top level encoder that converts `[String: Any]`, `[Any]` or `Any` into `Codable` types. -public final class KeyValueDecoder { +public struct KeyValueDecoder: Sendable { /// Contextual user-provided information for use during encoding. public var userInfo: [CodingUserInfoKey: any Sendable] @@ -94,7 +94,7 @@ extension KeyValueDecoder: TopLevelDecoder { extension KeyValueDecoder { static func makePlistCompatible() -> KeyValueDecoder { - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.nilDecodingStrategy = .stringNull return decoder } diff --git a/Sources/KeyValueEncoder.swift b/Sources/KeyValueEncoder.swift index a9a5454..23f35d5 100644 --- a/Sources/KeyValueEncoder.swift +++ b/Sources/KeyValueEncoder.swift @@ -32,7 +32,7 @@ import Foundation /// Top level encoder that converts `Codable` instances into loosely typed `[String: Any]`, `[Any]` or `Any`. -public final class KeyValueEncoder { +public struct KeyValueEncoder: Sendable { /// Contextual user-provided information for use during encoding. public var userInfo: [CodingUserInfoKey: any Sendable] @@ -85,7 +85,7 @@ extension KeyValueEncoder: TopLevelEncoder { extension KeyValueEncoder { static func makePlistCompatible() -> KeyValueEncoder { - let encoder = KeyValueEncoder() + var encoder = KeyValueEncoder() encoder.nilEncodingStrategy = .stringNull return encoder } diff --git a/Tests/KeyValueDecoderTests.swift b/Tests/KeyValueDecoderTests.swift index 646665c..9f1bd7d 100644 --- a/Tests/KeyValueDecoderTests.swift +++ b/Tests/KeyValueDecoderTests.swift @@ -146,7 +146,7 @@ struct KeyValueDecoderTests { @Test func decodesRounded_Ints() throws { - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.intDecodingStrategy = .rounding(rule: .toNearestOrAwayFromZero) #expect( @@ -235,7 +235,7 @@ struct KeyValueDecoderTests { @Test func decodesRounded_UInts() throws { - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.intDecodingStrategy = .rounding(rule: .toNearestOrAwayFromZero) #expect( @@ -422,7 +422,7 @@ struct KeyValueDecoderTests { @Test func decodes_Null() throws { - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.nilDecodingStrategy = .default #expect(throws: (any Error).self) { @@ -461,7 +461,7 @@ struct KeyValueDecoderTests { @Test func decodes_UnkeyedOptionals() throws { - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.nilDecodingStrategy = .removed #expect( @@ -704,7 +704,7 @@ struct KeyValueDecoderTests { @Test func decodes_UnkeyedNil() throws { - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.nilDecodingStrategy = .default #expect( @@ -917,7 +917,7 @@ struct KeyValueDecoderTests { UInt8(from: Double.nan, using: .clamping(roundingRule: nil)) == nil ) - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.intDecodingStrategy = .clamping(roundingRule: .toNearestOrAwayFromZero) #expect( try decoder.decode([Int8].self, from: [10, 20.5, 1000, -Double.infinity]) == [ @@ -1004,7 +1004,7 @@ private extension KeyValueDecoder { return try closure(&container) } - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.userInfo[.decoder] = proxy as any DecodingProxy _ = try decoder.decode(StubDecoder.self, from: value) return proxy.result! @@ -1019,14 +1019,14 @@ private extension KeyValueDecoder { return try closure(&container) } - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.userInfo[.decoder] = proxy as any DecodingProxy _ = try decoder.decode(StubDecoder.self, from: value) return proxy.result! } static func makeJSONCompatible() -> KeyValueDecoder { - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.nilDecodingStrategy = .nsNull return decoder } diff --git a/Tests/KeyValueDecoderXCTests.swift b/Tests/KeyValueDecoderXCTests.swift index 40793ca..0449ada 100644 --- a/Tests/KeyValueDecoderXCTests.swift +++ b/Tests/KeyValueDecoderXCTests.swift @@ -34,7 +34,7 @@ import XCTest -final class KeyValueDecoderTests: XCTestCase { +final class KeyValueDecoderXCTests: XCTestCase { func testDecodes_String() { let decoder = KeyValueDecoder() @@ -149,7 +149,7 @@ final class KeyValueDecoderTests: XCTestCase { } func testDecodesRounded_Ints() { - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.intDecodingStrategy = .rounding(rule: .toNearestOrAwayFromZero) XCTAssertEqual( @@ -247,7 +247,7 @@ final class KeyValueDecoderTests: XCTestCase { } func testDecodesRounded_UInts() { - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.intDecodingStrategy = .rounding(rule: .toNearestOrAwayFromZero) XCTAssertEqual( @@ -460,7 +460,7 @@ final class KeyValueDecoderTests: XCTestCase { } func testDecodes_Null() { - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.nilDecodingStrategy = .default XCTAssertThrowsError( @@ -498,7 +498,7 @@ final class KeyValueDecoderTests: XCTestCase { } func testDecodes_UnkeyedOptionals() { - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.nilDecodingStrategy = .removed XCTAssertEqual( @@ -749,7 +749,7 @@ final class KeyValueDecoderTests: XCTestCase { } func testDecodes_UnkeyedNil() { - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.nilDecodingStrategy = .default XCTAssertEqual( @@ -963,7 +963,7 @@ final class KeyValueDecoderTests: XCTestCase { ) // [10, , 20.5, 1000, -Double.infinity] - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.intDecodingStrategy = .clamping(roundingRule: .toNearestOrAwayFromZero) XCTAssertEqual( try decoder.decode([Int8].self, from: [10, 20.5, 1000, -Double.infinity]), @@ -1037,32 +1037,39 @@ private extension KeyValueDecoder { } - static func decodeValue(from value: [String: Any], keyedBy: K.Type = K.self, with closure: @escaping (inout KeyedDecodingContainer) throws -> T) throws -> T { + static func decodeValue( + from value: [String: Any], + keyedBy: K.Type = K.self, + with closure: @escaping (inout KeyedDecodingContainer + ) throws -> T) throws -> T { let proxy = StubDecoder.Proxy { decoder in var container = try decoder.container(keyedBy: K.self) return try closure(&container) } - let decoder = KeyValueDecoder() - decoder.userInfo[.decoder] = proxy.decode(from:) + var decoder = KeyValueDecoder() + decoder.userInfo[.decoder] = proxy as any DecodingProxy _ = try decoder.decode(StubDecoder.self, from: value) return proxy.result! } - static func decodeUnkeyedValue(from value: [Any], with closure: @escaping (inout any UnkeyedDecodingContainer) throws -> T) throws -> T { + static func decodeUnkeyedValue( + from value: [Any], + with closure: @escaping (inout any UnkeyedDecodingContainer) throws -> T + ) throws -> T { let proxy = StubDecoder.Proxy { decoder in var container = try decoder.unkeyedContainer() return try closure(&container) } - let decoder = KeyValueDecoder() - decoder.userInfo[.decoder] = proxy.decode(from:) + var decoder = KeyValueDecoder() + decoder.userInfo[.decoder] = proxy as any DecodingProxy _ = try decoder.decode(StubDecoder.self, from: value) return proxy.result! } static func makeJSONCompatible() -> KeyValueDecoder { - let decoder = KeyValueDecoder() + var decoder = KeyValueDecoder() decoder.nilDecodingStrategy = .nsNull return decoder } @@ -1072,9 +1079,13 @@ private extension CodingUserInfoKey { static let decoder = CodingUserInfoKey(rawValue: "decoder")! } +private protocol DecodingProxy: Sendable { + func decode(from decoder: any Decoder) throws +} + private struct StubDecoder: Decodable { - final class Proxy { + final class Proxy: @unchecked Sendable, DecodingProxy { private let closure: (any Decoder) throws -> T private(set) var result: T? @@ -1088,8 +1099,8 @@ private struct StubDecoder: Decodable { } init(from decoder: any Decoder) throws { - let closure = decoder.userInfo[.decoder] as! (any Decoder) throws -> Void - try closure(decoder) + let proxy = decoder.userInfo[.decoder] as! any DecodingProxy + try proxy.decode(from: decoder) } } diff --git a/Tests/KeyValueEncoderTests.swift b/Tests/KeyValueEncoderTests.swift index 274a893..1b500b5 100644 --- a/Tests/KeyValueEncoderTests.swift +++ b/Tests/KeyValueEncoderTests.swift @@ -540,7 +540,7 @@ struct KeyValueEncodedTests { @Test func nilEncodingStrategy_SingleContainer() throws { - let encoder = KeyValueEncoder() + var encoder = KeyValueEncoder() encoder.nilEncodingStrategy = .removed #expect( @@ -568,7 +568,7 @@ struct KeyValueEncodedTests { @Test func nilEncodingStrategy_UnkeyedContainer() throws { - let encoder = KeyValueEncoder() + var encoder = KeyValueEncoder() encoder.nilEncodingStrategy = .removed #expect( @@ -677,7 +677,7 @@ private extension KeyValueEncoder { static func encodeSingleValue(nilEncodingStrategy: NilEncodingStrategy = .default, with closure: (inout any SingleValueEncodingContainer) throws -> Void) throws -> EncodedValue { - let encoder = KeyValueEncoder() + var encoder = KeyValueEncoder() encoder.nilEncodingStrategy = nilEncodingStrategy return try encoder.encodeValue { var container = $0.singleValueContainer() @@ -692,11 +692,12 @@ private extension KeyValueEncoder { } } - static func encodeKeyedValue(keyedBy: K.Type = K.self, - nilEncodingStrategy: NilEncodingStrategy = .default, - with closure: @escaping (inout KeyedEncodingContainer) throws -> Void) throws - -> EncodedValue { - let encoder = KeyValueEncoder() + static func encodeKeyedValue( + keyedBy: K.Type = K.self, + nilEncodingStrategy: NilEncodingStrategy = .default, + with closure: @escaping (inout KeyedEncodingContainer) throws -> Void + ) throws -> EncodedValue { + var encoder = KeyValueEncoder() encoder.nilEncodingStrategy = nilEncodingStrategy return try encoder.encodeValue { var container = $0.container(keyedBy: K.self) @@ -711,7 +712,7 @@ private extension KeyValueEncoder { } static func makeJSONCompatible() -> KeyValueEncoder { - let encoder = KeyValueEncoder() + var encoder = KeyValueEncoder() encoder.nilEncodingStrategy = .nsNull return encoder } diff --git a/Tests/KeyValueEncoderXCTests.swift b/Tests/KeyValueEncoderXCTests.swift index aa10241..856c584 100644 --- a/Tests/KeyValueEncoderXCTests.swift +++ b/Tests/KeyValueEncoderXCTests.swift @@ -34,7 +34,7 @@ import XCTest -final class KeyValueEncodedTests: XCTestCase { +final class KeyValueEncodedXCTests: XCTestCase { typealias EncodedValue = KeyValueEncoder.EncodedValue @@ -543,7 +543,7 @@ final class KeyValueEncodedTests: XCTestCase { } func testNilEncodingStrategy_SingleContainer() { - let encoder = KeyValueEncoder() + var encoder = KeyValueEncoder() encoder.nilEncodingStrategy = .removed XCTAssertNil( @@ -572,7 +572,7 @@ final class KeyValueEncodedTests: XCTestCase { } func testNilEncodingStrategy_UnkeyedContainer() { - let encoder = KeyValueEncoder() + var encoder = KeyValueEncoder() encoder.nilEncodingStrategy = .removed XCTAssertEqual( @@ -670,7 +670,7 @@ private extension KeyValueEncoder { static func encodeSingleValue(nilEncodingStrategy: NilEncodingStrategy = .default, with closure: (inout any SingleValueEncodingContainer) throws -> Void) throws -> EncodedValue { - let encoder = KeyValueEncoder() + var encoder = KeyValueEncoder() encoder.nilEncodingStrategy = nilEncodingStrategy return try encoder.encodeValue { var container = $0.singleValueContainer() @@ -688,7 +688,7 @@ private extension KeyValueEncoder { static func encodeKeyedValue(keyedBy: K.Type = K.self, nilEncodingStrategy: NilEncodingStrategy = .default, with closure: @escaping (inout KeyedEncodingContainer) throws -> Void) throws -> EncodedValue { - let encoder = KeyValueEncoder() + var encoder = KeyValueEncoder() encoder.nilEncodingStrategy = nilEncodingStrategy return try encoder.encodeValue { var container = $0.container(keyedBy: K.self) @@ -703,7 +703,7 @@ private extension KeyValueEncoder { } static func makeJSONCompatible() -> KeyValueEncoder { - let encoder = KeyValueEncoder() + var encoder = KeyValueEncoder() encoder.nilEncodingStrategy = .nsNull return encoder }