diff --git a/FirebaseFunctions/Sources/Internal/FunctionsSerializer.swift b/FirebaseFunctions/Sources/Internal/FunctionsSerializer.swift index 653cd02d7fd..4c1317755b3 100644 --- a/FirebaseFunctions/Sources/Internal/FunctionsSerializer.swift +++ b/FirebaseFunctions/Sources/Internal/FunctionsSerializer.swift @@ -47,22 +47,16 @@ class FunctionsSerializer: NSObject { return object as AnyObject } else if object is NSDictionary { let dict = object as! NSDictionary - let encoded: NSMutableDictionary = .init() - dict.enumerateKeysAndObjects { key, obj, _ in - // TODO(wilsonryan): Not exact translation - let anyObj = obj as AnyObject - let stringKey = key as! String - let value = try! encode(anyObj) - encoded[stringKey] = value + let encoded = NSMutableDictionary() + try dict.forEach { key, value in + encoded[key] = try encode(value) } return encoded } else if object is NSArray { let array = object as! NSArray let encoded = NSMutableArray() - for item in array { - let anyItem = item as AnyObject - let encodedItem = try encode(anyItem) - encoded.add(encodedItem) + try array.forEach { element in + try encoded.add(encode(element)) } return encoded @@ -91,14 +85,11 @@ class FunctionsSerializer: NSObject { } return decoded } else if let array = object as? NSArray { - let result = NSMutableArray(capacity: array.count) - for obj in array { - // TODO: Is this data loss? The API is a bit weird. - if let decoded = try decode(obj) { - result.add(decoded) - } + let decoded = NSMutableArray(capacity: array.count) + try array.forEach { element in + try decoded.add(decode(element) as Any) } - return result + return decoded } else if object is NSNumber || object is NSString || object is NSNull { return object as AnyObject } diff --git a/FirebaseFunctions/Tests/Unit/FunctionsSerializerTests.swift b/FirebaseFunctions/Tests/Unit/FunctionsSerializerTests.swift index 5c415d0d12d..7fe77fd4dd9 100644 --- a/FirebaseFunctions/Tests/Unit/FunctionsSerializerTests.swift +++ b/FirebaseFunctions/Tests/Unit/FunctionsSerializerTests.swift @@ -187,6 +187,12 @@ class FunctionsSerializerTests: XCTestCase { XCTAssertEqual(input, try serializer.encode(input) as? NSArray) } + func testEncodeArrayWithInvalidElements() { + let input = ["TEST", CustomObject()] as NSArray + + try assert(serializer.encode(input), throwsUnsupportedTypeErrorWithName: "CustomObject") + } + func testDecodeArray() throws { let input = [ 1 as Int64, @@ -198,7 +204,13 @@ class FunctionsSerializerTests: XCTestCase { XCTAssertEqual(expected, try serializer.decode(input) as? NSArray) } - func testEncodeMap() { + func testDecodeArrayWithInvalidElements() { + let input = ["TEST", CustomObject()] as NSArray + + try assert(serializer.decode(input), throwsUnsupportedTypeErrorWithName: "CustomObject") + } + + func testEncodeDictionary() throws { let input = [ "foo": 1 as Int32, "bar": "hello", @@ -213,12 +225,38 @@ class FunctionsSerializerTests: XCTestCase { XCTAssertEqual(expected, try serializer.encode(input) as? NSDictionary) } - func testDecodeMap() { + func testEncodeDictionaryWithInvalidElements() { + let input = ["TEST_CustomObj": CustomObject()] as NSDictionary + + try assert(serializer.encode(input), throwsUnsupportedTypeErrorWithName: "CustomObject") + } + + func testEncodeDictionaryWithInvalidNestedDictionary() { + let input = + ["TEST_NestedDict": ["TEST_CustomObj": CustomObject()] as NSDictionary] as NSDictionary + + try assert(serializer.encode(input), throwsUnsupportedTypeErrorWithName: "CustomObject") + } + + func testDecodeDictionary() throws { let input = ["foo": 1, "bar": "hello", "baz": [3, 9_876_543_210]] as NSDictionary let expected = ["foo": 1, "bar": "hello", "baz": [3, 9_876_543_210]] as NSDictionary XCTAssertEqual(expected, try serializer.decode(input) as? NSDictionary) } + func testDecodeDictionaryWithInvalidElements() { + let input = ["TEST_CustomObj": CustomObject()] as NSDictionary + + try assert(serializer.decode(input), throwsUnsupportedTypeErrorWithName: "CustomObject") + } + + func testDecodeDictionaryWithInvalidNestedDictionary() { + let input = + ["TEST_NestedDict": ["TEST_CustomObj": CustomObject()] as NSDictionary] as NSDictionary + + try assert(serializer.decode(input), throwsUnsupportedTypeErrorWithName: "CustomObject") + } + func testEncodeUnknownType() { let input = ["@type": "unknown", "value": "whatever"] as NSDictionary XCTAssertEqual(input, try serializer.encode(input) as? NSDictionary) @@ -237,37 +275,34 @@ class FunctionsSerializerTests: XCTestCase { func testEncodeUnsupportedType() { let input = CustomObject() - do { - let _ = try serializer.encode(input) - XCTFail("Expected an error") - } catch { - guard case let .unsupportedType(typeName: typeName) = error as? FunctionsSerializer.Error - else { - return XCTFail("Unexpected error: \(error)") - } - - XCTAssertEqual(typeName, "CustomObject") - } + try assert(serializer.encode(input), throwsUnsupportedTypeErrorWithName: "CustomObject") } func testDecodeUnsupportedType() { let input = CustomObject() - do { - let _ = try serializer.decode(input) - XCTFail("Expected an error") - } catch { - guard case let .unsupportedType(typeName: typeName) = error as? FunctionsSerializer.Error - else { - return XCTFail("Unexpected error: \(error)") + try assert(serializer.decode(input), throwsUnsupportedTypeErrorWithName: "CustomObject") + } +} + +// MARK: - Utilities + +extension FunctionsSerializerTests { + private func assert(_ expression: @autoclosure () throws -> T, + throwsUnsupportedTypeErrorWithName expectedTypeName: String, + line: UInt = #line) { + XCTAssertThrowsError(try expression(), line: line) { error in + guard case let .unsupportedType(typeName: typeName) = error as? FunctionsSerializer + .Error else { + return XCTFail("Unexpected error: \(error)", line: line) } - XCTAssertEqual(typeName, "CustomObject") + XCTAssertEqual(typeName, expectedTypeName, line: line) } } } /// Used to represent a type that cannot be encoded or decoded. -private struct CustomObject { +private class CustomObject { let id = 123 }