FSTPath.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "Firestore/Source/Model/FSTPath.h"
  17. #import "Firestore/Source/Model/FSTDocumentKey.h"
  18. #import "Firestore/Source/Util/FSTAssert.h"
  19. #import "Firestore/Source/Util/FSTClasses.h"
  20. #import "Firestore/Source/Util/FSTUsageValidation.h"
  21. NS_ASSUME_NONNULL_BEGIN
  22. @interface FSTPath ()
  23. /** An underlying array of which a subset of elements are the segments of the path. */
  24. @property(strong, nonatomic) NSArray<NSString *> *segments;
  25. /** The index into the segments array of the first segment in this path. */
  26. @property int offset;
  27. @end
  28. @implementation FSTPath
  29. /**
  30. * Designated initializer.
  31. *
  32. * @param segments The underlying array of segments for the path.
  33. * @param offset The starting index in the underlying array for the subarray to use.
  34. * @param length The length of the subarray to use.
  35. */
  36. - (instancetype)initWithSegments:(NSArray<NSString *> *)segments
  37. offset:(int)offset
  38. length:(int)length {
  39. FSTAssert(offset <= segments.count, @"offset %d out of range %d", offset, (int)segments.count);
  40. FSTAssert(length <= segments.count - offset, @"offset %d out of range %d", offset,
  41. (int)segments.count - offset);
  42. if (self = [super init]) {
  43. _segments = segments;
  44. _offset = offset;
  45. _length = length;
  46. }
  47. return self;
  48. }
  49. - (BOOL)isEqual:(id)object {
  50. if (self == object) {
  51. return YES;
  52. }
  53. if (![object isKindOfClass:[FSTPath class]]) {
  54. return NO;
  55. }
  56. FSTPath *path = object;
  57. return [self compare:path] == NSOrderedSame;
  58. }
  59. - (NSUInteger)hash {
  60. NSUInteger hash = 0;
  61. for (int i = 0; i < self.length; ++i) {
  62. hash += [self segmentAtIndex:i].hash;
  63. }
  64. return hash;
  65. }
  66. - (NSString *)description {
  67. return [self canonicalString];
  68. }
  69. - (id)objectAtIndexedSubscript:(int)index {
  70. return [self segmentAtIndex:index];
  71. }
  72. - (NSString *)segmentAtIndex:(int)index {
  73. FSTAssert(index < self.length, @"index %d out of range", index);
  74. return self.segments[self.offset + index];
  75. }
  76. - (NSString *)firstSegment {
  77. FSTAssert(!self.isEmpty, @"Cannot call firstSegment on empty path");
  78. return [self segmentAtIndex:0];
  79. }
  80. - (NSString *)lastSegment {
  81. FSTAssert(!self.isEmpty, @"Cannot call lastSegment on empty path");
  82. return [self segmentAtIndex:self.length - 1];
  83. }
  84. - (NSComparisonResult)compare:(FSTPath *)other {
  85. int length = MIN(self.length, other.length);
  86. for (int i = 0; i < length; ++i) {
  87. NSString *left = [self segmentAtIndex:i];
  88. NSString *right = [other segmentAtIndex:i];
  89. NSComparisonResult result = [left compare:right];
  90. if (result != NSOrderedSame) {
  91. return result;
  92. }
  93. }
  94. if (self.length < other.length) {
  95. return NSOrderedAscending;
  96. }
  97. if (self.length > other.length) {
  98. return NSOrderedDescending;
  99. }
  100. return NSOrderedSame;
  101. }
  102. - (instancetype)pathWithSegments:(NSArray<NSString *> *)segments
  103. offset:(int)offset
  104. length:(int)length {
  105. return [[[self class] alloc] initWithSegments:segments offset:offset length:length];
  106. }
  107. - (instancetype)pathByAppendingSegment:(NSString *)segment {
  108. int newLength = self.length + 1;
  109. NSMutableArray<NSString *> *segments = [NSMutableArray arrayWithCapacity:newLength];
  110. for (int i = 0; i < self.length; ++i) {
  111. [segments addObject:self[i]];
  112. }
  113. [segments addObject:segment];
  114. return [self pathWithSegments:segments offset:0 length:newLength];
  115. }
  116. - (instancetype)pathByAppendingPath:(FSTPath *)path {
  117. int newLength = self.length + path.length;
  118. NSMutableArray<NSString *> *segments = [NSMutableArray arrayWithCapacity:newLength];
  119. for (int i = 0; i < self.length; ++i) {
  120. [segments addObject:self[i]];
  121. }
  122. for (int i = 0; i < path.length; ++i) {
  123. [segments addObject:path[i]];
  124. }
  125. return [self pathWithSegments:segments offset:0 length:newLength];
  126. }
  127. - (BOOL)isEmpty {
  128. return self.length == 0;
  129. }
  130. - (instancetype)pathByRemovingFirstSegment {
  131. FSTAssert(!self.isEmpty, @"Cannot call pathByRemovingFirstSegment on empty path");
  132. return [self pathWithSegments:self.segments offset:self.offset + 1 length:self.length - 1];
  133. }
  134. - (instancetype)pathByRemovingFirstSegments:(int)count {
  135. FSTAssert(self.length >= count, @"pathByRemovingFirstSegments:%d on path of length %d", count,
  136. self.length);
  137. return
  138. [self pathWithSegments:self.segments offset:self.offset + count length:self.length - count];
  139. }
  140. - (instancetype)pathByRemovingLastSegment {
  141. FSTAssert(!self.isEmpty, @"Cannot call pathByRemovingLastSegment on empty path");
  142. return [self pathWithSegments:self.segments offset:self.offset length:self.length - 1];
  143. }
  144. - (BOOL)isPrefixOfPath:(FSTPath *)other {
  145. if (other.length < self.length) {
  146. return NO;
  147. }
  148. for (int i = 0; i < self.length; ++i) {
  149. if (![self[i] isEqual:other[i]]) {
  150. return NO;
  151. }
  152. }
  153. return YES;
  154. }
  155. /** Returns a standardized string representation of this path. */
  156. - (NSString *)canonicalString {
  157. @throw FSTAbstractMethodException(); // NOLINT
  158. }
  159. @end
  160. @implementation FSTFieldPath
  161. + (instancetype)pathWithSegments:(NSArray<NSString *> *)segments {
  162. return [[FSTFieldPath alloc] initWithSegments:segments offset:0 length:(int)segments.count];
  163. }
  164. + (instancetype)pathWithServerFormat:(NSString *)fieldPath {
  165. NSMutableArray<NSString *> *segments = [NSMutableArray array];
  166. // TODO(b/37244157): Once we move to v1beta1, we should make this more strict. Right now, it
  167. // allows non-identifier path components, even if they aren't escaped. Technically, this will
  168. // mangle paths with backticks in them used in v1alpha1, but that's fine.
  169. const char *source = [fieldPath UTF8String];
  170. char *segment = (char *)malloc(strlen(source) + 1);
  171. char *segmentEnd = segment;
  172. // If we're inside '`' backticks, then we should ignore '.' dots.
  173. BOOL inBackticks = NO;
  174. char c;
  175. do {
  176. // Examine current character. This is legit even on zero-length strings because there's always
  177. // a null terminator.
  178. c = *source++;
  179. switch (c) {
  180. case '\0': // Falls through
  181. case '.':
  182. if (!inBackticks) {
  183. // Segment is complete
  184. *segmentEnd = '\0';
  185. if (segment == segmentEnd) {
  186. FSTThrowInvalidArgument(
  187. @"Invalid field path (%@). Paths must not be empty, begin with "
  188. @"'.', end with '.', or contain '..'",
  189. fieldPath);
  190. }
  191. [segments addObject:[NSString stringWithUTF8String:segment]];
  192. segmentEnd = segment;
  193. } else {
  194. // copy into the current segment
  195. *segmentEnd++ = c;
  196. }
  197. break;
  198. case '`':
  199. if (inBackticks) {
  200. inBackticks = NO;
  201. } else {
  202. inBackticks = YES;
  203. }
  204. break;
  205. case '\\':
  206. // advance to escaped character
  207. c = *source++;
  208. // TODO(b/37244157): Make this a user-facing exception once we finalize field escaping.
  209. FSTAssert(c != '\0', @"Trailing escape characters not allowed in %@", fieldPath);
  210. // Fall through
  211. default:
  212. // copy into the current segment
  213. *segmentEnd++ = c;
  214. break;
  215. }
  216. } while (c);
  217. FSTAssert(!inBackticks, @"Unterminated ` in path %@", fieldPath);
  218. free(segment);
  219. return [FSTFieldPath pathWithSegments:segments];
  220. }
  221. + (instancetype)keyFieldPath {
  222. static FSTFieldPath *keyFieldPath;
  223. static dispatch_once_t onceToken;
  224. dispatch_once(&onceToken, ^{
  225. keyFieldPath = [FSTFieldPath pathWithSegments:@[ kDocumentKeyPath ]];
  226. });
  227. return keyFieldPath;
  228. }
  229. + (instancetype)emptyPath {
  230. static FSTFieldPath *emptyPath;
  231. static dispatch_once_t onceToken;
  232. dispatch_once(&onceToken, ^{
  233. emptyPath = [FSTFieldPath pathWithSegments:@[]];
  234. });
  235. return emptyPath;
  236. }
  237. /** Return YES if the string could be used as a segment in a field path without escaping. */
  238. + (BOOL)isValidIdentifier:(NSString *)segment {
  239. if (segment.length == 0) {
  240. return NO;
  241. }
  242. unichar first = [segment characterAtIndex:0];
  243. if (first != '_' && (first < 'a' || first > 'z') && (first < 'A' || first > 'Z')) {
  244. return NO;
  245. }
  246. for (int i = 1; i < segment.length; i++) {
  247. unichar c = [segment characterAtIndex:i];
  248. if (c != '_' && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9')) {
  249. return NO;
  250. }
  251. }
  252. return YES;
  253. }
  254. - (BOOL)isKeyFieldPath {
  255. return [self isEqual:FSTFieldPath.keyFieldPath];
  256. }
  257. - (NSString *)canonicalString {
  258. NSMutableString *result = [NSMutableString string];
  259. for (int i = 0; i < self.length; i++) {
  260. if (i > 0) {
  261. [result appendString:@"."];
  262. }
  263. NSString *escaped = [self[i] stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
  264. escaped = [escaped stringByReplacingOccurrencesOfString:@"`" withString:@"\\`"];
  265. if (![FSTFieldPath isValidIdentifier:escaped]) {
  266. escaped = [NSString stringWithFormat:@"`%@`", escaped];
  267. }
  268. [result appendString:escaped];
  269. }
  270. return result;
  271. }
  272. @end
  273. @implementation FSTResourcePath
  274. + (instancetype)pathWithSegments:(NSArray<NSString *> *)segments {
  275. return [[FSTResourcePath alloc] initWithSegments:segments offset:0 length:(int)segments.count];
  276. }
  277. + (instancetype)pathWithString:(NSString *)resourcePath {
  278. // NOTE: The client is ignorant of any path segments containing escape sequences (e.g. __id123__)
  279. // and just passes them through raw (they exist for legacy reasons and should not be used
  280. // frequently).
  281. if ([resourcePath rangeOfString:@"//"].location != NSNotFound) {
  282. FSTThrowInvalidArgument(@"Invalid path (%@). Paths must not contain // in them.", resourcePath);
  283. }
  284. NSMutableArray *segments = [[resourcePath componentsSeparatedByString:@"/"] mutableCopy];
  285. // We may still have an empty segment at the beginning or end if they had a leading or trailing
  286. // slash (which we allow).
  287. [segments removeObject:@""];
  288. return [self pathWithSegments:segments];
  289. }
  290. - (NSString *)canonicalString {
  291. // NOTE: The client is ignorant of any path segments containing escape sequences (e.g. __id123__)
  292. // and just passes them through raw (they exist for legacy reasons and should not be used
  293. // frequently).
  294. NSMutableString *result = [NSMutableString string];
  295. for (int i = 0; i < self.length; i++) {
  296. if (i > 0) {
  297. [result appendString:@"/"];
  298. }
  299. [result appendString:self[i]];
  300. }
  301. return result;
  302. }
  303. @end
  304. NS_ASSUME_NONNULL_END