From 7a23d5ba4d5f3261a1d203626b765b1c664170ed Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Mon, 14 Aug 2023 07:35:41 -0700 Subject: [PATCH] Expanded `EnsureNonEmptyArrayDecodable` to `EnsureNonEmptyCollectionDecodable` (#3002) --- RevenueCat.xcodeproj/project.pbxproj | 8 ++--- ...> EnsureNonEmptyCollectionDecodable.swift} | 27 ++++++++-------- .../DecoderExtensionTests.swift | 32 +++++++++++++++++-- 3 files changed, 48 insertions(+), 19 deletions(-) rename Sources/Misc/Codable/{EnsureNonEmptyArrayDecodable.swift => EnsureNonEmptyCollectionDecodable.swift} (50%) diff --git a/RevenueCat.xcodeproj/project.pbxproj b/RevenueCat.xcodeproj/project.pbxproj index fe319d5f80..bfba18a126 100644 --- a/RevenueCat.xcodeproj/project.pbxproj +++ b/RevenueCat.xcodeproj/project.pbxproj @@ -253,7 +253,7 @@ 4F83F6BA2A5DB807003F90A5 /* CurrentTestCaseTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575A17AA2773A59300AA6F22 /* CurrentTestCaseTracker.swift */; }; 4F83F6BB2A5DB80B003F90A5 /* OSVersionEquivalent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DE80AD28075D77008D6C6F /* OSVersionEquivalent.swift */; }; 4F8452682A5756CC00084550 /* HTTPRequestBody+Signing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8452672A5756CC00084550 /* HTTPRequestBody+Signing.swift */; }; - 4F8929192A65EF3000A91EA2 /* EnsureNonEmptyArrayDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8929182A65EF3000A91EA2 /* EnsureNonEmptyArrayDecodable.swift */; }; + 4F8929192A65EF3000A91EA2 /* EnsureNonEmptyCollectionDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8929182A65EF3000A91EA2 /* EnsureNonEmptyCollectionDecodable.swift */; }; 4F8A58172A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8A58162A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift */; }; 4F8A58182A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8A58162A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift */; }; 4F90AFCB2A3915340047E63F /* TestMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F90AFCA2A3915340047E63F /* TestMessage.swift */; }; @@ -982,7 +982,7 @@ 4F7DBFBC2A1E986C00A2F511 /* StoreKit2TransactionFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKit2TransactionFetcher.swift; sourceTree = ""; }; 4F8038322A1EA7C300D21039 /* TransactionPoster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionPoster.swift; sourceTree = ""; }; 4F8452672A5756CC00084550 /* HTTPRequestBody+Signing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HTTPRequestBody+Signing.swift"; sourceTree = ""; }; - 4F8929182A65EF3000A91EA2 /* EnsureNonEmptyArrayDecodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnsureNonEmptyArrayDecodable.swift; sourceTree = ""; }; + 4F8929182A65EF3000A91EA2 /* EnsureNonEmptyCollectionDecodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnsureNonEmptyCollectionDecodable.swift; sourceTree = ""; }; 4F8A58162A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockOfflineCustomerInfoCreator.swift; sourceTree = ""; }; 4F90AFCA2A3915340047E63F /* TestMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestMessage.swift; sourceTree = ""; }; 4F98E9D22A465A4400DB6EAB /* TestStoreProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestStoreProduct.swift; sourceTree = ""; }; @@ -2470,7 +2470,7 @@ 57EAE52C274468900060EB74 /* RawDataContainer.swift */, 4F0BBA802A1D0524000E75AB /* DefaultDecodable.swift */, 4FBBC5672A61E42F0077281F /* NonEmptyStringDecodable.swift */, - 4F8929182A65EF3000A91EA2 /* EnsureNonEmptyArrayDecodable.swift */, + 4F8929182A65EF3000A91EA2 /* EnsureNonEmptyCollectionDecodable.swift */, ); path = Codable; sourceTree = ""; @@ -3373,7 +3373,7 @@ 2D9C7BB326D838FC006838BE /* UIApplication+RCExtensions.swift in Sources */, F56E2E7727622B5E009FED5B /* TransactionsManager.swift in Sources */, B34605CC279A6E380031CA74 /* LogInOperation.swift in Sources */, - 4F8929192A65EF3000A91EA2 /* EnsureNonEmptyArrayDecodable.swift in Sources */, + 4F8929192A65EF3000A91EA2 /* EnsureNonEmptyCollectionDecodable.swift in Sources */, 35F82BB626A9B8040051DF03 /* AttributionDataMigrator.swift in Sources */, A55D08302722368600D919E0 /* SK2BeginRefundRequestHelper.swift in Sources */, 35D832CD262A5B7500E60AC5 /* ETagManager.swift in Sources */, diff --git a/Sources/Misc/Codable/EnsureNonEmptyArrayDecodable.swift b/Sources/Misc/Codable/EnsureNonEmptyCollectionDecodable.swift similarity index 50% rename from Sources/Misc/Codable/EnsureNonEmptyArrayDecodable.swift rename to Sources/Misc/Codable/EnsureNonEmptyCollectionDecodable.swift index 3bc17bc7f3..1b833a235d 100644 --- a/Sources/Misc/Codable/EnsureNonEmptyArrayDecodable.swift +++ b/Sources/Misc/Codable/EnsureNonEmptyCollectionDecodable.swift @@ -7,36 +7,37 @@ // // https://opensource.org/licenses/MIT // -// EnsureNonEmptyArrayDecodable.swift +// EnsureNonEmptyCollectionDecodable.swift // // Created by Nacho Soto on 7/17/23. import Foundation -/// A property wrapper that ensures decoded arrays aren't empty. +/// A property wrapper that ensures decoded collections aren't empty. /// - Example: /// ``` /// struct Data { -/// @EnsureNonEmptyArrayDecodable var values: [String] // fails to decode if array is empty +/// @EnsureNonEmptyCollectionDecodable var values: [String] // fails to decode if array is empty +/// @EnsureNonEmptyCollectionDecodable var dictionary: [String: String] // fails to decode if dictionary is empty /// } /// ``` @propertyWrapper -struct EnsureNonEmptyArrayDecodable { +struct EnsureNonEmptyCollectionDecodable where Value: Codable { struct Error: Swift.Error {} - var wrappedValue: [Value] + var wrappedValue: Value } -extension EnsureNonEmptyArrayDecodable: Equatable where Value: Equatable {} -extension EnsureNonEmptyArrayDecodable: Hashable where Value: Hashable {} +extension EnsureNonEmptyCollectionDecodable: Equatable where Value: Equatable {} +extension EnsureNonEmptyCollectionDecodable: Hashable where Value: Hashable {} -extension EnsureNonEmptyArrayDecodable: Decodable { +extension EnsureNonEmptyCollectionDecodable: Decodable { init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() - let array = try container.decode([Value].self) + let array = try container.decode(Value.self) if array.isEmpty { throw Error() @@ -47,7 +48,7 @@ extension EnsureNonEmptyArrayDecodable: Decodable { } -extension EnsureNonEmptyArrayDecodable: Encodable { +extension EnsureNonEmptyCollectionDecodable: Encodable { func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -59,11 +60,11 @@ extension EnsureNonEmptyArrayDecodable: Encodable { extension KeyedDecodingContainer { func decode( - _ type: EnsureNonEmptyArrayDecodable.Type, + _ type: EnsureNonEmptyCollectionDecodable.Type, forKey key: Key - ) throws -> EnsureNonEmptyArrayDecodable { + ) throws -> EnsureNonEmptyCollectionDecodable { return try self.decodeIfPresent(type, forKey: key) - .orThrow(EnsureNonEmptyArrayDecodable.Error()) + .orThrow(EnsureNonEmptyCollectionDecodable.Error()) } } diff --git a/Tests/UnitTests/FoundationExtensions/DecoderExtensionTests.swift b/Tests/UnitTests/FoundationExtensions/DecoderExtensionTests.swift index 300c0fbb8a..08c3786656 100644 --- a/Tests/UnitTests/FoundationExtensions/DecoderExtensionTests.swift +++ b/Tests/UnitTests/FoundationExtensions/DecoderExtensionTests.swift @@ -293,7 +293,7 @@ class DecoderExtensionsNonEmptyStringTests: TestCase { class DecoderExtensionsNonEmptyArrayTests: TestCase { private struct Data: Codable, Equatable { - @EnsureNonEmptyArrayDecodable var value: [String] + @EnsureNonEmptyCollectionDecodable var value: [String] init(value: [String]) { self.value = value @@ -317,7 +317,35 @@ class DecoderExtensionsNonEmptyArrayTests: TestCase { func testThrowsWhenDecodingEmptyArray() throws { expect { try Data.decode("{\"value\": []}") - }.to(throwError(EnsureNonEmptyArrayDecodable.Error())) + }.to(throwError(EnsureNonEmptyCollectionDecodable<[String]>.Error())) + } + +} + +class DecoderExtensionsNonEmptyDictionaryTests: TestCase { + + private struct Data: Codable, Equatable { + @EnsureNonEmptyCollectionDecodable var value: [String: Int] + } + + func testDecodesOneValues() throws { + let data = Data(value: ["1": 1]) + expect(try data.encodeAndDecode()) == data + } + + func testDecodesMultipleValues() throws { + let data = Data(value: ["1": 1, "2": 2]) + expect(try data.encodeAndDecode()) == data + } + + func testEncodesEmptyValues() throws { + expect(try Data(value: [:]).encodedJSON) == "{\"value\":{}}" + } + + func testThrowsWhenDecodingEmptyDictionary() throws { + expect { + try Data.decode("{\"value\": {}}") + }.to(throwError(EnsureNonEmptyCollectionDecodable<[String: Int]>.Error())) } }