Files

166 lines
4.9 KiB
Swift
Raw Permalink Normal View History

// Copyright 2022 Google LLC
2022-01-26 15:24:08 -05: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
//
// http://www.apache.org/licenses/LICENSE-2.0
2022-01-26 15:24:08 -05: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
extension FunctionsSerializer {
2024-07-27 19:06:14 +02:00
enum Error: Swift.Error {
case unsupportedType(typeName: String)
case failedToParseWrappedNumber(value: String, type: String)
2024-07-27 19:06:14 +02:00
}
2022-01-26 15:24:08 -05:00
}
final class FunctionsSerializer: Sendable {
// MARK: - Internal APIs
2022-01-26 15:24:08 -05: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
// - NSDictionary
// - NSArray
func encode(_ object: Any) throws -> Any {
2022-01-26 15:24:08 -05:00
if object is NSNull {
return object
} else if let number = object as? NSNumber {
return wrapNumberIfNeeded(number)
2022-01-26 15:24:08 -05:00
} else if object is NSString {
return object
} else if let dict = object as? NSDictionary {
let encoded = NSMutableDictionary()
try dict.forEach { key, value in
encoded[key] = try encode(value)
2022-01-26 15:24:08 -05:00
}
return encoded
} 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
}
}
// 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
// - NSDictionary
// - NSArray
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 {
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 {
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
}
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))
}
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:
// All other types should fit JS limits, so return the number as is:
return number
2022-01-26 15:24:08 -05: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
case .unsignedLong:
guard let n = UInt(wrapped.value) else {
throw .failedToParseWrappedNumber(
value: wrapped.value,
type: wrapped.type.rawValue
)
}
return n
}
}
}
// 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
}
}
}