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,9 @@ 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
+ let number = try unwrapNumber ( wrappedNumber) {
68
+ return number
83
69
}
84
70
85
71
let decoded = NSMutableDictionary ( )
@@ -106,73 +92,70 @@ final class FunctionsSerializer: Sendable {
106
92
String ( describing: type ( of: value) )
107
93
}
108
94
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
-
95
+ private func wrapNumberIfNeeded( _ number: NSNumber ) -> Any {
96
+ switch String ( cString: number. objCType) {
97
+ case " q " :
98
+ // "long long" might be larger than JS supports, so make it a string:
99
+ return WrappedNumber ( type: . long, value: " \( number) " ) . encoded
100
+ case " Q " :
101
+ // "unsigned long long" might be larger than JS supports, so make it a string:
102
+ return WrappedNumber ( type: . unsignedLong, value: " \( number) " ) . encoded
149
103
default :
150
- // All documented codes should be handled above , so this shouldn"t happen.
151
- throw Error . unknownNumberType ( charValue : String ( cType [ 0 ] ) , number: number )
104
+ // All other types should fit JS limits , so return the number as is:
105
+ return number
152
106
}
153
107
}
154
108
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)
109
+ private func unwrapNumber( _ wrapped: WrappedNumber ) throws ( Error) -> ( any Numeric ) ? {
110
+ switch wrapped. type {
111
+ case . long:
112
+ guard let n = Int ( wrapped. value) else {
113
+ throw . failedToParseWrappedNumber( wrapped)
114
+ }
115
+ return n
116
+ case . unsignedLong:
117
+ guard let n = UInt ( wrapped. value) else {
118
+ throw . failedToParseWrappedNumber( wrapped)
161
119
}
162
120
return n
121
+ }
122
+ }
123
+ }
124
+
125
+ // MARK: - WrappedNumber
126
+
127
+ extension FunctionsSerializer {
128
+ struct WrappedNumber {
129
+ let type : NumberType
130
+ let value : String
131
+
132
+ // When / if objects are encoded / decoded using `Codable`,
133
+ // these two `init`s and `encoded` won’t be needed anymore:
134
+
135
+ init ( type: NumberType , value: String ) {
136
+ self . type = type
137
+ self . value = value
138
+ }
163
139
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 )
140
+ init ? ( from dictionary : NSDictionary ) {
141
+ guard
142
+ let typeString = dictionary [ " @type " ] as? String ,
143
+ let type = NumberType ( rawValue : typeString ) ,
144
+ let value = dictionary [ " value " ] as? String
145
+ else {
146
+ return nil
171
147
}
172
- return NSNumber ( value: returnValue)
173
148
174
- default :
175
- return nil
149
+ self . init ( type: type, value: value)
150
+ }
151
+
152
+ var encoded : [ String : String ] {
153
+ [ " @type " : type. rawValue, " value " : value]
154
+ }
155
+
156
+ enum NumberType : String {
157
+ case long = " type.googleapis.com/google.protobuf.Int64Value "
158
+ case unsignedLong = " type.googleapis.com/google.protobuf.UInt64Value "
176
159
}
177
160
}
178
161
}
0 commit comments