FUNSerializer.m 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. // Copyright 2017 Google
  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 "FUNSerializer.h"
  15. #import "FIRError.h"
  16. #import "FUNUsageValidation.h"
  17. NS_ASSUME_NONNULL_BEGIN
  18. static NSString *const kLongType = @"type.googleapis.com/google.protobuf.Int64Value";
  19. static NSString *const kUnsignedLongType = @"type.googleapis.com/google.protobuf.UInt64Value";
  20. static NSString *const kDateType = @"type.googleapis.com/google.protobuf.Timestamp";
  21. @interface FUNSerializer () {
  22. NSDateFormatter *_dateFormatter;
  23. }
  24. @end
  25. @implementation FUNSerializer
  26. - (instancetype)init {
  27. self = [super init];
  28. if (self) {
  29. _dateFormatter = [[NSDateFormatter alloc] init];
  30. _dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
  31. _dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
  32. }
  33. return self;
  34. }
  35. - (id)encodeNumber:(NSNumber *)number {
  36. // Recover the underlying type of the number, using the method described here:
  37. // http://stackoverflow.com/questions/2518761/get-type-of-nsnumber
  38. const char *cType = [number objCType];
  39. // Type Encoding values taken from
  40. // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/
  41. // Articles/ocrtTypeEncodings.html
  42. switch (cType[0]) {
  43. case 'q':
  44. // "long long" might be larger than JS supports, so make it a string.
  45. return @{
  46. @"@type" : kLongType,
  47. @"value" : [NSString stringWithFormat:@"%@", number],
  48. };
  49. case 'Q':
  50. // "unsigned long long" might be larger than JS supports, so make it a string.
  51. return @{
  52. @"@type" : kUnsignedLongType,
  53. @"value" : [NSString stringWithFormat:@"%@", number],
  54. };
  55. case 'i':
  56. case 's':
  57. case 'l':
  58. case 'I':
  59. case 'S':
  60. // If it's an integer that isn't too long, so just use the number.
  61. return number;
  62. case 'f':
  63. case 'd':
  64. // It's a float/double that's not too large.
  65. return number;
  66. case 'B':
  67. case 'c':
  68. case 'C':
  69. // Boolean values are weird.
  70. //
  71. // On arm64, objCType of a BOOL-valued NSNumber will be "c", even though @encode(BOOL)
  72. // returns "B". "c" is the same as @encode(signed char). Unfortunately this means that
  73. // legitimate usage of signed chars is impossible, but this should be rare.
  74. //
  75. // Just return Boolean values as-is.
  76. return number;
  77. default:
  78. // All documented codes should be handled above, so this shouldn't happen.
  79. FUNThrowInvalidArgument(@"Unknown NSNumber objCType %s on %@", cType, number);
  80. }
  81. }
  82. - (id)encode:(id)object {
  83. if ([object isEqual:[NSNull null]]) {
  84. return object;
  85. }
  86. if ([object isKindOfClass:[NSNumber class]]) {
  87. return [self encodeNumber:object];
  88. }
  89. if ([object isKindOfClass:[NSString class]]) {
  90. return object;
  91. }
  92. if ([object isKindOfClass:[NSDictionary class]]) {
  93. NSMutableDictionary *encoded = [NSMutableDictionary dictionary];
  94. [object
  95. enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
  96. encoded[key] = [self encode:obj];
  97. }];
  98. return encoded;
  99. }
  100. if ([object isKindOfClass:[NSArray class]]) {
  101. NSMutableArray *encoded = [NSMutableArray arrayWithCapacity:[object count]];
  102. for (id obj in object) {
  103. [encoded addObject:[self encode:obj]];
  104. }
  105. return encoded;
  106. }
  107. // TODO(klimt): Add this back when we support NSDate.
  108. /*
  109. if ([object isKindOfClass:[NSDate class]]) {
  110. NSString *iso8601 = [_dateFormatter stringFromDate:object];
  111. return @{
  112. @"@type" : kDateType,
  113. @"value" : iso8601,
  114. };
  115. }
  116. */
  117. FUNThrowInvalidArgument(@"Unsupported type: %@ for value %@", NSStringFromClass([object class]),
  118. object);
  119. }
  120. NSError *FUNInvalidNumberError(id value, id wrapped) {
  121. NSString *description = [NSString stringWithFormat:@"Invalid number: %@ for %@", value, wrapped];
  122. NSDictionary *userInfo = @{
  123. NSLocalizedDescriptionKey : description,
  124. };
  125. return [NSError errorWithDomain:FIRFunctionsErrorDomain
  126. code:FIRFunctionsErrorCodeInternal
  127. userInfo:userInfo];
  128. }
  129. - (id)decodeWrappedType:(NSDictionary *)wrapped error:(NSError **)error {
  130. NSAssert(error, @"error must not be nil");
  131. NSString *type = wrapped[@"@type"];
  132. NSString *value = wrapped[@"value"];
  133. if (!value) {
  134. return nil;
  135. }
  136. if ([type isEqualToString:kLongType]) {
  137. NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
  138. NSNumber *n = [formatter numberFromString:value];
  139. if (n == nil) {
  140. *error = FUNInvalidNumberError(value, wrapped);
  141. return nil;
  142. }
  143. return n;
  144. } else if ([type isEqualToString:kUnsignedLongType]) {
  145. // NSNumber formatter doesn't handle unsigned long long, so we have to parse it.
  146. const char *str = value.UTF8String;
  147. char *end = NULL;
  148. unsigned long long n = strtoull(str, &end, 10);
  149. if (errno == ERANGE) {
  150. // This number was actually too big for an unsigned long long.
  151. *error = FUNInvalidNumberError(value, wrapped);
  152. return nil;
  153. }
  154. if (*end) {
  155. // The whole string wasn't parsed.
  156. *error = FUNInvalidNumberError(value, wrapped);
  157. return nil;
  158. }
  159. return @(n);
  160. }
  161. return nil;
  162. }
  163. - (id)decode:(id)object error:(NSError **)error {
  164. NSAssert(error, @"error must not be nil");
  165. if ([object isKindOfClass:[NSDictionary class]]) {
  166. if (object[@"@type"]) {
  167. id result = [self decodeWrappedType:object error:error];
  168. if (*error) {
  169. return nil;
  170. }
  171. if (result) {
  172. return result;
  173. }
  174. // Treat unknown types as dictionaries, so we don't crash old clients when we add types.
  175. }
  176. NSMutableDictionary *decoded = [NSMutableDictionary dictionary];
  177. __block NSError *decodeError = nil;
  178. [object
  179. enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
  180. id decodedItem = [self decode:obj error:&decodeError];
  181. if (decodeError) {
  182. *stop = YES;
  183. return;
  184. }
  185. decoded[key] = decodedItem;
  186. }];
  187. if (decodeError) {
  188. *error = decodeError;
  189. return nil;
  190. }
  191. return decoded;
  192. }
  193. if ([object isKindOfClass:[NSArray class]]) {
  194. NSMutableArray *result = [NSMutableArray arrayWithCapacity:[object count]];
  195. for (id obj in object) {
  196. id decoded = [self decode:obj error:error];
  197. if (*error) {
  198. return nil;
  199. }
  200. [result addObject:decoded];
  201. }
  202. return result;
  203. }
  204. if ([object isKindOfClass:[NSNumber class]] || [object isKindOfClass:[NSString class]] ||
  205. [object isEqual:[NSNull null]]) {
  206. return object;
  207. }
  208. FUNThrowInvalidArgument(@"Unsupported type: %@ for value %@", NSStringFromClass([object class]),
  209. object);
  210. }
  211. @end
  212. NS_ASSUME_NONNULL_END