From b31fe35eb7a1301e1e6c5d3381b9f3d8054734dd Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Tue, 6 Feb 2018 19:15:54 -0500 Subject: C++ port: port FSTFieldPath and FSTResourcePath to C++ (#749) Similar to Objective-C, FieldPath and ResourcePath share most of their interface (and implementation) by deriving from BasePath (using CRTP, so that factory methods in BasePath can return an instance of the derived class). --- .../src/firebase/firestore/model/CMakeLists.txt | 3 + .../core/src/firebase/firestore/model/base_path.h | 181 +++++++++++++++++++++ .../src/firebase/firestore/model/field_path.cc | 176 ++++++++++++++++++++ .../core/src/firebase/firestore/model/field_path.h | 94 +++++++++++ .../src/firebase/firestore/model/resource_path.cc | 57 +++++++ .../src/firebase/firestore/model/resource_path.h | 84 ++++++++++ 6 files changed, 595 insertions(+) create mode 100644 Firestore/core/src/firebase/firestore/model/base_path.h create mode 100644 Firestore/core/src/firebase/firestore/model/field_path.cc create mode 100644 Firestore/core/src/firebase/firestore/model/field_path.h create mode 100644 Firestore/core/src/firebase/firestore/model/resource_path.cc create mode 100644 Firestore/core/src/firebase/firestore/model/resource_path.h (limited to 'Firestore/core/src/firebase/firestore/model') diff --git a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt index aee0d86..8bdbe18 100644 --- a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt @@ -15,8 +15,11 @@ cc_library( firebase_firestore_model SOURCES + base_path.h database_id.cc database_id.h + field_path.cc + field_path.h field_value.cc field_value.h timestamp.cc diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h new file mode 100644 index 0000000..f5a8ab7 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -0,0 +1,181 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_BASE_PATH_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_BASE_PATH_H_ + +#include +#include +#include +#include +#include +#include + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +namespace firebase { +namespace firestore { +namespace model { +namespace impl { + +/** + * BasePath represents a path sequence in the Firestore database. It is composed + * of an ordered sequence of string segments. + * + * BasePath is reassignable and movable. Apart from those, all other mutating + * operations return new independent instances. + * + * ## Subclassing Notes + * + * BasePath is strictly meant as a base class for concrete implementations. It + * doesn't contain a single virtual method, can't be instantiated, and should + * never be used in any polymorphic way. BasePath is templated to allow static + * factory methods to return objects of the derived class (the expected + * inheritance would use CRTP: struct Derived : BasePath). + */ +template +class BasePath { + protected: + using SegmentsT = std::vector; + + public: + using const_iterator = SegmentsT::const_iterator; + + /** Returns i-th segment of the path. */ + const std::string& operator[](const size_t i) const { + FIREBASE_ASSERT_MESSAGE(i < segments_.size(), "index %u out of range", i); + return segments_[i]; + } + + /** Returns the first segment of the path. */ + const std::string& first_segment() const { + FIREBASE_ASSERT_MESSAGE(!empty(), + "Cannot call first_segment on empty path"); + return segments_[0]; + } + /** Returns the last segment of the path. */ + const std::string& last_segment() const { + FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call last_segment on empty path"); + return segments_[size() - 1]; + } + + size_t size() const { + return segments_.size(); + } + bool empty() const { + return segments_.empty(); + } + + const_iterator begin() const { + return segments_.begin(); + } + const_iterator end() const { + return segments_.end(); + } + + /** + * Returns a new path which is the result of concatenating this path with an + * additional segment. + */ + T Append(const std::string& segment) const { + auto appended = segments_; + appended.push_back(segment); + return T{std::move(appended)}; + } + T Append(std::string&& segment) const { + auto appended = segments_; + appended.push_back(std::move(segment)); + return T{std::move(appended)}; + } + + /** + * Returns a new path which is the result of concatenating this path with an + * another path. + */ + T Append(const T& path) const { + auto appended = segments_; + appended.insert(appended.end(), path.begin(), path.end()); + return T{std::move(appended)}; + } + + /** + * Returns a new path which is the result of omitting the first n segments of + * this path. + */ + T PopFirst(const size_t n = 1) const { + FIREBASE_ASSERT_MESSAGE(n <= size(), + "Cannot call PopFirst(%u) on path of length %u", n, + size()); + return T{begin() + n, end()}; + } + + /** + * Returns a new path which is the result of omitting the last segment of + * this path. + */ + T PopLast() const { + FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call PopLast() on empty path"); + return T{begin(), end() - 1}; + } + + /** + * Returns true if this path is a prefix of the given path. + * + * Empty path is a prefix of any path. Any path is a prefix of itself. + */ + bool IsPrefixOf(const T& rhs) const { + return size() <= rhs.size() && std::equal(begin(), end(), rhs.begin()); + } + + bool operator==(const BasePath& rhs) const { + return segments_ == rhs.segments_; + } + bool operator!=(const BasePath& rhs) const { + return segments_ != rhs.segments_; + } + bool operator<(const BasePath& rhs) const { + return segments_ < rhs.segments_; + } + bool operator>(const BasePath& rhs) const { + return segments_ > rhs.segments_; + } + bool operator<=(const BasePath& rhs) const { + return segments_ <= rhs.segments_; + } + bool operator>=(const BasePath& rhs) const { + return segments_ >= rhs.segments_; + } + + protected: + BasePath() = default; + template + BasePath(const IterT begin, const IterT end) : segments_{begin, end} { + } + BasePath(std::initializer_list list) : segments_{list} { + } + BasePath(SegmentsT&& segments) : segments_{std::move(segments)} { + } + + private: + SegmentsT segments_; +}; + +} // namespace impl +} // namespace model +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_BASE_PATH_H_ diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc new file mode 100644 index 0000000..6c40600 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -0,0 +1,176 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/field_path.h" + +#include +#include + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" +#include "absl/strings/str_join.h" +#include "absl/strings/str_replace.h" +#include "absl/strings/str_split.h" + +namespace firebase { +namespace firestore { +namespace model { + +namespace { + +// TODO(varconst): move to C++ equivalent of FSTDocumentKey.{h,cc} +const char* const kDocumentKeyPath = "__name__"; + +/** + * True if the string could be used as a segment in a field path without + * escaping. Valid identifies follow the regex [a-zA-Z_][a-zA-Z0-9_]* + */ +bool IsValidIdentifier(const std::string& segment) { + if (segment.empty()) { + return false; + } + + // Note: strictly speaking, only digits are guaranteed by the Standard to + // be a contiguous range, while alphabetic characters may have gaps. Ignoring + // this peculiarity, because it doesn't affect the platforms that Firestore + // supports. + const unsigned char first = segment.front(); + if (first != '_' && (first < 'a' || first > 'z') && + (first < 'A' || first > 'Z')) { + return false; + } + for (int i = 1; i != segment.size(); ++i) { + const unsigned char c = segment[i]; + if (c != '_' && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && + (c < '0' || c > '9')) { + return false; + } + } + + return true; +} + +} // namespace + +FieldPath FieldPath::FromServerFormat(const absl::string_view path) { + // TODO(b/37244157): Once we move to v1beta1, we should make this more + // strict. Right now, it allows non-identifier path components, even if they + // aren't escaped. Technically, this will mangle paths with backticks in + // them used in v1alpha1, but that's fine. + + SegmentsT segments; + std::string segment; + segment.reserve(path.size()); + + // string_view doesn't have a c_str() method, because it might not be + // null-terminated. Assertions expect C strings, so construct std::string on + // the fly, so that c_str() might be called on it. + const auto to_string = [](const absl::string_view view) { + return std::string{view.data(), view.data() + view.size()}; + }; + const auto finish_segment = [&segments, &segment, &path, &to_string] { + FIREBASE_ASSERT_MESSAGE( + !segment.empty(), + "Invalid field path (%s). Paths must not be empty, begin with " + "'.', end with '.', or contain '..'", + to_string(path).c_str()); + // Move operation will clear segment, but capacity will remain the same + // (not, strictly speaking, required by the standard, but true in practice). + segments.push_back(std::move(segment)); + }; + + // Inside backticks, dots are treated literally. + bool inside_backticks = false; + int i = 0; + while (i < path.size()) { + const char c = path[i]; + // std::string (and string_view) may contain embedded nulls. For full + // compatibility with Objective C behavior, finish upon encountering the + // first terminating null. + if (c == '\0') { + break; + } + + switch (c) { + case '.': + if (!inside_backticks) { + finish_segment(); + } else { + segment += c; + } + break; + + case '`': + inside_backticks = !inside_backticks; + break; + + case '\\': + // TODO(b/37244157): Make this a user-facing exception once we + // finalize field escaping. + FIREBASE_ASSERT_MESSAGE(i + 1 != path.size(), + "Trailing escape characters not allowed in %s", + to_string(path).c_str()); + ++i; + segment += path[i]; + break; + + default: + segment += c; + break; + } + ++i; + } + finish_segment(); + + FIREBASE_ASSERT_MESSAGE(!inside_backticks, "Unterminated ` in path %s", + to_string(path).c_str()); + + return FieldPath{std::move(segments)}; +} + +const FieldPath& FieldPath::EmptyPath() { + static const FieldPath empty_path; + return empty_path; +} + +const FieldPath& FieldPath::KeyFieldPath() { + static const FieldPath key_field_path{kDocumentKeyPath}; + return key_field_path; +} + +bool FieldPath::IsKeyFieldPath() const { + return size() == 1 && first_segment() == kDocumentKeyPath; +} + +std::string FieldPath::CanonicalString() const { + const auto escaped_segment = [](const std::string& segment) { + auto escaped = absl::StrReplaceAll(segment, {{"\\", "\\\\"}, {"`", "\\`"}}); + const bool needs_escaping = !IsValidIdentifier(escaped); + if (needs_escaping) { + escaped.insert(escaped.begin(), '`'); + escaped.push_back('`'); + } + return escaped; + }; + return absl::StrJoin( + begin(), end(), ".", + [escaped_segment](std::string* out, const std::string& segment) { + out->append(escaped_segment(segment)); + }); +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h new file mode 100644 index 0000000..a8b147e --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -0,0 +1,94 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_ + +#include +#include +#include + +#include "Firestore/core/src/firebase/firestore/model/base_path.h" +#include "absl/strings/string_view.h" + +namespace firebase { +namespace firestore { +namespace model { + +/** + * A dot-separated path for navigating sub-objects within a document. + * + * Immutable; all instances are fully independent. + */ +class FieldPath : public impl::BasePath { + public: + FieldPath() = default; + /** Constructs the path from segments. */ + template + FieldPath(const IterT begin, const IterT end) : BasePath{begin, end} { + } + FieldPath(std::initializer_list list) : BasePath{list} { + } + + /** + * Creates and returns a new path from the server formatted field-path string, + * where path segments are separated by a dot "." and optionally encoded using + * backticks. + */ + static FieldPath FromServerFormat(absl::string_view path); + /** Returns a field path that represents an empty path. */ + static const FieldPath& EmptyPath(); + /** Returns a field path that represents a document key. */ + static const FieldPath& KeyFieldPath(); + + /** Returns a standardized string representation of this path. */ + std::string CanonicalString() const; + /** True if this FieldPath represents a document key. */ + bool IsKeyFieldPath() const; + + bool operator==(const FieldPath& rhs) const { + return BasePath::operator==(rhs); + } + bool operator!=(const FieldPath& rhs) const { + return BasePath::operator!=(rhs); + } + bool operator<(const FieldPath& rhs) const { + return BasePath::operator<(rhs); + } + bool operator>(const FieldPath& rhs) const { + return BasePath::operator>(rhs); + } + bool operator<=(const FieldPath& rhs) const { + return BasePath::operator<=(rhs); + } + bool operator>=(const FieldPath& rhs) const { + return BasePath::operator>=(rhs); + } + + private: + FieldPath(SegmentsT&& segments) : BasePath{std::move(segments)} { + } + + // So that methods of base can construct FieldPath using the private + // constructor. + friend class BasePath; +}; + +} // namespace model +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_ diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.cc b/Firestore/core/src/firebase/firestore/model/resource_path.cc new file mode 100644 index 0000000..36218e9 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/resource_path.cc @@ -0,0 +1,57 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" + +#include +#include + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" +#include "absl/strings/str_join.h" +#include "absl/strings/str_split.h" + +namespace firebase { +namespace firestore { +namespace model { + +ResourcePath ResourcePath::Parse(const absl::string_view path) { + // NOTE: The client is ignorant of any path segments containing escape + // sequences (e.g. __id123__) and just passes them through raw (they exist + // for legacy reasons and should not be used frequently). + + FIREBASE_ASSERT_MESSAGE( + path.find("//") == std::string::npos, + "Invalid path (%s). Paths must not contain // in them.", + std::string{path.data(), path.data() + path.size()}.c_str()); + + // SkipEmpty because we may still have an empty segment at the beginning or + // end if they had a leading or trailing slash (which we allow). + std::vector segments = + absl::StrSplit(path, '/', absl::SkipEmpty()); + return ResourcePath{std::move(segments)}; +} + +std::string ResourcePath::CanonicalString() const { + // NOTE: The client is ignorant of any path segments containing escape + // sequences (e.g. __id123__) and just passes them through raw (they exist + // for legacy reasons and should not be used frequently). + + return absl::StrJoin(begin(), end(), "/"); +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.h b/Firestore/core/src/firebase/firestore/model/resource_path.h new file mode 100644 index 0000000..481d32f --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/resource_path.h @@ -0,0 +1,84 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_RESOURCE_PATH_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_RESOURCE_PATH_H_ + +#include +#include + +#include "Firestore/core/src/firebase/firestore/model/base_path.h" +#include "absl/strings/string_view.h" + +namespace firebase { +namespace firestore { +namespace model { + +/** + * A slash-separated path for navigating resources (documents and collections) + * within Firestore. Immutable; all instances are fully independent. + */ +class ResourcePath : public impl::BasePath { + public: + ResourcePath() = default; + /** Constructs the path from segments. */ + template + ResourcePath(const IterT begin, const IterT end) : BasePath{begin, end} { + } + ResourcePath(std::initializer_list list) : BasePath{list} { + } + /** + * Creates and returns a new path from the given resource-path string, where + * the path segments are separated by a slash "/". + */ + static ResourcePath Parse(absl::string_view path); + + /** Returns a standardized string representation of this path. */ + std::string CanonicalString() const; + + bool operator==(const ResourcePath& rhs) const { + return BasePath::operator==(rhs); + } + bool operator!=(const ResourcePath& rhs) const { + return BasePath::operator!=(rhs); + } + bool operator<(const ResourcePath& rhs) const { + return BasePath::operator<(rhs); + } + bool operator>(const ResourcePath& rhs) const { + return BasePath::operator>(rhs); + } + bool operator<=(const ResourcePath& rhs) const { + return BasePath::operator<=(rhs); + } + bool operator>=(const ResourcePath& rhs) const { + return BasePath::operator>=(rhs); + } + + private: + ResourcePath(SegmentsT&& segments) : BasePath{std::move(segments)} { + } + + // So that methods of base can construct ResourcePath using the private + // constructor. + friend class BasePath; +}; + +} // namespace model +} // namespace firestore +} // namespace firebase + +#endif -- cgit v1.2.3