FUNSerializer.m 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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 "FirebaseFunctions/Sources/FUNSerializer.h"
  15. #import "FirebaseFunctions/Sources/FUNUsageValidation.h"
  16. #import "FirebaseFunctions/Sources/Public/FirebaseFunctions/FIRError.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. - (nullable 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. if (error != NULL) {
  141. *error = FUNInvalidNumberError(value, wrapped);
  142. }
  143. return nil;
  144. }
  145. return n;
  146. } else if ([type isEqualToString:kUnsignedLongType]) {
  147. // NSNumber formatter doesn't handle unsigned long long, so we have to parse it.
  148. const char *str = value.UTF8String;
  149. char *end = NULL;
  150. unsigned long long n = strtoull(str, &end, 10);
  151. if (errno == ERANGE) {
  152. // This number was actually too big for an unsigned long long.
  153. if (error != NULL) {
  154. *error = FUNInvalidNumberError(value, wrapped);
  155. }
  156. return nil;
  157. }
  158. if (*end) {
  159. // The whole string wasn't parsed.
  160. if (error != NULL) {
  161. *error = FUNInvalidNumberError(value, wrapped);
  162. }
  163. return nil;
  164. }
  165. return @(n);
  166. }
  167. return nil;
  168. }
  169. - (nullable id)decode:(id)object error:(NSError **)error {
  170. NSAssert(error, @"error must not be nil");
  171. if ([object isKindOfClass:[NSDictionary class]]) {
  172. if (object[@"@type"]) {
  173. id result = [self decodeWrappedType:object error:error];
  174. if (*error) {
  175. return nil;
  176. }
  177. if (result) {
  178. return result;
  179. }
  180. // Treat unknown types as dictionaries, so we don't crash old clients when we add types.
  181. }
  182. NSMutableDictionary *decoded = [NSMutableDictionary dictionary];
  183. __block NSError *decodeError = nil;
  184. [object
  185. enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
  186. id decodedItem = [self decode:obj error:&decodeError];
  187. if (decodeError) {
  188. *stop = YES;
  189. return;
  190. }
  191. decoded[key] = decodedItem;
  192. }];
  193. if (decodeError) {
  194. if (error != NULL) {
  195. *error = decodeError;
  196. }
  197. return nil;
  198. }
  199. return decoded;
  200. }
  201. if ([object isKindOfClass:[NSArray class]]) {
  202. NSMutableArray *result = [NSMutableArray arrayWithCapacity:[object count]];
  203. for (id obj in object) {
  204. id decoded = [self decode:obj error:error];
  205. if (*error) {
  206. return nil;
  207. }
  208. [result addObject:decoded];
  209. }
  210. return result;
  211. }
  212. if ([object isKindOfClass:[NSNumber class]] || [object isKindOfClass:[NSString class]] ||
  213. [object isEqual:[NSNull null]]) {
  214. return object;
  215. }
  216. FUNThrowInvalidArgument(@"Unsupported type: %@ for value %@", NSStringFromClass([object class]),
  217. object);
  218. }
  219. @end
  220. NS_ASSUME_NONNULL_END