aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/core
diff options
context:
space:
mode:
authorGravatar rsgowman <rgowman@google.com>2018-05-04 15:21:22 -0400
committerGravatar GitHub <noreply@github.com>2018-05-04 15:21:22 -0400
commite12b997c6147663c692233a7e29a7433f4aee6f6 (patch)
treec19fe24c70e057fac00de00ebe5c63214972d72d /Firestore/core
parentadc514a894b819c0a63e85c083b766907b68e6ea (diff)
[De]serialize DocumentKeys via the remote Serializer (#1212)
Diffstat (limited to 'Firestore/core')
-rw-r--r--Firestore/core/src/firebase/firestore/remote/serializer.cc75
-rw-r--r--Firestore/core/src/firebase/firestore/remote/serializer.h39
-rw-r--r--Firestore/core/test/firebase/firestore/remote/serializer_test.cc42
3 files changed, 140 insertions, 16 deletions
diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.cc b/Firestore/core/src/firebase/firestore/remote/serializer.cc
index 2a89515..e81ea2d 100644
--- a/Firestore/core/src/firebase/firestore/remote/serializer.cc
+++ b/Firestore/core/src/firebase/firestore/remote/serializer.cc
@@ -25,14 +25,18 @@
#include <utility>
#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
namespace firebase {
namespace firestore {
namespace remote {
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::FieldValue;
using firebase::firestore::model::ObjectValue;
+using firebase::firestore::model::ResourcePath;
using firebase::firestore::util::Status;
using firebase::firestore::util::StatusOr;
@@ -719,6 +723,64 @@ ObjectValue::Map DecodeObject(Reader* reader) {
});
}
+/**
+ * Creates the prefix for a fully qualified resource path, without a local path
+ * on the end.
+ */
+ResourcePath EncodeDatabaseId(const DatabaseId& database_id) {
+ return ResourcePath{"projects", database_id.project_id(), "databases",
+ database_id.database_id()};
+}
+
+/**
+ * Encodes a databaseId and resource path into the following form:
+ * /projects/$projectId/database/$databaseId/documents/$path
+ */
+std::string EncodeResourceName(const DatabaseId& database_id,
+ const ResourcePath& path) {
+ return EncodeDatabaseId(database_id)
+ .Append("documents")
+ .Append(path)
+ .CanonicalString();
+}
+
+/**
+ * Validates that a path has a prefix that looks like a valid encoded
+ * databaseId.
+ */
+bool IsValidResourceName(const ResourcePath& path) {
+ // Resource names have at least 4 components (project ID, database ID)
+ // and commonly the (root) resource type, e.g. documents
+ return path.size() >= 4 && path[0] == "projects" && path[2] == "databases";
+}
+
+/**
+ * Decodes a fully qualified resource name into a resource path and validates
+ * that there is a project and database encoded in the path. There are no
+ * guarantees that a local path is also encoded in this resource name.
+ */
+ResourcePath DecodeResourceName(absl::string_view encoded) {
+ ResourcePath resource = ResourcePath::FromString(encoded);
+ FIREBASE_ASSERT_MESSAGE(IsValidResourceName(resource),
+ "Tried to deserialize invalid key %s",
+ resource.CanonicalString().c_str());
+ return resource;
+}
+
+/**
+ * Decodes a fully qualified resource name into a resource path and validates
+ * that there is a project and database encoded in the path along with a local
+ * path.
+ */
+ResourcePath ExtractLocalPathFromResourceName(
+ const ResourcePath& resource_name) {
+ FIREBASE_ASSERT_MESSAGE(
+ resource_name.size() > 4 && resource_name[4] == "documents",
+ "Tried to deserialize invalid key %s",
+ resource_name.CanonicalString().c_str());
+ return resource_name.PopFirst(5);
+}
+
} // namespace
Status Serializer::EncodeFieldValue(const FieldValue& field_value,
@@ -739,6 +801,19 @@ StatusOr<FieldValue> Serializer::DecodeFieldValue(const uint8_t* bytes,
}
}
+std::string Serializer::EncodeKey(const DocumentKey& key) const {
+ return EncodeResourceName(database_id_, key.path());
+}
+
+DocumentKey Serializer::DecodeKey(absl::string_view name) const {
+ ResourcePath resource = DecodeResourceName(name);
+ FIREBASE_ASSERT_MESSAGE(resource[1] == database_id_.project_id(),
+ "Tried to deserialize key from different project.");
+ FIREBASE_ASSERT_MESSAGE(resource[3] == database_id_.database_id(),
+ "Tried to deserialize key from different database.");
+ return DocumentKey{ExtractLocalPathFromResourceName(resource)};
+}
+
} // namespace remote
} // namespace firestore
} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.h b/Firestore/core/src/firebase/firestore/remote/serializer.h
index 7d13583..86aa6e2 100644
--- a/Firestore/core/src/firebase/firestore/remote/serializer.h
+++ b/Firestore/core/src/firebase/firestore/remote/serializer.h
@@ -19,13 +19,17 @@
#include <cstdint>
#include <cstdlib>
+#include <string>
#include <vector>
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
#include "Firestore/core/src/firebase/firestore/util/status.h"
#include "Firestore/core/src/firebase/firestore/util/statusor.h"
#include "absl/base/attributes.h"
+#include "absl/strings/string_view.h"
namespace firebase {
namespace firestore {
@@ -46,20 +50,13 @@ namespace remote {
// interpret." Adjust for C++.
class Serializer {
public:
- Serializer() {
+ /**
+ * @param database_id Must remain valid for the lifetime of this Serializer
+ * object.
+ */
+ explicit Serializer(const firebase::firestore::model::DatabaseId& database_id)
+ : database_id_(database_id) {
}
- // TODO(rsgowman): We eventually need the DatabaseId, but can't add it just
- // yet since it's not used yet (which travis complains about). So for now,
- // we'll create a parameterless ctor (above) that likely won't exist in the
- // final version of this class.
- ///**
- // * @param database_id Must remain valid for the lifetime of this Serializer
- // * object.
- // */
- // explicit Serializer(const firebase::firestore::model::DatabaseId&
- // database_id)
- // : database_id_(database_id) {
- //}
/**
* Converts the FieldValue model passed into bytes.
@@ -101,9 +98,21 @@ class Serializer {
return DecodeFieldValue(bytes.data(), bytes.size());
}
+ /**
+ * Encodes the given document key as a fully qualified name. This includes the
+ * databaseId associated with this Serializer and the key path.
+ */
+ std::string EncodeKey(
+ const firebase::firestore::model::DocumentKey& key) const;
+
+ /**
+ * Decodes the given document key from a fully qualified name.
+ */
+ firebase::firestore::model::DocumentKey DecodeKey(
+ absl::string_view name) const;
+
private:
- // TODO(rsgowman): We don't need the database_id_ yet (but will eventually).
- // model::DatabaseId* database_id_;
+ const firebase::firestore::model::DatabaseId& database_id_;
};
} // namespace remote
diff --git a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
index ecfbf32..1dded05 100644
--- a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
+++ b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
@@ -37,14 +37,17 @@
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
#include "Firestore/core/src/firebase/firestore/util/status.h"
#include "Firestore/core/src/firebase/firestore/util/statusor.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
#include "google/protobuf/stubs/common.h"
#include "google/protobuf/util/message_differencer.h"
#include "gtest/gtest.h"
using firebase::firestore::FirestoreErrorCode;
+using firebase::firestore::model::DatabaseId;
using firebase::firestore::model::FieldValue;
using firebase::firestore::model::ObjectValue;
using firebase::firestore::remote::Serializer;
+using firebase::firestore::testutil::Key;
using firebase::firestore::util::Status;
using firebase::firestore::util::StatusOr;
using google::protobuf::util::MessageDifferencer;
@@ -65,8 +68,10 @@ TEST(Serializer, CanLinkToNanopb) {
// Fixture for running serializer tests.
class SerializerTest : public ::testing::Test {
public:
- SerializerTest() : serializer(/*DatabaseId("p", "d")*/) {
+ SerializerTest() : serializer(kDatabaseId) {
}
+
+ const DatabaseId kDatabaseId{"p", "d"};
Serializer serializer;
void ExpectRoundTrip(const FieldValue& model,
@@ -425,5 +430,40 @@ TEST_F(SerializerTest, IncompleteTag) {
Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
}
+TEST_F(SerializerTest, EncodesKey) {
+ EXPECT_EQ("projects/p/databases/d/documents", serializer.EncodeKey(Key("")));
+ EXPECT_EQ("projects/p/databases/d/documents/one/two/three/four",
+ serializer.EncodeKey(Key("one/two/three/four")));
+}
+
+TEST_F(SerializerTest, DecodesKey) {
+ EXPECT_EQ(Key(""), serializer.DecodeKey("projects/p/databases/d/documents"));
+ EXPECT_EQ(Key("one/two/three/four"),
+ serializer.DecodeKey(
+ "projects/p/databases/d/documents/one/two/three/four"));
+ // Same, but with a leading slash
+ EXPECT_EQ(Key("one/two/three/four"),
+ serializer.DecodeKey(
+ "/projects/p/databases/d/documents/one/two/three/four"));
+}
+
+TEST_F(SerializerTest, BadKey) {
+ std::vector<std::string> bad_cases{
+ "", // empty (and too short)
+ "projects/p", // too short
+ "projects/p/databases/d", // too short
+ "projects/p/databases/d/documents/odd_number_of_local_elements",
+ "projects_spelled_wrong/p/databases/d/documents",
+ "projects/p/databases_spelled_wrong/d/documents",
+ "projects/not_project_p/databases/d/documents",
+ "projects/p/databases/not_database_d/documents",
+ "projects/p/databases/d/not_documents",
+ };
+
+ for (const std::string& bad_key : bad_cases) {
+ EXPECT_ANY_THROW(serializer.DecodeKey(bad_key));
+ }
+}
+
// TODO(rsgowman): Test [en|de]coding multiple protos into the same output
// vector.