field_path.cc 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /*
  2. * Copyright 2018 Google LLC
  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. #include "Firestore/core/src/model/field_path.h"
  17. #include <algorithm>
  18. #include <utility>
  19. #include "Firestore/core/src/util/exception.h"
  20. #include "Firestore/core/src/util/hard_assert.h"
  21. #include "Firestore/core/src/util/no_destructor.h"
  22. #include "Firestore/core/src/util/status.h"
  23. #include "Firestore/core/src/util/statusor.h"
  24. #include "absl/strings/str_join.h"
  25. #include "absl/strings/str_replace.h"
  26. #include "absl/strings/str_split.h"
  27. namespace firebase {
  28. namespace firestore {
  29. namespace model {
  30. namespace {
  31. using util::NoDestructor;
  32. using util::Status;
  33. using util::StatusOr;
  34. using util::StringFormat;
  35. using util::ThrowInvalidArgument;
  36. /**
  37. * True if the string could be used as a segment in a field path without
  38. * escaping. Valid identifies follow the regex [a-zA-Z_][a-zA-Z0-9_]*
  39. */
  40. bool IsValidIdentifier(const std::string& segment) {
  41. if (segment.empty()) {
  42. return false;
  43. }
  44. // Note: strictly speaking, only digits are guaranteed by the Standard to
  45. // be a contiguous range, while alphabetic characters may have gaps. Ignoring
  46. // this peculiarity, because it doesn't affect the platforms that Firestore
  47. // supports.
  48. const unsigned char first = segment.front();
  49. if (first != '_' && (first < 'a' || first > 'z') &&
  50. (first < 'A' || first > 'Z')) {
  51. return false;
  52. }
  53. for (size_t i = 1; i != segment.size(); ++i) {
  54. const unsigned char c = segment[i];
  55. if (c != '_' && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') &&
  56. (c < '0' || c > '9')) {
  57. return false;
  58. }
  59. }
  60. return true;
  61. }
  62. /** A custom formatter to be used with absl::StrJoin(). */
  63. struct JoinEscaped {
  64. static std::string escaped_segment(const std::string& segment) {
  65. auto escaped = absl::StrReplaceAll(segment, {{"\\", "\\\\"}, {"`", "\\`"}});
  66. const bool needs_escaping = !IsValidIdentifier(escaped);
  67. if (needs_escaping) {
  68. escaped.insert(escaped.begin(), '`');
  69. escaped.push_back('`');
  70. }
  71. return escaped;
  72. }
  73. template <typename T>
  74. void operator()(T* out, const std::string& segment) {
  75. out->append(escaped_segment(segment));
  76. }
  77. };
  78. } // namespace
  79. constexpr const char* FieldPath::kDocumentKeyPath;
  80. FieldPath FieldPath::FromDotSeparatedString(const std::string& path) {
  81. return FromDotSeparatedStringView(path);
  82. }
  83. FieldPath FieldPath::FromDotSeparatedStringView(absl::string_view path) {
  84. if (path.find_first_of("~*/[]") != absl::string_view::npos) {
  85. ThrowInvalidArgument(
  86. "Invalid field path (%s). Paths must not contain '~', '*', '/', '[', "
  87. "or ']'",
  88. path);
  89. }
  90. SegmentsT segments =
  91. absl::StrSplit(path, '.', [path](absl::string_view segment) {
  92. if (segment.empty()) {
  93. ThrowInvalidArgument(
  94. "Invalid field path (%s). Paths must not be empty, begin with "
  95. "'.', end with '.', or contain '..'",
  96. path);
  97. }
  98. return true;
  99. });
  100. return FieldPath(std::move(segments));
  101. }
  102. StatusOr<FieldPath> FieldPath::FromServerFormat(const std::string& path) {
  103. return FromServerFormatView(path);
  104. }
  105. StatusOr<FieldPath> FieldPath::FromServerFormatView(absl::string_view path) {
  106. SegmentsT segments;
  107. std::string segment;
  108. segment.reserve(path.size());
  109. Status status;
  110. const auto finish_segment = [&segments, &segment, &path] {
  111. if (segment.empty()) {
  112. return Status{
  113. Error::kErrorInvalidArgument,
  114. StringFormat(
  115. "Invalid field path (%s). Paths must not be empty, begin with "
  116. "'.', end with '.', or contain '..'",
  117. path)};
  118. }
  119. // Move operation will clear segment, but capacity will remain the same
  120. // (not, strictly speaking, required by the standard, but true in practice).
  121. segments.push_back(std::move(segment));
  122. return Status::OK();
  123. };
  124. // Inside backticks, dots are treated literally.
  125. bool inside_backticks = false;
  126. size_t i = 0;
  127. while (i < path.size()) {
  128. const char c = path[i];
  129. // std::string (and string_view) may contain embedded nulls. For full
  130. // compatibility with Objective-C behavior, finish upon encountering the
  131. // first terminating null.
  132. if (c == '\0') {
  133. break;
  134. }
  135. switch (c) {
  136. case '.':
  137. if (!inside_backticks) {
  138. status = finish_segment();
  139. } else {
  140. segment += c;
  141. }
  142. break;
  143. case '`':
  144. inside_backticks = !inside_backticks;
  145. break;
  146. case '\\':
  147. if (i + 1 == path.size()) {
  148. status =
  149. Status{Error::kErrorInvalidArgument,
  150. StringFormat(
  151. "Trailing escape characters not allowed in %s", path)};
  152. } else {
  153. ++i;
  154. segment += path[i];
  155. }
  156. break;
  157. default:
  158. segment += c;
  159. break;
  160. }
  161. ++i;
  162. if (!status.ok()) return status;
  163. }
  164. status = finish_segment();
  165. if (!status.ok()) return status;
  166. if (inside_backticks) {
  167. return Status{Error::kErrorInvalidArgument,
  168. StringFormat("Unterminated ` in path %s", path)};
  169. }
  170. return FieldPath{std::move(segments)};
  171. }
  172. const FieldPath& FieldPath::EmptyPath() {
  173. static const NoDestructor<FieldPath> empty_path;
  174. return *empty_path;
  175. }
  176. const FieldPath& FieldPath::KeyFieldPath() {
  177. static const NoDestructor<FieldPath> key_field_path(
  178. FieldPath{FieldPath::kDocumentKeyPath});
  179. return *key_field_path;
  180. }
  181. bool FieldPath::IsKeyFieldPath() const {
  182. return size() == 1 && first_segment() == FieldPath::kDocumentKeyPath;
  183. }
  184. std::string FieldPath::CanonicalString() const {
  185. return absl::StrJoin(begin(), end(), ".", JoinEscaped());
  186. }
  187. void FieldPath::ValidateSegments(const SegmentsT& segments) {
  188. if (segments.empty()) {
  189. ThrowInvalidArgument(
  190. "Invalid field path. Provided names must not be empty.");
  191. }
  192. for (size_t i = 0; i < segments.size(); i++) {
  193. if (segments[i].empty()) {
  194. ThrowInvalidArgument(
  195. "Invalid field name at index %s. Field names must not be empty.", i);
  196. }
  197. }
  198. }
  199. } // namespace model
  200. } // namespace firestore
  201. } // namespace firebase