From bc2bc6b9f282f0ebec0cebcd2ab40b28bfd2c94a Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 29 Jul 2022 11:07:20 -0700 Subject: [PATCH] Fix RC Codable to handle arrays and dictionaries from plist defaults (#10046) --- FirebaseRemoteConfigSwift.podspec | 2 + FirebaseRemoteConfigSwift/CHANGELOG.md | 3 ++ ...rebaseRemoteConfigValueDecoderHelper.swift | 10 ++++- .../Tests/Defaults-testInfo.plist | 42 +++++++++++++++++++ .../Tests/SwiftAPI/Codable.swift | 38 +++++++++++++++++ .../FirebaseRemoteConfigValueDecoding.swift | 3 +- .../FirebaseDataEncoder.swift | 21 +++++++++- Package.swift | 3 ++ scripts/localize_podfile.swift | 1 + 9 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 FirebaseRemoteConfigSwift/Tests/Defaults-testInfo.plist diff --git a/FirebaseRemoteConfigSwift.podspec b/FirebaseRemoteConfigSwift.podspec index 02f5dd07f2..152dd42b13 100644 --- a/FirebaseRemoteConfigSwift.podspec +++ b/FirebaseRemoteConfigSwift.podspec @@ -53,6 +53,7 @@ app update. 'FirebaseRemoteConfigSwift/Tests/FakeUtils/*.swift', 'FirebaseRemoteConfigSwift/Tests/ObjC/*.[hm]', ] + swift_api.resources = 'FirebaseRemoteConfigSwift/Tests/Defaults-testInfo.plist' swift_api.requires_app_host = true swift_api.pod_target_xcconfig = { 'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfigSwift/Tests/ObjC/Bridging-Header.h', @@ -75,6 +76,7 @@ app update. 'FirebaseRemoteConfigSwift/Tests/FakeConsole/*.swift', 'FirebaseRemoteConfigSwift/Tests/ObjC/*.[hm]', ] + fake_console.resources = 'FirebaseRemoteConfigSwift/Tests/Defaults-testInfo.plist' fake_console.requires_app_host = true fake_console.pod_target_xcconfig = { 'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseRemoteConfigSwift/Tests/ObjC/Bridging-Header.h', diff --git a/FirebaseRemoteConfigSwift/CHANGELOG.md b/FirebaseRemoteConfigSwift/CHANGELOG.md index 82cfd078f5..a023d6e841 100644 --- a/FirebaseRemoteConfigSwift/CHANGELOG.md +++ b/FirebaseRemoteConfigSwift/CHANGELOG.md @@ -1,3 +1,6 @@ +# 9.5.0 +- [fixed] Fix Codable implementation to handle arrays and dictionaries from plist defaults. (#9980) + # 9.0.0 - [added] **Breaking change:** `FirebaseRemoteConfigSwift` has exited beta and is now generally available for use. diff --git a/FirebaseRemoteConfigSwift/Sources/FirebaseRemoteConfigValueDecoderHelper.swift b/FirebaseRemoteConfigSwift/Sources/FirebaseRemoteConfigValueDecoderHelper.swift index 44d892ad4c..2a7b79a135 100644 --- a/FirebaseRemoteConfigSwift/Sources/FirebaseRemoteConfigValueDecoderHelper.swift +++ b/FirebaseRemoteConfigSwift/Sources/FirebaseRemoteConfigValueDecoderHelper.swift @@ -40,9 +40,15 @@ struct FirebaseRemoteConfigValueDecoderHelper: FirebaseRemoteConfigValueDecoding return value.dataValue } - func jsonValue() -> [String: AnyHashable]? { + func arrayValue() -> [AnyHashable]? { + guard let value = value.jsonValue as? [AnyHashable] else { + return nil + } + return value + } + + func dictionaryValue() -> [String: AnyHashable]? { guard let value = value.jsonValue as? [String: AnyHashable] else { - // nil is the historical behavior for failing to extract JSON. return nil } return value diff --git a/FirebaseRemoteConfigSwift/Tests/Defaults-testInfo.plist b/FirebaseRemoteConfigSwift/Tests/Defaults-testInfo.plist new file mode 100644 index 0000000000..784dcb4a40 --- /dev/null +++ b/FirebaseRemoteConfigSwift/Tests/Defaults-testInfo.plist @@ -0,0 +1,42 @@ + + + + + lastCheckTime + 2016-02-28T18:33:31Z + isPaidUser + + dataValue + Mi40 + newItem + 2.4 + Languages + English + FileInfo + To setup default config. + format + key to value. + arrayValue + + foo + bar + baz + + arrayIntValue + + 1 + 2 + 0 + 3 + + dictValue + + foo + foo + bar + bar + baz + baz + + + diff --git a/FirebaseRemoteConfigSwift/Tests/SwiftAPI/Codable.swift b/FirebaseRemoteConfigSwift/Tests/SwiftAPI/Codable.swift index 21a888d7f5..5ebf90dfe0 100644 --- a/FirebaseRemoteConfigSwift/Tests/SwiftAPI/Codable.swift +++ b/FirebaseRemoteConfigSwift/Tests/SwiftAPI/Codable.swift @@ -170,5 +170,43 @@ import XCTest } XCTFail("Failed to throw on missing field") } + + func testCodableAfterPlistDefaults() throws { + struct Defaults: Codable { + let format: String + let isPaidUser: Bool + let newItem: Double + let Languages: String + let dictValue: [String: String] + let arrayValue: [String] + let arrayIntValue: [Int] + } + // setDefaults(fromPlist:) doesn't work because of dynamic linking. + // More details in RCNRemoteConfigTest.m + var findPlist: String? + #if SWIFT_PACKAGE + findPlist = Bundle.module.path(forResource: "Defaults-testInfo", ofType: "plist") + #else + for b in Bundle.allBundles { + findPlist = b.path(forResource: "Defaults-testInfo", ofType: "plist") + if findPlist != nil { + break + } + } + #endif + let plistFile = try XCTUnwrap(findPlist) + let defaults = NSDictionary(contentsOfFile: plistFile) + config.setDefaults(defaults as? [String: NSObject]) + let readDefaults: Defaults = try config.decoded() + XCTAssertEqual(readDefaults.format, "key to value.") + XCTAssertEqual(readDefaults.isPaidUser, true) + XCTAssertEqual(readDefaults.newItem, 2.4) + XCTAssertEqual(readDefaults.Languages, "English") + XCTAssertEqual(readDefaults.dictValue, ["foo": "foo", + "bar": "bar", + "baz": "baz"]) + XCTAssertEqual(readDefaults.arrayValue, ["foo", "bar", "baz"]) + XCTAssertEqual(readDefaults.arrayIntValue, [1, 2, 0, 3]) + } } #endif diff --git a/FirebaseSharedSwift/Sources/FirebaseRemoteConfigValueDecoding.swift b/FirebaseSharedSwift/Sources/FirebaseRemoteConfigValueDecoding.swift index da6c446784..6d81d90e19 100644 --- a/FirebaseSharedSwift/Sources/FirebaseRemoteConfigValueDecoding.swift +++ b/FirebaseSharedSwift/Sources/FirebaseRemoteConfigValueDecoding.swift @@ -20,5 +20,6 @@ public protocol FirebaseRemoteConfigValueDecoding { func boolValue() -> Bool func stringValue() -> String func dataValue() -> Data - func jsonValue() -> [String: AnyHashable]? + func arrayValue() -> [AnyHashable]? + func dictionaryValue() -> [String: AnyHashable]? } diff --git a/FirebaseSharedSwift/Sources/third_party/FirebaseDataEncoder/FirebaseDataEncoder.swift b/FirebaseSharedSwift/Sources/third_party/FirebaseDataEncoder/FirebaseDataEncoder.swift index 03788f712a..215e0abfab 100644 --- a/FirebaseSharedSwift/Sources/third_party/FirebaseDataEncoder/FirebaseDataEncoder.swift +++ b/FirebaseSharedSwift/Sources/third_party/FirebaseDataEncoder/FirebaseDataEncoder.swift @@ -1252,8 +1252,11 @@ fileprivate class __JSONDecoder : Decoder { debugDescription: "Cannot get keyed decoding container -- found null value instead.")) } var topContainer : [String : Any] - if let rcValue = self.storage.topContainer as? FirebaseRemoteConfigValueDecoding, - let top = rcValue.jsonValue() { + if let rcValue = self.storage.topContainer as? FirebaseRemoteConfigValueDecoding { + guard let top = rcValue.dictionaryValue() else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, + reality: rcValue) + } topContainer = top } else { guard let top = self.storage.topContainer as? [String : Any] else { @@ -1273,6 +1276,13 @@ fileprivate class __JSONDecoder : Decoder { debugDescription: "Cannot get unkeyed decoding container -- found null value instead.")) } + if let rcValue = self.storage.topContainer as? FirebaseRemoteConfigValueDecoding { + guard let arrayValue = rcValue.arrayValue() else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: rcValue) + } + return _JSONUnkeyedDecodingContainer(referencing: self, wrapping: arrayValue ) + } + guard let topContainer = self.storage.topContainer as? [Any] else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: self.storage.topContainer) } @@ -2468,6 +2478,13 @@ extension __JSONDecoder { fileprivate func unbox(_ value: Any, as type: _JSONStringDictionaryDecodableMarker.Type) throws -> T? { guard !(value is NSNull) else { return nil } + if let rcValue = value as? FirebaseRemoteConfigValueDecoding { + guard let dictionaryValue = rcValue.dictionaryValue() else { + throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: rcValue) + } + return dictionaryValue as? T + } + var result = [String : Any]() guard let dict = value as? NSDictionary else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) diff --git a/Package.swift b/Package.swift index 72722c80b3..14353bbd25 100644 --- a/Package.swift +++ b/Package.swift @@ -1060,6 +1060,9 @@ let package = Package( "README.md", "ObjC/", ], + resources: [ + .process("Defaults-testInfo.plist"), + ], cSettings: [ .headerSearchPath("../../"), ] diff --git a/scripts/localize_podfile.swift b/scripts/localize_podfile.swift index 2a8fca2c19..c111312d32 100755 --- a/scripts/localize_podfile.swift +++ b/scripts/localize_podfile.swift @@ -39,6 +39,7 @@ let implicitPods = [ "FirebaseCoreExtension", "FirebaseAppCheckInterop", "FirebaseAuthInterop", "FirebaseMessagingInterop", "FirebaseStorageInternal", "FirebaseCoreInternal", + "FirebaseSharedSwift", ] let binaryPods = [