14
14
15
15
import Foundation
16
16
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
-
23
17
extension FunctionsSerializer {
24
18
enum Error : Swift . Error {
25
19
case unsupportedType( typeName: String )
26
- case unknownNumberType( charValue: String , number: NSNumber )
27
- case invalidValueForType( value: String , requestedType: String )
20
+ case failedToParseWrappedNumber( WrappedNumber )
28
21
}
29
22
}
30
23
@@ -41,8 +34,8 @@ final class FunctionsSerializer: Sendable {
41
34
func encode( _ object: Any ) throws -> Any {
42
35
if object is NSNull {
43
36
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 )
46
39
} else if object is NSString {
47
40
return object
48
41
} else if let dict = object as? NSDictionary {
@@ -70,16 +63,8 @@ final class FunctionsSerializer: Sendable {
70
63
func decode( _ object: Any ) throws -> Any {
71
64
// Return these types as is. PORTING NOTE: Moved from the bottom of the func for readability.
72
65
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)
83
68
}
84
69
85
70
let decoded = NSMutableDictionary ( )
@@ -106,73 +91,70 @@ final class FunctionsSerializer: Sendable {
106
91
String ( describing: type ( of: value) )
107
92
}
108
93
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
149
102
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
152
105
}
153
106
}
154
107
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( wrapped)
113
+ }
114
+ return n
115
+ case . unsignedLong:
116
+ guard let n = UInt ( wrapped. value) else {
117
+ throw . failedToParseWrappedNumber( wrapped)
161
118
}
162
119
return n
120
+ }
121
+ }
122
+ }
123
+
124
+ // MARK: - WrappedNumber
125
+
126
+ extension FunctionsSerializer {
127
+ struct WrappedNumber {
128
+ let type : NumberType
129
+ let value : String
130
+
131
+ // When / if objects are encoded / decoded using `Codable`,
132
+ // these two `init`s and `encoded` won’t be needed anymore:
133
+
134
+ init ( type: NumberType , value: String ) {
135
+ self . type = type
136
+ self . value = value
137
+ }
163
138
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 )
139
+ init ? ( from dictionary : NSDictionary ) {
140
+ guard
141
+ let typeString = dictionary [ " @type " ] as? String ,
142
+ let type = NumberType ( rawValue : typeString ) ,
143
+ let value = dictionary [ " value " ] as? String
144
+ else {
145
+ return nil
171
146
}
172
- return NSNumber ( value: returnValue)
173
147
174
- default :
175
- return nil
148
+ self . init ( type: type, value: value)
149
+ }
150
+
151
+ var encoded : [ String : String ] {
152
+ [ " @type " : type. rawValue, " value " : value]
153
+ }
154
+
155
+ enum NumberType : String {
156
+ case long = " type.googleapis.com/google.protobuf.Int64Value "
157
+ case unsignedLong = " type.googleapis.com/google.protobuf.UInt64Value "
176
158
}
177
159
}
178
160
}
0 commit comments