FunctionsSerializer.swift 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // Copyright 2022 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. private enum Constants {
  16. static let longType = "type.googleapis.com/google.protobuf.Int64Value"
  17. static let unsignedLongType = "type.googleapis.com/google.protobuf.UInt64Value"
  18. static let dateType = "type.googleapis.com/google.protobuf.Timestamp"
  19. }
  20. extension FunctionsSerializer {
  21. enum Error: Swift.Error {
  22. case unsupportedType(typeName: String)
  23. case unknownNumberType(charValue: String, number: NSNumber)
  24. case invalidValueForType(value: String, requestedType: String)
  25. }
  26. }
  27. final class FunctionsSerializer: Sendable {
  28. // MARK: - Internal APIs
  29. func encode(_ object: Any) throws -> Any {
  30. if object is NSNull {
  31. return object
  32. } else if object is NSNumber {
  33. return try encodeNumber(object as! NSNumber)
  34. } else if object is NSString {
  35. return object
  36. } else if let dict = object as? NSDictionary {
  37. let encoded = NSMutableDictionary()
  38. try dict.forEach { key, value in
  39. encoded[key] = try encode(value)
  40. }
  41. return encoded
  42. } else if let array = object as? NSArray {
  43. return try array.map { element in
  44. try encode(element)
  45. }
  46. } else {
  47. throw Error.unsupportedType(typeName: typeName(of: object))
  48. }
  49. }
  50. func decode(_ object: Any) throws -> Any {
  51. // Return these types as is. PORTING NOTE: Moved from the bottom of the func for readability.
  52. if let dict = object as? NSDictionary {
  53. if let requestedType = dict["@type"] as? String {
  54. guard let value = dict["value"] as? String else {
  55. // Seems like we should throw here - but this maintains compatibility.
  56. return dict
  57. }
  58. if let result = try decodeWrappedType(requestedType, value) {
  59. return result
  60. }
  61. // Treat unknown types as dictionaries, so we don't crash old clients when we add types.
  62. }
  63. let decoded = NSMutableDictionary()
  64. try dict.forEach { key, value in
  65. decoded[key] = try decode(value)
  66. }
  67. return decoded
  68. } else if let array = object as? NSArray {
  69. let decoded = NSMutableArray(capacity: array.count)
  70. try array.forEach { element in
  71. try decoded.add(decode(element) as Any)
  72. }
  73. return decoded
  74. } else if object is NSNumber || object is NSString || object is NSNull {
  75. return object as AnyObject
  76. }
  77. throw Error.unsupportedType(typeName: typeName(of: object))
  78. }
  79. // MARK: - Private Helpers
  80. private func typeName(of value: Any) -> String {
  81. String(describing: type(of: value))
  82. }
  83. private func encodeNumber(_ number: NSNumber) throws -> AnyObject {
  84. // Recover the underlying type of the number, using the method described here:
  85. // http://stackoverflow.com/questions/2518761/get-type-of-nsnumber
  86. let cType = number.objCType
  87. // Type Encoding values taken from
  88. // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/
  89. // Articles/ocrtTypeEncodings.html
  90. switch cType[0] {
  91. case CChar("q".utf8.first!):
  92. // "long long" might be larger than JS supports, so make it a string.
  93. return ["@type": Constants.longType, "value": "\(number)"] as AnyObject
  94. case CChar("Q".utf8.first!):
  95. // "unsigned long long" might be larger than JS supports, so make it a string.
  96. return ["@type": Constants.unsignedLongType,
  97. "value": "\(number)"] as AnyObject
  98. case CChar("i".utf8.first!),
  99. CChar("s".utf8.first!),
  100. CChar("l".utf8.first!),
  101. CChar("I".utf8.first!),
  102. CChar("S".utf8.first!):
  103. // If it"s an integer that isn"t too long, so just use the number.
  104. return number
  105. case CChar("f".utf8.first!), CChar("d".utf8.first!):
  106. // It"s a float/double that"s not too large.
  107. return number
  108. case CChar("B".utf8.first!), CChar("c".utf8.first!), CChar("C".utf8.first!):
  109. // Boolean values are weird.
  110. //
  111. // On arm64, objCType of a BOOL-valued NSNumber will be "c", even though @encode(BOOL)
  112. // returns "B". "c" is the same as @encode(signed char). Unfortunately this means that
  113. // legitimate usage of signed chars is impossible, but this should be rare.
  114. //
  115. // Just return Boolean values as-is.
  116. return number
  117. default:
  118. // All documented codes should be handled above, so this shouldn"t happen.
  119. throw Error.unknownNumberType(charValue: String(cType[0]), number: number)
  120. }
  121. }
  122. private func decodeWrappedType(_ type: String, _ value: String) throws -> AnyObject? {
  123. switch type {
  124. case Constants.longType:
  125. let formatter = NumberFormatter()
  126. guard let n = formatter.number(from: value) else {
  127. throw Error.invalidValueForType(value: value, requestedType: type)
  128. }
  129. return n
  130. case Constants.unsignedLongType:
  131. // NSNumber formatter doesn't handle unsigned long long, so we have to parse it.
  132. let str = (value as NSString).utf8String
  133. var endPtr: UnsafeMutablePointer<CChar>?
  134. let returnValue = UInt64(strtoul(str, &endPtr, 10))
  135. guard String(returnValue) == value else {
  136. throw Error.invalidValueForType(value: value, requestedType: type)
  137. }
  138. return NSNumber(value: returnValue)
  139. default:
  140. return nil
  141. }
  142. }
  143. }