Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Functions Serializer Updates #13409

Merged
merged 1 commit into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 23 additions & 32 deletions FirebaseFunctions/Sources/Internal/FunctionsSerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
ncooke3 marked this conversation as resolved.
Show resolved Hide resolved
case unsupportedType(typeName: String)
ncooke3 marked this conversation as resolved.
Show resolved Hide resolved
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

Expand Down Expand Up @@ -67,7 +67,7 @@ class FUNSerializer: NSObject {
return encoded

} else {
throw SerializerError.unsupportedType
throw Error.unsupportedType(typeName: typeName(of: object))
}
}

Expand All @@ -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)
paulb777 marked this conversation as resolved.
Show resolved Hide resolved
}
return decoded
} else if let array = object as? NSArray {
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
}

Expand All @@ -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

Expand All @@ -182,7 +173,7 @@ class FUNSerializer: NSObject {
var endPtr: UnsafeMutablePointer<CChar>?
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)

Expand Down
87 changes: 45 additions & 42 deletions FirebaseFunctions/Tests/Unit/SerializerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,67 +25,64 @@ 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)
XCTAssertEqual("1", dict["value"] as? String)
}

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)
XCTAssertEqual("-9223372036854775800", dict["value"] as? String)
}

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
Expand All @@ -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
Expand All @@ -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"]
Expand All @@ -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"]
Expand All @@ -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
Expand All @@ -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",
Expand All @@ -204,7 +188,6 @@ class SerializerTests: XCTestCase {
}

func testDecodeArray() throws {
let serializer = FUNSerializer()
let input = [
1 as Int64,
"two",
Expand All @@ -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 {
ncooke3 marked this conversation as resolved.
Show resolved Hide resolved
let id = 123
}
Loading