1414
1515import Foundation
1616
17- private enum Constants {
18- static let longType = " type.googleapis.com/google.protobuf.Int64Value "
19- static let unsignedLongType = " type.googleapis.com/google.protobuf.UInt64Value "
20- static let dateType = " type.googleapis.com/google.protobuf.Timestamp "
21- }
22-
2317extension FunctionsSerializer {
2418 enum Error : Swift . Error {
2519 case unsupportedType( typeName: String )
26- case unknownNumberType( charValue: String , number: NSNumber )
27- case invalidValueForType( value: String , requestedType: String )
20+ case failedToParseWrappedNumber( value: String , type: String )
2821 }
2922}
3023
@@ -41,8 +34,8 @@ final class FunctionsSerializer: Sendable {
4134 func encode( _ object: Any ) throws -> Any {
4235 if object is NSNull {
4336 return object
44- } else if object is NSNumber {
45- return try encodeNumber ( object as! NSNumber )
37+ } else if let number = object as? NSNumber {
38+ return wrapNumberIfNeeded ( number )
4639 } else if object is NSString {
4740 return object
4841 } else if let dict = object as? NSDictionary {
@@ -70,16 +63,8 @@ final class FunctionsSerializer: Sendable {
7063 func decode( _ object: Any ) throws -> Any {
7164 // Return these types as is. PORTING NOTE: Moved from the bottom of the func for readability.
7265 if let dict = object as? NSDictionary {
73- if let requestedType = dict [ " @type " ] as? String {
74- guard let value = dict [ " value " ] as? String else {
75- // Seems like we should throw here - but this maintains compatibility.
76- return dict
77- }
78- if let result = try decodeWrappedType ( requestedType, value) {
79- return result
80- }
81-
82- // Treat unknown types as dictionaries, so we don't crash old clients when we add types.
66+ if let wrappedNumber = WrappedNumber ( from: dict) {
67+ return try unwrapNumber ( wrappedNumber)
8368 }
8469
8570 let decoded = NSMutableDictionary ( )
@@ -106,73 +91,76 @@ final class FunctionsSerializer: Sendable {
10691 String ( describing: type ( of: value) )
10792 }
10893
109- private func encodeNumber( _ number: NSNumber ) throws -> AnyObject {
110- // Recover the underlying type of the number, using the method described here:
111- // http://stackoverflow.com/questions/2518761/get-type-of-nsnumber
112- let cType = number. objCType
113-
114- // Type Encoding values taken from
115- // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/
116- // Articles/ocrtTypeEncodings.html
117- switch cType [ 0 ] {
118- case CChar ( " q " . utf8. first!) :
119- // "long long" might be larger than JS supports, so make it a string.
120- return [ " @type " : Constants . longType, " value " : " \( number) " ] as AnyObject
121-
122- case CChar ( " Q " . utf8. first!) :
123- // "unsigned long long" might be larger than JS supports, so make it a string.
124- return [ " @type " : Constants . unsignedLongType,
125- " value " : " \( number) " ] as AnyObject
126-
127- case CChar ( " i " . utf8. first!) ,
128- CChar ( " s " . utf8. first!) ,
129- CChar ( " l " . utf8. first!) ,
130- CChar ( " I " . utf8. first!) ,
131- CChar ( " S " . utf8. first!) :
132- // If it"s an integer that isn"t too long, so just use the number.
133- return number
134-
135- case CChar ( " f " . utf8. first!) , CChar ( " d " . utf8. first!) :
136- // It"s a float/double that"s not too large.
137- return number
138-
139- case CChar ( " B " . utf8. first!) , CChar ( " c " . utf8. first!) , CChar ( " C " . utf8. first!) :
140- // Boolean values are weird.
141- //
142- // On arm64, objCType of a BOOL-valued NSNumber will be "c", even though @encode(BOOL)
143- // returns "B". "c" is the same as @encode(signed char). Unfortunately this means that
144- // legitimate usage of signed chars is impossible, but this should be rare.
145- //
146- // Just return Boolean values as-is.
147- return number
148-
94+ private func wrapNumberIfNeeded( _ number: NSNumber ) -> Any {
95+ switch String ( cString: number. objCType) {
96+ case " q " :
97+ // "long long" might be larger than JS supports, so make it a string:
98+ return WrappedNumber ( type: . long, value: " \( number) " ) . encoded
99+ case " Q " :
100+ // "unsigned long long" might be larger than JS supports, so make it a string:
101+ return WrappedNumber ( type: . unsignedLong, value: " \( number) " ) . encoded
149102 default :
150- // All documented codes should be handled above , so this shouldn"t happen.
151- throw Error . unknownNumberType ( charValue : String ( cType [ 0 ] ) , number: number )
103+ // All other types should fit JS limits , so return the number as is:
104+ return number
152105 }
153106 }
154107
155- private func decodeWrappedType( _ type: String , _ value: String ) throws -> AnyObject ? {
156- switch type {
157- case Constants . longType:
158- let formatter = NumberFormatter ( )
159- guard let n = formatter. number ( from: value) else {
160- throw Error . invalidValueForType ( value: value, requestedType: type)
108+ private func unwrapNumber( _ wrapped: WrappedNumber ) throws ( Error) -> any Numeric {
109+ switch wrapped. type {
110+ case . long:
111+ guard let n = Int ( wrapped. value) else {
112+ throw . failedToParseWrappedNumber(
113+ value: wrapped. value,
114+ type: wrapped. type. rawValue
115+ )
116+ }
117+ return n
118+ case . unsignedLong:
119+ guard let n = UInt ( wrapped. value) else {
120+ throw . failedToParseWrappedNumber(
121+ value: wrapped. value,
122+ type: wrapped. type. rawValue
123+ )
161124 }
162125 return n
126+ }
127+ }
128+ }
129+
130+ // MARK: - WrappedNumber
131+
132+ extension FunctionsSerializer {
133+ private struct WrappedNumber {
134+ let type : NumberType
135+ let value : String
136+
137+ // When / if objects are encoded / decoded using `Codable`,
138+ // these two `init`s and `encoded` won’t be needed anymore:
139+
140+ init ( type: NumberType , value: String ) {
141+ self . type = type
142+ self . value = value
143+ }
163144
164- case Constants . unsignedLongType :
165- // NSNumber formatter doesn't handle unsigned long long, so we have to parse it.
166- let str = ( value as NSString ) . utf8String
167- var endPtr : UnsafeMutablePointer < CChar > ?
168- let returnValue = UInt64 ( strtoul ( str , & endPtr , 10 ) )
169- guard String ( returnValue ) == value else {
170- throw Error . invalidValueForType ( value : value , requestedType : type )
145+ init ? ( from dictionary : NSDictionary ) {
146+ guard
147+ let typeString = dictionary [ " @type " ] as? String ,
148+ let type = NumberType ( rawValue : typeString ) ,
149+ let value = dictionary [ " value " ] as? String
150+ else {
151+ return nil
171152 }
172- return NSNumber ( value: returnValue)
173153
174- default :
175- return nil
154+ self . init ( type: type, value: value)
155+ }
156+
157+ var encoded : [ String : String ] {
158+ [ " @type " : type. rawValue, " value " : value]
159+ }
160+
161+ enum NumberType : String {
162+ case long = " type.googleapis.com/google.protobuf.Int64Value "
163+ case unsignedLong = " type.googleapis.com/google.protobuf.UInt64Value "
176164 }
177165 }
178166}
0 commit comments