Pārlūkot izejas kodu

Fix RC Codable to handle arrays and dictionaries from plist defaults (#10046)

Paul Beusterien 3 gadi atpakaļ
vecāks
revīzija
bc2bc6b9f2

+ 2 - 0
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',

+ 3 - 0
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.

+ 8 - 2
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

+ 42 - 0
FirebaseRemoteConfigSwift/Tests/Defaults-testInfo.plist

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>lastCheckTime</key>
+	<date>2016-02-28T18:33:31Z</date>
+	<key>isPaidUser</key>
+	<true/>
+	<key>dataValue</key>
+	<data>Mi40</data>
+	<key>newItem</key>
+	<real>2.4</real>
+	<key>Languages</key>
+	<string>English</string>
+	<key>FileInfo</key>
+	<string>To setup default config.</string>
+	<key>format</key>
+	<string>key to value.</string>
+	<key>arrayValue</key>
+	<array>
+		<string>foo</string>
+		<string>bar</string>
+		<string>baz</string>
+	</array>
+	<key>arrayIntValue</key>
+	<array>
+		<integer>1</integer>
+		<integer>2</integer>
+		<integer>0</integer>
+		<integer>3</integer>
+	</array>
+	<key>dictValue</key>
+	<dict>
+		<key>foo</key>
+		<string>foo</string>
+		<key>bar</key>
+		<string>bar</string>
+		<key>baz</key>
+		<string>baz</string>
+	</dict>
+</dict>
+</plist>

+ 38 - 0
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

+ 2 - 1
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]?
 }

+ 19 - 2
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<T>(_ 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)

+ 3 - 0
Package.swift

@@ -1060,6 +1060,9 @@ let package = Package(
         "README.md",
         "ObjC/",
       ],
+      resources: [
+        .process("Defaults-testInfo.plist"),
+      ],
       cSettings: [
         .headerSearchPath("../../"),
       ]

+ 1 - 0
scripts/localize_podfile.swift

@@ -39,6 +39,7 @@ let implicitPods = [
   "FirebaseCoreExtension", "FirebaseAppCheckInterop",
   "FirebaseAuthInterop", "FirebaseMessagingInterop",
   "FirebaseStorageInternal", "FirebaseCoreInternal",
+  "FirebaseSharedSwift",
 ]
 
 let binaryPods = [