diff --git a/FirebaseFunctions/Sources/Internal/FunctionsSerializer.swift b/FirebaseFunctions/Sources/Internal/FunctionsSerializer.swift index 5d018a872b0..938ff32a4f8 100644 --- a/FirebaseFunctions/Sources/Internal/FunctionsSerializer.swift +++ b/FirebaseFunctions/Sources/Internal/FunctionsSerializer.swift @@ -20,21 +20,21 @@ private enum Constants { static let dateType = "type.googleapis.com/google.protobuf.Timestamp" } -enum SerializerError: Error { - // TODO: Add parameters class name and value - case unsupportedType // (className: String, value: AnyObject) - case unknownNumberType(charValue: String, number: NSNumber) - case invalidValueForType(value: String, requestedType: String) +extension FUNSerializer { + enum Error: Swift.Error { + case unsupportedType(typeName: String) + case unknownNumberType(charValue: String, number: NSNumber) + case invalidValueForType(value: String, requestedType: String) + } } class FUNSerializer: NSObject { - private let dateFormatter: DateFormatter - - override init() { - dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" - dateFormatter.timeZone = TimeZone(identifier: "UTC") - } + private let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + formatter.timeZone = TimeZone(identifier: "UTC") + return formatter + }() // MARK: - Internal APIs @@ -67,7 +67,7 @@ class FUNSerializer: NSObject { return encoded } else { - throw SerializerError.unsupportedType + throw Error.unsupportedType(typeName: typeName(of: object)) } } @@ -86,21 +86,8 @@ class FUNSerializer: NSObject { } let decoded = NSMutableDictionary() - var decodeError: Error? - dict.enumerateKeysAndObjects { key, obj, stopPointer in - do { - let decodedItem = try self.decode(obj) - decoded[key] = decodedItem - } catch { - decodeError = error - stopPointer.pointee = true - return - } - } - - // Throw the internal error that popped up, if it did. - if let decodeError { - throw decodeError + try dict.forEach { key, value in + decoded[key] = try decode(value) } return decoded } else if let array = object as? NSArray { @@ -116,11 +103,15 @@ class FUNSerializer: NSObject { return object as AnyObject } - throw SerializerError.unsupportedType + throw Error.unsupportedType(typeName: typeName(of: object)) } // MARK: - Private Helpers + private func typeName(of value: Any) -> String { + String(describing: type(of: value)) + } + private func encodeNumber(_ number: NSNumber) throws -> AnyObject { // Recover the underlying type of the number, using the method described here: // http://stackoverflow.com/questions/2518761/get-type-of-nsnumber @@ -163,7 +154,7 @@ class FUNSerializer: NSObject { default: // All documented codes should be handled above, so this shouldn"t happen. - throw SerializerError.unknownNumberType(charValue: String(cType[0]), number: number) + throw Error.unknownNumberType(charValue: String(cType[0]), number: number) } } @@ -172,7 +163,7 @@ class FUNSerializer: NSObject { case Constants.longType: let formatter = NumberFormatter() guard let n = formatter.number(from: value) else { - throw SerializerError.invalidValueForType(value: value, requestedType: type) + throw Error.invalidValueForType(value: value, requestedType: type) } return n @@ -182,7 +173,7 @@ class FUNSerializer: NSObject { var endPtr: UnsafeMutablePointer? let returnValue = UInt64(strtoul(str, &endPtr, 10)) guard String(returnValue) == value else { - throw SerializerError.invalidValueForType(value: value, requestedType: type) + throw Error.invalidValueForType(value: value, requestedType: type) } return NSNumber(value: returnValue) diff --git a/FirebaseFunctions/Tests/Unit/SerializerTests.swift b/FirebaseFunctions/Tests/Unit/SerializerTests.swift index 6aaf1e9b947..8ab0d8925a4 100644 --- a/FirebaseFunctions/Tests/Unit/SerializerTests.swift +++ b/FirebaseFunctions/Tests/Unit/SerializerTests.swift @@ -25,26 +25,29 @@ import FirebaseCore import XCTest class SerializerTests: XCTestCase { + private var serializer: FUNSerializer! + + override func setUp() { + super.setUp() + serializer = FUNSerializer() + } + func testEncodeNull() throws { - let serializer = FUNSerializer() let null = NSNull() XCTAssertEqual(try serializer.encode(null) as? NSNull, null) } func testDecodeNull() throws { - let serializer = FUNSerializer() let null = NSNull() XCTAssertEqual(try serializer.decode(null) as? NSNull, null) } func testEncodeInt32() throws { - let serializer = FUNSerializer() let one = NSNumber(value: 1 as Int32) XCTAssertEqual(one, try serializer.encode(one) as? NSNumber) } func testEncodeInt() throws { - let serializer = FUNSerializer() let one = NSNumber(1) let dict = try XCTUnwrap(serializer.encode(one) as? NSDictionary) XCTAssertEqual("type.googleapis.com/google.protobuf.Int64Value", dict["@type"] as? String) @@ -52,26 +55,22 @@ class SerializerTests: XCTestCase { } func testDecodeInt32() throws { - let serializer = FUNSerializer() let one = NSNumber(value: 1 as Int32) XCTAssertEqual(one, try serializer.decode(one) as? NSNumber) } func testDecodeInt() throws { - let serializer = FUNSerializer() let one = NSNumber(1) XCTAssertEqual(one, try serializer.decode(one) as? NSNumber) } func testDecodeIntFromDictionary() throws { - let serializer = FUNSerializer() let dictOne = ["@type": "type.googleapis.com/google.protobuf.Int64Value", "value": "1"] XCTAssertEqual(NSNumber(1), try serializer.decode(dictOne) as? NSNumber) } func testEncodeLong() throws { - let serializer = FUNSerializer() let lowLong = NSNumber(-9_223_372_036_854_775_800) let dict = try XCTUnwrap(serializer.encode(lowLong) as? NSDictionary) XCTAssertEqual("type.googleapis.com/google.protobuf.Int64Value", dict["@type"] as? String) @@ -79,13 +78,11 @@ class SerializerTests: XCTestCase { } func testDecodeLong() throws { - let serializer = FUNSerializer() let lowLong = NSNumber(-9_223_372_036_854_775_800) XCTAssertEqual(lowLong, try serializer.decode(lowLong) as? NSNumber) } func testDecodeLongFromDictionary() throws { - let serializer = FUNSerializer() let dictLowLong = ["@type": "type.googleapis.com/google.protobuf.Int64Value", "value": "-9223372036854775800"] let decoded = try serializer.decode(dictLowLong) as? NSNumber @@ -96,13 +93,12 @@ class SerializerTests: XCTestCase { } func testDecodeInvalidLong() throws { - let serializer = FUNSerializer() let typeString = "type.googleapis.com/google.protobuf.Int64Value" let badVal = "-9223372036854775800 and some other junk" let dictLowLong = ["@type": typeString, "value": badVal] do { _ = try serializer.decode(dictLowLong) as? NSNumber - } catch let SerializerError.invalidValueForType(value, type) { + } catch let FUNSerializer.Error.invalidValueForType(value, type) { XCTAssertEqual(value, badVal) XCTAssertEqual(type, typeString) return @@ -111,7 +107,6 @@ class SerializerTests: XCTestCase { } func testEncodeUnsignedLong() throws { - let serializer = FUNSerializer() let typeString = "type.googleapis.com/google.protobuf.UInt64Value" let highULong = NSNumber(value: 18_446_744_073_709_551_607 as UInt64) let expected = ["@type": typeString, "value": "18446744073709551607"] @@ -120,13 +115,11 @@ class SerializerTests: XCTestCase { } func testDecodeUnsignedLong() throws { - let serializer = FUNSerializer() let highULong = NSNumber(value: 18_446_744_073_709_551_607 as UInt64) XCTAssertEqual(highULong, try serializer.decode(highULong) as? NSNumber) } func testDecodeUnsignedLongFromDictionary() throws { - let serializer = FUNSerializer() let typeString = "type.googleapis.com/google.protobuf.UInt64Value" let highULong = NSNumber(value: 18_446_744_073_709_551_607 as UInt64) let coded = ["@type": typeString, "value": "18446744073709551607"] @@ -138,13 +131,12 @@ class SerializerTests: XCTestCase { } func testDecodeUnsignedLongFromDictionaryOverflow() throws { - let serializer = FUNSerializer() let typeString = "type.googleapis.com/google.protobuf.UInt64Value" let tooHighVal = "18446744073709551616" let coded = ["@type": typeString, "value": tooHighVal] do { _ = try serializer.decode(coded) as? NSNumber - } catch let SerializerError.invalidValueForType(value, type) { + } catch let FUNSerializer.Error.invalidValueForType(value, type) { XCTAssertEqual(value, tooHighVal) XCTAssertEqual(type, typeString) return @@ -153,47 +145,39 @@ class SerializerTests: XCTestCase { } func testEncodeDouble() throws { - let serializer = FUNSerializer() let myDouble = NSNumber(value: 1.2 as Double) XCTAssertEqual(myDouble, try serializer.encode(myDouble) as? NSNumber) } func testDecodeDouble() throws { - let serializer = FUNSerializer() let myDouble = NSNumber(value: 1.2 as Double) XCTAssertEqual(myDouble, try serializer.decode(myDouble) as? NSNumber) } func testEncodeBool() throws { - let serializer = FUNSerializer() XCTAssertEqual(true, try serializer.encode(true) as? NSNumber) } func testDecodeBool() throws { - let serializer = FUNSerializer() XCTAssertEqual(true, try serializer.decode(true) as? NSNumber) } func testEncodeString() throws { - let serializer = FUNSerializer() XCTAssertEqual("hello", try serializer.encode("hello") as? String) } func testDecodeString() throws { - let serializer = FUNSerializer() XCTAssertEqual("good-bye", try serializer.decode("good-bye") as? String) } // TODO: Should we add support for Array as well as NSArray? func testEncodeSimpleArray() throws { - let serializer = FUNSerializer() let input = [1 as Int32, 2 as Int32] as NSArray XCTAssertEqual(input, try serializer.encode(input) as? NSArray) } func testEncodeArray() throws { - let serializer = FUNSerializer() let input = [ 1 as Int32, "two", @@ -204,7 +188,6 @@ class SerializerTests: XCTestCase { } func testDecodeArray() throws { - let serializer = FUNSerializer() let input = [ 1 as Int64, "two", @@ -227,44 +210,64 @@ class SerializerTests: XCTestCase { "baz": [3, ["@type": "type.googleapis.com/google.protobuf.Int64Value", "value": "9876543210"]] as [Any], ] as NSDictionary - let serializer = FUNSerializer() XCTAssertEqual(expected, try serializer.encode(input) as? NSDictionary) } func testDecodeMap() { 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 - let serializer = FUNSerializer() XCTAssertEqual(expected, try serializer.decode(input) as? NSDictionary) } func testEncodeUnknownType() { let input = ["@type": "unknown", "value": "whatever"] as NSDictionary - let serializer = FUNSerializer() XCTAssertEqual(input, try serializer.encode(input) as? NSDictionary) } func testDecodeUnknownType() { let input = ["@type": "unknown", "value": "whatever"] as NSDictionary - let serializer = FUNSerializer() XCTAssertEqual(input, try serializer.decode(input) as? NSDictionary) } func testDecodeUnknownTypeWithoutValue() { let input = ["@type": "unknown"] as NSDictionary - let serializer = FUNSerializer() XCTAssertEqual(input, try serializer.decode(input) as? NSDictionary) } - // - (void)testDecodeUnknownTypeWithoutValue { -// NSDictionary *input = @{ -// @"@type" : @"unknown", -// }; -// FUNSerializer *serializer = [[FUNSerializer alloc] init]; -// NSError *error = nil; -// XCTAssertEqualObjects(input, [serializer decode:input error:&error]); -// XCTAssertNil(error); - // } -// - // @end + func testEncodeUnsupportedType() { + let input = CustomObject() + + do { + let _ = try serializer.encode(input) + XCTFail("Expected an error") + } catch { + guard case let .unsupportedType(typeName: typeName) = error as? FUNSerializer.Error + else { + return XCTFail("Unexpected error: \(error)") + } + + XCTAssertEqual(typeName, "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? FUNSerializer.Error + else { + return XCTFail("Unexpected error: \(error)") + } + + XCTAssertEqual(typeName, "CustomObject") + } + } +} + +/// Used to represent a type that cannot be encoded or decoded. +private struct CustomObject { + let id = 123 }