From 80033485be537acaf29924f6b717c777b550a418 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 9 Feb 2018 19:42:28 -0500 Subject: C++ port: add C++ equivalent of FSTDocumentKey. (#762) Also move kDocumentKeyPath to the only point of usage - make it a static member variable of FieldPath. --- .../src/firebase/firestore/model/CMakeLists.txt | 2 + .../src/firebase/firestore/model/document_key.cc | 54 ++++++++ .../src/firebase/firestore/model/document_key.h | 101 ++++++++++++++ .../src/firebase/firestore/model/field_path.cc | 7 +- .../core/src/firebase/firestore/model/field_path.h | 3 + .../src/firebase/firestore/model/resource_path.cc | 2 +- .../src/firebase/firestore/model/resource_path.h | 2 +- .../test/firebase/firestore/model/CMakeLists.txt | 1 + .../firebase/firestore/model/document_key_test.cc | 153 +++++++++++++++++++++ .../firebase/firestore/model/resource_path_test.cc | 8 +- 10 files changed, 322 insertions(+), 11 deletions(-) create mode 100644 Firestore/core/src/firebase/firestore/model/document_key.cc create mode 100644 Firestore/core/src/firebase/firestore/model/document_key.h create mode 100644 Firestore/core/test/firebase/firestore/model/document_key_test.cc (limited to 'Firestore/core') diff --git a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt index 169e310..95310ec 100644 --- a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt @@ -18,6 +18,8 @@ cc_library( base_path.h database_id.cc database_id.h + document_key.cc + document_key.h field_path.cc field_path.h field_value.cc diff --git a/Firestore/core/src/firebase/firestore/model/document_key.cc b/Firestore/core/src/firebase/firestore/model/document_key.cc new file mode 100644 index 0000000..ddda4c9 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/document_key.cc @@ -0,0 +1,54 @@ +/* + * 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/document_key.h" + +#include + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +namespace firebase { +namespace firestore { +namespace model { + +namespace { + +void AssertValidPath(const ResourcePath& path) { + FIREBASE_ASSERT_MESSAGE(DocumentKey::IsDocumentKey(path), + "invalid document key path: %s", + path.CanonicalString().c_str()); +} + +} // namespace + +DocumentKey::DocumentKey(const ResourcePath& path) + : path_{std::make_shared(path)} { + AssertValidPath(*path_); +} + +DocumentKey::DocumentKey(ResourcePath&& path) + : path_{std::make_shared(std::move(path))} { + AssertValidPath(*path_); +} + +const DocumentKey& DocumentKey::Empty() { + static const DocumentKey empty; + return empty; +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/document_key.h b/Firestore/core/src/firebase/firestore/model/document_key.h new file mode 100644 index 0000000..6eb1e18 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/document_key.h @@ -0,0 +1,101 @@ +/* + * 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_DOCUMENT_KEY_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_H_ + +#include +#include +#include + +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "absl/strings/string_view.h" + +namespace firebase { +namespace firestore { +namespace model { + +/** + * DocumentKey represents the location of a document in the Firestore database. + */ +class DocumentKey { + public: + /** Creates a "blank" document key not associated with any document. */ + DocumentKey() : path_{std::make_shared()} { + } + + /** Creates a new document key containing a copy of the given path. */ + explicit DocumentKey(const ResourcePath& path); + + /** Creates a new document key, taking ownership of the given path. */ + explicit DocumentKey(ResourcePath&& path); + + /** + * Creates and returns a new document key using '/' to split the string into + * segments. + */ + static DocumentKey FromPathString(const absl::string_view path) { + return DocumentKey{ResourcePath::FromString(path)}; + } + + /** Creates and returns a new document key with the given segments. */ + static DocumentKey FromSegments(std::initializer_list list) { + return DocumentKey{ResourcePath{list}}; + } + + /** Returns a shared instance of an empty document key. */ + static const DocumentKey& Empty(); + + /** Returns true iff the given path is a path to a document. */ + static bool IsDocumentKey(const ResourcePath& path) { + return path.size() % 2 == 0; + } + + /** The path to the document. */ + const ResourcePath& path() const { + return path_ ? *path_ : Empty().path(); + } + + private: + // This is an optimization to make passing DocumentKey around cheaper (it's + // copied often). + std::shared_ptr path_; +}; + +inline bool operator==(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() == rhs.path(); +} +inline bool operator!=(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() != rhs.path(); +} +inline bool operator<(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() < rhs.path(); +} +inline bool operator<=(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() <= rhs.path(); +} +inline bool operator>(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() > rhs.path(); +} +inline bool operator>=(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() >= rhs.path(); +} + +} // namespace model +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_H_ diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index 0da2319..bc0e97c 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -30,9 +30,6 @@ 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_]* @@ -146,12 +143,12 @@ const FieldPath& FieldPath::EmptyPath() { } const FieldPath& FieldPath::KeyFieldPath() { - static const FieldPath key_field_path{kDocumentKeyPath}; + static const FieldPath key_field_path{FieldPath::kDocumentKeyPath}; return key_field_path; } bool FieldPath::IsKeyFieldPath() const { - return size() == 1 && first_segment() == kDocumentKeyPath; + return size() == 1 && first_segment() == FieldPath::kDocumentKeyPath; } std::string FieldPath::CanonicalString() const { diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index ba6e2bd..fdf4918 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -35,6 +35,9 @@ namespace model { */ class FieldPath : public impl::BasePath { public: + /** The field path string that represents the document's key. */ + static constexpr const char* kDocumentKeyPath = "__name__"; + // Note: Xcode 8.2 requires explicit specification of the constructor. FieldPath() : impl::BasePath() { } diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.cc b/Firestore/core/src/firebase/firestore/model/resource_path.cc index a4f921f..c95aa63 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.cc +++ b/Firestore/core/src/firebase/firestore/model/resource_path.cc @@ -28,7 +28,7 @@ namespace firebase { namespace firestore { namespace model { -ResourcePath ResourcePath::Parse(const absl::string_view path) { +ResourcePath ResourcePath::FromString(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). diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.h b/Firestore/core/src/firebase/firestore/model/resource_path.h index b0853c6..53c1951 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.h +++ b/Firestore/core/src/firebase/firestore/model/resource_path.h @@ -45,7 +45,7 @@ class ResourcePath : public impl::BasePath { * 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); + static ResourcePath FromString(absl::string_view path); /** Returns a standardized string representation of this path. */ std::string CanonicalString() const; diff --git a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt index 282befd..e73be98 100644 --- a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt @@ -16,6 +16,7 @@ cc_test( firebase_firestore_model_test SOURCES database_id_test.cc + document_key_test.cc field_path_test.cc field_value_test.cc snapshot_version_test.cc diff --git a/Firestore/core/test/firebase/firestore/model/document_key_test.cc b/Firestore/core/test/firebase/firestore/model/document_key_test.cc new file mode 100644 index 0000000..0e0df2d --- /dev/null +++ b/Firestore/core/test/firebase/firestore/model/document_key_test.cc @@ -0,0 +1,153 @@ +/* + * 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 +#include +#include +#include + +#include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace model { + +TEST(DocumentKey, Constructor_Empty) { + const DocumentKey default_key; + EXPECT_TRUE(default_key.path().empty()); + + const auto& empty_key = DocumentKey::Empty(); + const auto& another_empty_key = DocumentKey::Empty(); + EXPECT_EQ(default_key, empty_key); + EXPECT_EQ(empty_key, another_empty_key); + EXPECT_EQ(&empty_key, &another_empty_key); +} + +TEST(DocumentKey, Constructor_FromPath) { + ResourcePath path{"rooms", "firestore", "messages", "1"}; + const DocumentKey key_from_path_copy{path}; + // path shouldn't have been moved from. + EXPECT_FALSE(path.empty()); + EXPECT_EQ(key_from_path_copy.path(), path); + + const DocumentKey key_from_moved_path{std::move(path)}; + EXPECT_TRUE(path.empty()); + EXPECT_FALSE(key_from_moved_path.path().empty()); + EXPECT_EQ(key_from_path_copy.path(), key_from_moved_path.path()); +} + +TEST(DocumentKey, CopyAndMove) { + DocumentKey key({"rooms", "firestore", "messages", "1"}); + const std::string path_string = "rooms/firestore/messages/1"; + EXPECT_EQ(path_string, key.path().CanonicalString()); + + DocumentKey copied = key; + EXPECT_EQ(path_string, copied.path().CanonicalString()); + EXPECT_EQ(key, copied); + + const DocumentKey moved = std::move(key); + EXPECT_EQ(path_string, moved.path().CanonicalString()); + EXPECT_NE(key, moved); + EXPECT_TRUE(key.path().empty()); + + // Reassignment. + + key = copied; + EXPECT_EQ(copied, key); + EXPECT_EQ(path_string, key.path().CanonicalString()); + + key = {}; + EXPECT_TRUE(key.path().empty()); + key = std::move(copied); + EXPECT_NE(copied, key); + EXPECT_TRUE(copied.path().empty()); + EXPECT_EQ(path_string, key.path().CanonicalString()); +} + +TEST(DocumentKey, Constructor_StaticFactory) { + const auto key_from_segments = + DocumentKey::FromSegments({"rooms", "firestore", "messages", "1"}); + const std::string path_string = "rooms/firestore/messages/1"; + const auto key_from_string = DocumentKey::FromPathString(path_string); + EXPECT_EQ(path_string, key_from_string.path().CanonicalString()); + EXPECT_EQ(path_string, key_from_segments.path().CanonicalString()); + EXPECT_EQ(key_from_segments, key_from_string); + + const auto from_empty_path = DocumentKey::FromPathString(""); + EXPECT_EQ(from_empty_path, DocumentKey{}); +} + +TEST(DocumentKey, Constructor_BadArguments) { + ASSERT_ANY_THROW(DocumentKey(ResourcePath{"foo"})); + ASSERT_ANY_THROW(DocumentKey(ResourcePath{"foo", "bar", "baz"})); + + ASSERT_ANY_THROW(DocumentKey::FromSegments({"foo"})); + ASSERT_ANY_THROW(DocumentKey::FromSegments({"foo", "bar", "baz"})); + + ASSERT_ANY_THROW(DocumentKey::FromPathString("invalid")); + ASSERT_ANY_THROW(DocumentKey::FromPathString("invalid//string")); + ASSERT_ANY_THROW(DocumentKey::FromPathString("invalid/key/path")); +} + +TEST(DocumentKey, IsDocumentKey) { + EXPECT_TRUE(DocumentKey::IsDocumentKey({})); + EXPECT_FALSE(DocumentKey::IsDocumentKey({"foo"})); + EXPECT_TRUE(DocumentKey::IsDocumentKey({"foo", "bar"})); + EXPECT_FALSE(DocumentKey::IsDocumentKey({"foo", "bar", "baz"})); +} + +TEST(DocumentKey, Comparison) { + const DocumentKey abcd({"a", "b", "c", "d"}); + const DocumentKey abcd_too({"a", "b", "c", "d"}); + const DocumentKey xyzw({"x", "y", "z", "w"}); + EXPECT_EQ(abcd, abcd_too); + EXPECT_NE(abcd, xyzw); + + const DocumentKey empty; + const DocumentKey a({"a", "a"}); + const DocumentKey b({"b", "b"}); + const DocumentKey ab({"a", "a", "b", "b"}); + + EXPECT_FALSE(empty < empty); + EXPECT_TRUE(empty <= empty); + EXPECT_TRUE(empty < a); + EXPECT_TRUE(empty <= a); + EXPECT_TRUE(a > empty); + EXPECT_TRUE(a >= empty); + + EXPECT_FALSE(a < a); + EXPECT_TRUE(a <= a); + EXPECT_FALSE(a > a); + EXPECT_TRUE(a >= a); + EXPECT_TRUE(a == a); + EXPECT_FALSE(a != a); + + EXPECT_TRUE(a < b); + EXPECT_TRUE(a <= b); + EXPECT_TRUE(b > a); + EXPECT_TRUE(b >= a); + + EXPECT_TRUE(a < ab); + EXPECT_TRUE(a <= ab); + EXPECT_TRUE(ab > a); + EXPECT_TRUE(ab >= a); +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc index 378a5e3..637e78e 100644 --- a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc @@ -74,7 +74,7 @@ TEST(ResourcePath, Comparison) { TEST(ResourcePath, Parsing) { const auto parse = [](const std::pair expected) { - const auto path = ResourcePath::Parse(expected.first); + const auto path = ResourcePath::FromString(expected.first); return std::make_pair(path.CanonicalString(), path.size()); }; const auto make_expected = [](const std::string& str, const size_t size) { @@ -92,12 +92,12 @@ TEST(ResourcePath, Parsing) { expected = make_expected(R"(foo/__!?#@..`..\`/baz)", 3); EXPECT_EQ(expected, parse(expected)); - EXPECT_EQ(ResourcePath::Parse("/foo/").CanonicalString(), "foo"); + EXPECT_EQ(ResourcePath::FromString("/foo/").CanonicalString(), "foo"); } TEST(ResourcePath, ParseFailures) { - ASSERT_ANY_THROW(ResourcePath::Parse("//")); - ASSERT_ANY_THROW(ResourcePath::Parse("foo//bar")); + ASSERT_ANY_THROW(ResourcePath::FromString("//")); + ASSERT_ANY_THROW(ResourcePath::FromString("foo//bar")); } } // namespace model -- cgit v1.2.3