2022-01-31 09:10:27 -08:00
|
|
|
|
// Copyright 2022 Google LLC
|
2022-01-26 15:24:08 -05:00
|
|
|
|
//
|
2022-01-31 09:10:27 -08:00
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
|
// You may obtain a copy of the License at
|
2022-01-26 15:24:08 -05:00
|
|
|
|
//
|
2022-01-31 09:10:27 -08:00
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
2022-01-26 15:24:08 -05:00
|
|
|
|
//
|
2022-01-31 09:10:27 -08:00
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
|
// limitations under the License.
|
2022-01-26 15:24:08 -05:00
|
|
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
|
|
2024-07-27 20:40:16 +02:00
|
|
|
|
extension FunctionsSerializer {
|
2024-07-27 19:06:14 +02:00
|
|
|
|
enum Error: Swift.Error {
|
|
|
|
|
|
case unsupportedType(typeName: String)
|
2025-06-03 16:59:42 +02:00
|
|
|
|
case failedToParseWrappedNumber(value: String, type: String)
|
2024-07-27 19:06:14 +02:00
|
|
|
|
}
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-25 20:19:31 -04:00
|
|
|
|
final class FunctionsSerializer: Sendable {
|
2022-02-08 09:23:19 -09:00
|
|
|
|
// MARK: - Internal APIs
|
2022-01-26 15:24:08 -05:00
|
|
|
|
|
2025-05-13 11:07:39 -04:00
|
|
|
|
// This function only supports the following types and will otherwise throw
|
|
|
|
|
|
// an error.
|
|
|
|
|
|
// - NSNull (note: `nil` collection values from a Swift caller will be treated as NSNull)
|
|
|
|
|
|
// - NSNumber
|
|
|
|
|
|
// - NSString
|
2025-06-12 17:46:27 +03:30
|
|
|
|
// - NSDictionary
|
2025-05-13 11:07:39 -04:00
|
|
|
|
// - NSArray
|
2025-03-04 15:47:28 +01:00
|
|
|
|
func encode(_ object: Any) throws -> Any {
|
2022-01-26 15:24:08 -05:00
|
|
|
|
if object is NSNull {
|
2025-03-04 15:47:28 +01:00
|
|
|
|
return object
|
2025-06-03 16:59:42 +02:00
|
|
|
|
} else if let number = object as? NSNumber {
|
|
|
|
|
|
return wrapNumberIfNeeded(number)
|
2022-01-26 15:24:08 -05:00
|
|
|
|
} else if object is NSString {
|
2025-03-04 15:47:28 +01:00
|
|
|
|
return object
|
|
|
|
|
|
} else if let dict = object as? NSDictionary {
|
2024-07-31 17:11:03 +02:00
|
|
|
|
let encoded = NSMutableDictionary()
|
|
|
|
|
|
try dict.forEach { key, value in
|
|
|
|
|
|
encoded[key] = try encode(value)
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
|
|
|
|
|
return encoded
|
2025-03-04 15:47:28 +01:00
|
|
|
|
} else if let array = object as? NSArray {
|
|
|
|
|
|
return try array.map { element in
|
|
|
|
|
|
try encode(element)
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2024-07-27 19:06:14 +02:00
|
|
|
|
throw Error.unsupportedType(typeName: typeName(of: object))
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-13 11:07:39 -04:00
|
|
|
|
// This function only supports the following types and will otherwise throw
|
|
|
|
|
|
// an error.
|
|
|
|
|
|
// - NSNull (note: `nil` collection values from a Swift caller will be treated as NSNull)
|
|
|
|
|
|
// - NSNumber
|
|
|
|
|
|
// - NSString
|
2025-06-12 17:46:27 +03:30
|
|
|
|
// - NSDictionary
|
2025-05-13 11:07:39 -04:00
|
|
|
|
// - NSArray
|
2025-02-28 23:53:36 +01:00
|
|
|
|
func decode(_ object: Any) throws -> Any {
|
2022-01-26 15:24:08 -05:00
|
|
|
|
// Return these types as is. PORTING NOTE: Moved from the bottom of the func for readability.
|
2022-01-26 17:04:58 -05:00
|
|
|
|
if let dict = object as? NSDictionary {
|
2025-06-03 16:59:42 +02:00
|
|
|
|
if let wrappedNumber = WrappedNumber(from: dict) {
|
|
|
|
|
|
return try unwrapNumber(wrappedNumber)
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let decoded = NSMutableDictionary()
|
2024-07-27 19:06:14 +02:00
|
|
|
|
try dict.forEach { key, value in
|
|
|
|
|
|
decoded[key] = try decode(value)
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
|
|
|
|
|
return decoded
|
|
|
|
|
|
} else if let array = object as? NSArray {
|
2024-07-31 17:11:03 +02:00
|
|
|
|
let decoded = NSMutableArray(capacity: array.count)
|
|
|
|
|
|
try array.forEach { element in
|
|
|
|
|
|
try decoded.add(decode(element) as Any)
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
2024-07-31 17:11:03 +02:00
|
|
|
|
return decoded
|
2022-01-26 17:04:58 -05:00
|
|
|
|
} else if object is NSNumber || object is NSString || object is NSNull {
|
|
|
|
|
|
return object as AnyObject
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-27 19:06:14 +02:00
|
|
|
|
throw Error.unsupportedType(typeName: typeName(of: object))
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - Private Helpers
|
|
|
|
|
|
|
2024-07-27 19:06:14 +02:00
|
|
|
|
private func typeName(of value: Any) -> String {
|
|
|
|
|
|
String(describing: type(of: value))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-03 16:59:42 +02:00
|
|
|
|
private func wrapNumberIfNeeded(_ number: NSNumber) -> Any {
|
|
|
|
|
|
switch String(cString: number.objCType) {
|
|
|
|
|
|
case "q":
|
|
|
|
|
|
// "long long" might be larger than JS supports, so make it a string:
|
|
|
|
|
|
return WrappedNumber(type: .long, value: "\(number)").encoded
|
|
|
|
|
|
case "Q":
|
|
|
|
|
|
// "unsigned long long" might be larger than JS supports, so make it a string:
|
|
|
|
|
|
return WrappedNumber(type: .unsignedLong, value: "\(number)").encoded
|
2022-01-26 15:24:08 -05:00
|
|
|
|
default:
|
2025-06-03 16:59:42 +02:00
|
|
|
|
// All other types should fit JS limits, so return the number as is:
|
|
|
|
|
|
return number
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-03 16:59:42 +02:00
|
|
|
|
private func unwrapNumber(_ wrapped: WrappedNumber) throws(Error) -> any Numeric {
|
|
|
|
|
|
switch wrapped.type {
|
|
|
|
|
|
case .long:
|
|
|
|
|
|
guard let n = Int(wrapped.value) else {
|
|
|
|
|
|
throw .failedToParseWrappedNumber(
|
|
|
|
|
|
value: wrapped.value,
|
|
|
|
|
|
type: wrapped.type.rawValue
|
|
|
|
|
|
)
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
|
|
|
|
|
return n
|
2025-06-03 16:59:42 +02:00
|
|
|
|
case .unsignedLong:
|
|
|
|
|
|
guard let n = UInt(wrapped.value) else {
|
|
|
|
|
|
throw .failedToParseWrappedNumber(
|
|
|
|
|
|
value: wrapped.value,
|
|
|
|
|
|
type: wrapped.type.rawValue
|
|
|
|
|
|
)
|
2022-02-08 09:23:19 -09:00
|
|
|
|
}
|
2025-06-03 16:59:42 +02:00
|
|
|
|
return n
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-02-08 09:23:19 -09:00
|
|
|
|
|
2025-06-03 16:59:42 +02:00
|
|
|
|
// MARK: - WrappedNumber
|
|
|
|
|
|
|
|
|
|
|
|
extension FunctionsSerializer {
|
|
|
|
|
|
private struct WrappedNumber {
|
|
|
|
|
|
let type: NumberType
|
|
|
|
|
|
let value: String
|
|
|
|
|
|
|
|
|
|
|
|
// When / if objects are encoded / decoded using `Codable`,
|
|
|
|
|
|
// these two `init`s and `encoded` won’t be needed anymore:
|
|
|
|
|
|
|
|
|
|
|
|
init(type: NumberType, value: String) {
|
|
|
|
|
|
self.type = type
|
|
|
|
|
|
self.value = value
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
init?(from dictionary: NSDictionary) {
|
|
|
|
|
|
guard
|
|
|
|
|
|
let typeString = dictionary["@type"] as? String,
|
|
|
|
|
|
let type = NumberType(rawValue: typeString),
|
|
|
|
|
|
let value = dictionary["value"] as? String
|
|
|
|
|
|
else {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
self.init(type: type, value: value)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var encoded: [String: String] {
|
|
|
|
|
|
["@type": type.rawValue, "value": value]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum NumberType: String {
|
|
|
|
|
|
case long = "type.googleapis.com/google.protobuf.Int64Value"
|
|
|
|
|
|
case unsignedLong = "type.googleapis.com/google.protobuf.UInt64Value"
|
2022-01-26 15:24:08 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|