diff options
Diffstat (limited to 'Firestore/core')
35 files changed, 1286 insertions, 231 deletions
diff --git a/Firestore/core/include/firebase/firestore/CMakeLists.txt b/Firestore/core/include/firebase/firestore/CMakeLists.txt index e4e7acd..0c7fd48 100644 --- a/Firestore/core/include/firebase/firestore/CMakeLists.txt +++ b/Firestore/core/include/firebase/firestore/CMakeLists.txt @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Hack to make the headers show up in IDEs +# Workaround to make the headers show up in IDEs # (see https://stackoverflow.com/questions/27039019/ and open issue on CMake # issue tracker: https://gitlab.kitware.com/cmake/cmake/issues/15234) add_custom_target(firebase_firestore_types_ide SOURCES diff --git a/Firestore/core/include/firebase/firestore/collection_reference.h b/Firestore/core/include/firebase/firestore/collection_reference.h new file mode 100644 index 0000000..4d47248 --- /dev/null +++ b/Firestore/core/include/firebase/firestore/collection_reference.h @@ -0,0 +1,98 @@ +/* + * 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_INCLUDE_FIREBASE_FIRESTORE_COLLECTION_REFERENCE_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_COLLECTION_REFERENCE_H_ + +namespace firebase { +namespace firestore { + +class CollectionReferenceInternal; +class FirestoreInternal; + +/** + * A CollectionReference refers to a collection of documents location in a + * Firestore database and can be used for adding documents, getting document + * references, and querying for documents. + */ +// TODO(zxu123): add more methods to complete the class and make it useful. +class CollectionReference { + public: + /** + * @brief Default constructor. This creates an invalid CollectionReference. + * Attempting to perform any operations on this reference will fail unless a + * valid CollectionReference has been assigned to it. + */ + CollectionReference(); + + /** + * @brief Copy constructor. It's totally okay (and efficient) to copy + * CollectionReference instances, as they simply point to the same location in + * the database. + * + * @param[in] reference CollectionReference to copy from. + */ + CollectionReference(const CollectionReference& reference); + + /** + * @brief Move constructor. Moving is an efficient operation for + * CollectionReference instances. + * + * @param[in] reference CollectionReference to move data from. + */ + CollectionReference(CollectionReference&& reference); + + /** @brief Required virtual destructor. */ + virtual ~CollectionReference(); + + /** + * @brief Copy assignment operator. It's totally okay (and efficient) to copy + * CollectionReference instances, as they simply point to the same location in + * the database. + * + * @param[in] reference CollectionReference to copy from. + * + * @returns Reference to the destination CollectionReference. + */ + CollectionReference& operator=(const CollectionReference& reference); + + /** + * @brief Move assignment operator. Moving is an efficient operation for + * CollectionReference instances. + * + * @param[in] reference CollectionReference to move data from. + * + * @returns Reference to the destination CollectionReference. + */ + CollectionReference& operator=(CollectionReference&& reference); + + protected: + explicit CollectionReference(CollectionReferenceInternal* internal); + + private: + friend class DocumentReference; + friend class DocumentReferenceInternal; + friend class FirestoreInternal; + + // TODO(zxu123): investigate possibility to use std::unique_ptr or + // firebase::UniquePtr. + CollectionReferenceInternal* internal_ = nullptr; +}; + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_COLLECTION_REFERENCE_H_ diff --git a/Firestore/core/include/firebase/firestore/document_change.h b/Firestore/core/include/firebase/firestore/document_change.h new file mode 100644 index 0000000..4812290 --- /dev/null +++ b/Firestore/core/include/firebase/firestore/document_change.h @@ -0,0 +1,34 @@ +/* + * 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_INCLUDE_FIREBASE_FIRESTORE_DOCUMENT_CHANGE_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_DOCUMENT_CHANGE_H_ + +namespace firebase { +namespace firestore { + +/** + * A DocumentChange represents a change to the documents matching a query. It + * contains the document affected and the type of change that occurred (added, + * modified, or removed). + */ +// TODO(zxu123): add more methods to complete the class and make it useful. +class DocumentChange {}; + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_DOCUMENT_CHANGE_H_ diff --git a/Firestore/core/include/firebase/firestore/document_reference.h b/Firestore/core/include/firebase/firestore/document_reference.h index d295188..9385ed3 100644 --- a/Firestore/core/include/firebase/firestore/document_reference.h +++ b/Firestore/core/include/firebase/firestore/document_reference.h @@ -29,6 +29,17 @@ #include <functional> #endif +#include "firebase/app.h" +#include "firebase/firestore/collection_reference.h" +#include "firebase/firestore/document_snapshot.h" +#include "firebase/firestore/event_listener.h" +#include "firebase/firestore/field_value.h" +#include "firebase/firestore/firestore.h" +#include "firebase/firestore/firestore_errors.h" +#include "firebase/firestore/listener_registration.h" +#include "firebase/firestore/set_options.h" +#include "firebase/future.h" + // TODO(rsgowman): Note that RTDB uses: // #if defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN // to protect move operators from older compilers. But all our supported @@ -36,34 +47,12 @@ // here so we don't forget to mention this during the API review, and should be // removed once this note has migrated to the API review doc. -// TODO(rsgowman): replace these forward decls with appropriate includes (once -// they exist) -namespace firebase { -class App; -template <typename T> -class Future; -} // namespace firebase - namespace firebase { namespace firestore { -// TODO(rsgowman): replace these forward decls with appropriate includes (once -// they exist) -class FieldValue; -class DocumentSnapshot; +class DocumentReferenceInternal; class Firestore; -class Error; -template <typename T> -class EventListener; -class ListenerRegistration; -class CollectionReference; -class DocumentListenOptions; -// TODO(rsgowman): not quite a forward decl, but required to make the default -// parameter to Set() "compile". -class SetOptions { - public: - SetOptions(); -}; +class FirestoreInternal; // TODO(rsgowman): move this into the FieldValue header #ifdef STLPORT @@ -80,12 +69,19 @@ using MapFieldValue = std::unordered_map<std::string, FieldValue>; * * Create a DocumentReference via Firebase::Document(const string& path). * + * NOT thread-safe: an instance should not be used from multiple threads + * * Subclassing Note: Firestore classes are not meant to be subclassed except for * use in test mocks. Subclassing is not supported in production code and new * SDK releases may break code that does so. */ class DocumentReference { public: + enum class MetadataChanges { + kExclude, + kInclude, + }; + /** * @brief Default constructor. This creates an invalid DocumentReference. * Attempting to perform any operations on this reference will fail (and cause @@ -269,28 +265,15 @@ class DocumentReference { * this DocumentReference. (Ownership is not transferred; you are responsible * for making sure that listener is valid as long as this DocumentReference is * valid and the listener is registered.) + * @param[in] metadata_changes Indicates whether metadata-only changes (i.e. + * only DocumentSnapshot.getMetadata() changed) should trigger snapshot + * events. * * @return A registration object that can be used to remove the listener. */ virtual ListenerRegistration AddSnapshotListener( - EventListener<DocumentSnapshot>* listener); - - /** - * @brief Starts listening to the document referenced by this - * DocumentReference. - * - * @param[in] options The options to use for this listen. - * @param[in] listener The event listener that will be called with the - * snapshots, which must remain in memory until you remove the listener from - * this DocumentReference. (Ownership is not transferred; you are responsible - * for making sure that listener is valid as long as this DocumentReference is - * valid and the listener is registered.) - * - * @return A registration object that can be used to remove the listener. - */ - virtual ListenerRegistration AddSnapshotListener( - const DocumentListenOptions& options, - EventListener<DocumentSnapshot>* listener); + EventListener<DocumentSnapshot>* listener, + MetadataChanges metadata_changes = MetadataChanges::kExclude); #if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN) /** @@ -299,6 +282,9 @@ class DocumentReference { * * @param[in] callback function or lambda to call. When this function is * called, exactly one of the parameters will be non-null. + * @param[in] metadata_changes Indicates whether metadata-only changes (i.e. + * only DocumentSnapshot.getMetadata() changed) should trigger snapshot + * events. * * @return A registration object that can be used to remove the listener. * @@ -306,28 +292,21 @@ class DocumentReference { * std::function is not supported on STLPort. */ virtual ListenerRegistration AddSnapshotListener( - std::function<void(const DocumentSnapshot*, const Error*)> callback); - - /** - * @brief Starts listening to the document referenced by this - * DocumentReference. - * - * @param[in] options The options to use for this listen. - * @param[in] callback function or lambda to call. When this function is - * called, exactly one of the parameters will be non-null. - * - * @return A registration object that can be used to remove the listener. - * - * @note This method is not available when using STLPort on Android, as - * std::function is not supported on STLPort. - */ - virtual ListenerRegistration AddSnapshotListener( - const DocumentListenOptions& options, - std::function<void(const DocumentSnapshot*, const Error*)> callback); + std::function<void(const DocumentSnapshot*, const Error*)> callback, + MetadataChanges metadata_changes = MetadataChanges::kExclude); #endif // defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN) + + protected: + explicit DocumentReference(DocumentReferenceInternal* internal); + + private: + friend class FirestoreInternal; + + // TODO(zxu123): investigate possibility to use std::unique_ptr or + // firebase::UniquePtr. + DocumentReferenceInternal* internal_ = nullptr; }; -// TODO(rsgowman): probably define and inline here. bool operator==(const DocumentReference& lhs, const DocumentReference& rhs); inline bool operator!=(const DocumentReference& lhs, diff --git a/Firestore/core/include/firebase/firestore/document_snapshot.h b/Firestore/core/include/firebase/firestore/document_snapshot.h new file mode 100644 index 0000000..3be72b5 --- /dev/null +++ b/Firestore/core/include/firebase/firestore/document_snapshot.h @@ -0,0 +1,38 @@ +/* + * 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_INCLUDE_FIREBASE_FIRESTORE_DOCUMENT_SNAPSHOT_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_DOCUMENT_SNAPSHOT_H_ + +namespace firebase { +namespace firestore { + +/** + * A DocumentSnapshot contains data read from a document in your Firestore + * database. The data can be extracted with the data() method or by using + * FooValue() to access a specific field, where Foo is the type of that field. + * + * For a DocumentSnapshot that points to a non-existing document, any data + * access will cause a failed assertion. You can use the exists() method to + * explicitly verify a documents existence. + */ +// TODO(zxu123): add more methods to complete the class and make it useful. +class DocumentSnapshot {}; + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_DOCUMENT_SNAPSHOT_H_ diff --git a/Firestore/core/include/firebase/firestore/event_listener.h b/Firestore/core/include/firebase/firestore/event_listener.h index 6c94428..cbe8a28 100644 --- a/Firestore/core/include/firebase/firestore/event_listener.h +++ b/Firestore/core/include/firebase/firestore/event_listener.h @@ -22,19 +22,19 @@ #ifndef FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_EVENT_LISTENER_H_ #define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_EVENT_LISTENER_H_ +#include "firebase/firestore/firestore_errors.h" + namespace firebase { namespace firestore { -// TODO(rsgowman): replace these forward decl's with appropriate includes (once -// they exist) -class Error; - /** * @brief An interface for event listeners. */ template <typename T> class EventListener { public: + virtual ~EventListener() { + } /** * @brief OnEvent will be called with the new value or the error if an error * occurred. @@ -44,7 +44,7 @@ class EventListener { * @param value The value of the event. null if there was an error. * @param error The error if there was error. null otherwise. */ - void OnEvent(const T* value, const Error* error); + virtual void OnEvent(const T* value, const Error* error) = 0; }; } // namespace firestore diff --git a/Firestore/core/include/firebase/firestore/field_path.h b/Firestore/core/include/firebase/firestore/field_path.h new file mode 100644 index 0000000..29e1dea --- /dev/null +++ b/Firestore/core/include/firebase/firestore/field_path.h @@ -0,0 +1,34 @@ +/* + * 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_INCLUDE_FIREBASE_FIRESTORE_FIELD_PATH_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIELD_PATH_H_ + +namespace firebase { +namespace firestore { + +/** + * A FieldPath refers to a field in a document. The path may consist of a single + * field name (referring to a top level field in the document), or a list of + * field names (referring to a nested field in the document). + */ +// TODO(zxu123): add more methods to complete the class and make it useful. +class FieldPath {}; + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIELD_PATH_H_ diff --git a/Firestore/core/include/firebase/firestore/field_value.h b/Firestore/core/include/firebase/firestore/field_value.h new file mode 100644 index 0000000..d919de4 --- /dev/null +++ b/Firestore/core/include/firebase/firestore/field_value.h @@ -0,0 +1,33 @@ +/* + * 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_INCLUDE_FIREBASE_FIRESTORE_FIELD_VALUE_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIELD_VALUE_H_ + +namespace firebase { +namespace firestore { + +/** + * Sentinel values that can be used when writing document fields with setData() + * or updateData(). + */ +// TODO(zxu123): add more methods to complete the class and make it useful. +class FieldValue {}; + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIELD_VALUE_H_ diff --git a/Firestore/core/include/firebase/firestore/firestore.h b/Firestore/core/include/firebase/firestore/firestore.h index 793fdd0..6591a72 100644 --- a/Firestore/core/include/firebase/firestore/firestore.h +++ b/Firestore/core/include/firebase/firestore/firestore.h @@ -22,23 +22,19 @@ #ifndef FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_H_ #define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_FIRESTORE_H_ +#include <memory> #include <string> -// TODO(rsgowman): replace these forward decl's with appropriate includes (once -// they exist) -namespace firebase { -class App; -class InitResult; -} // namespace firebase +#include "firebase/app.h" +#include "firebase/firestore/collection_reference.h" +#include "firebase/firestore/document_reference.h" +#include "firebase/firestore/settings.h" namespace firebase { namespace firestore { -// TODO(rsgowman): replace these forward decl's with appropriate includes (once -// they exist) class DocumentReference; -class CollectionReference; -class Settings; +class FirestoreInternal; /** * @brief Entry point for the Firebase Firestore C++ SDK. @@ -152,6 +148,16 @@ class Firestore { /** Globally enables / disables Firestore logging for the SDK. */ static void set_logging_enabled(bool logging_enabled); + + Firestore(const Firestore& src) = delete; + Firestore& operator=(const Firestore& src) = delete; + + private: + explicit Firestore(::firebase::App* app); + + // TODO(zxu123): investigate possibility to use std::unique_ptr or + // firebase::UniquePtr. + FirestoreInternal* internal_ = nullptr; }; } // namespace firestore diff --git a/Firestore/core/include/firebase/firestore/firestore_errors.h b/Firestore/core/include/firebase/firestore/firestore_errors.h index 7a0ff7c..92c0c92 100644 --- a/Firestore/core/include/firebase/firestore/firestore_errors.h +++ b/Firestore/core/include/firebase/firestore/firestore_errors.h @@ -109,6 +109,10 @@ enum FirestoreErrorCode { Unauthenticated = 16 }; +// TODO(zxu123): decide whether we actually want an Error class or just use +// enum. +using Error = FirestoreErrorCode; + } // namespace firestore } // namespace firebase diff --git a/Firestore/core/include/firebase/firestore/listener_registration.h b/Firestore/core/include/firebase/firestore/listener_registration.h new file mode 100644 index 0000000..a37c2aa --- /dev/null +++ b/Firestore/core/include/firebase/firestore/listener_registration.h @@ -0,0 +1,92 @@ +/* + * 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_INCLUDE_FIREBASE_FIRESTORE_LISTENER_REGISTRATION_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_LISTENER_REGISTRATION_H_ + +namespace firebase { +namespace firestore { + +class FirestoreInternal; +class ListenerRegistrationInternal; + +/** Represents a listener that can be removed by calling remove. */ +class ListenerRegistration { + public: + /** + * @brief Default constructor. This creates a no-op instance. + */ + ListenerRegistration(); + + /** + * @brief Copy constructor. It's totally okay to copy ListenerRegistration + * instances. + * + * @param[in] registration ListenerRegistration to copy from. + */ + ListenerRegistration(const ListenerRegistration& registration); + + /** + * @brief Move constructor. Moving is an efficient operation for + * ListenerRegistration instances. + * + * @param[in] registration ListenerRegistration to move data from. + */ + ListenerRegistration(ListenerRegistration&& registration); + + ~ListenerRegistration(); + + /** + * @brief Copy assignment operator. It's totally okay to copy + * ListenerRegistration instances. + * + * @param[in] registration ListenerRegistration to copy from. + * + * @returns Reference to the destination ListenerRegistration. + */ + ListenerRegistration& operator=(const ListenerRegistration& registration); + + /** + * @brief Move assignment operator. Moving is an efficient operation for + * ListenerRegistration instances. + * + * @param[in] registration ListenerRegistration to move data from. + * + * @returns Reference to the destination ListenerRegistration. + */ + ListenerRegistration& operator=(ListenerRegistration&& registration); + + /** + * Removes the listener being tracked by this ListenerRegistration. After the + * initial call, subsequent calls have no effect. + */ + void Remove(); + + private: + friend class DocumentReferenceInternal; + friend class ListenerRegistrationInternal; + friend class FirestoreInternal; + + explicit ListenerRegistration(ListenerRegistrationInternal* internal); + + FirestoreInternal* firestore_ = nullptr; + ListenerRegistrationInternal* internal_ = nullptr; +}; + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_LISTENER_REGISTRATION_H_ diff --git a/Firestore/core/include/firebase/firestore/query.h b/Firestore/core/include/firebase/firestore/query.h new file mode 100644 index 0000000..da6dfdd --- /dev/null +++ b/Firestore/core/include/firebase/firestore/query.h @@ -0,0 +1,33 @@ +/* + * 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_INCLUDE_FIREBASE_FIRESTORE_QUERY_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_QUERY_H_ + +namespace firebase { +namespace firestore { + +/** + * A Query which you can read or listen to. You can also construct refined + * Query objects by adding filters and ordering. + */ +// TODO(zxu123): add more methods to complete the class and make it useful. +class Query {}; + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_QUERY_H_ diff --git a/Firestore/core/include/firebase/firestore/query_snapshot.h b/Firestore/core/include/firebase/firestore/query_snapshot.h new file mode 100644 index 0000000..ffa2bd6 --- /dev/null +++ b/Firestore/core/include/firebase/firestore/query_snapshot.h @@ -0,0 +1,34 @@ +/* + * 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_INCLUDE_FIREBASE_FIRESTORE_QUERY_SNAPSHOT_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_QUERY_SNAPSHOT_H_ + +namespace firebase { +namespace firestore { + +/** + * A QuerySnapshot contains zero or more DocumentSnapshot objects. It can be + * iterated using a range-based for loop and its size can be inspected with + * empty() and count(). + */ +// TODO(zxu123): add more methods to complete the class and make it useful. +class QuerySnapshot {}; + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_QUERY_SNAPSHOT_H_ diff --git a/Firestore/core/include/firebase/firestore/set_options.h b/Firestore/core/include/firebase/firestore/set_options.h new file mode 100644 index 0000000..802f3b5 --- /dev/null +++ b/Firestore/core/include/firebase/firestore/set_options.h @@ -0,0 +1,39 @@ +/* + * 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_INCLUDE_FIREBASE_FIRESTORE_SET_OPTIONS_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_SET_OPTIONS_H_ + +namespace firebase { +namespace firestore { + +/** + * An options object that configures the behavior of Set() calls. By providing + * the SetOptions objects returned by Merge(), the Set() methods in + * DocumentReference, WriteBatch and Transaction can be configured to perform + * granular merges instead of overwriting the target documents in their + * entirety. + */ +// TODO(zxu123): add more methods to complete the class and make it useful. +class SetOptions { + public: + SetOptions(); +}; + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_SET_OPTIONS_H_ diff --git a/Firestore/core/include/firebase/firestore/settings.h b/Firestore/core/include/firebase/firestore/settings.h new file mode 100644 index 0000000..9356b26 --- /dev/null +++ b/Firestore/core/include/firebase/firestore/settings.h @@ -0,0 +1,32 @@ +/* + * 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_INCLUDE_FIREBASE_FIRESTORE_SETTINGS_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_SETTINGS_H_ + +namespace firebase { +namespace firestore { + +class SettingsInternal; + +/** Settings used to configure a Firestore instance. */ +// TODO(zxu123): add more methods to complete the class and make it useful. +class Settings {}; + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_SETTINGS_H_ diff --git a/Firestore/core/include/firebase/firestore/snapshot_metadata.h b/Firestore/core/include/firebase/firestore/snapshot_metadata.h new file mode 100644 index 0000000..9bcc54c --- /dev/null +++ b/Firestore/core/include/firebase/firestore/snapshot_metadata.h @@ -0,0 +1,46 @@ +/* + * 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_INCLUDE_FIREBASE_FIRESTORE_SNAPSHOT_METADATA_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_SNAPSHOT_METADATA_H_ + +namespace firebase { +namespace firestore { + +/** Metadata about a snapshot, describing the state of the snapshot. */ +class SnapshotMetadata { + public: + SnapshotMetadata(bool has_pending_writes, bool is_from_cache) + : has_pending_writes_(has_pending_writes), is_from_cache_(is_from_cache) { + } + + bool has_pending_writes() const { + return has_pending_writes_; + } + + bool is_from_cache() const { + return is_from_cache_; + } + + private: + const bool has_pending_writes_; + const bool is_from_cache_; +}; + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_SNAPSHOT_METADATA_H_ diff --git a/Firestore/core/include/firebase/firestore/transaction.h b/Firestore/core/include/firebase/firestore/transaction.h new file mode 100644 index 0000000..be043b8 --- /dev/null +++ b/Firestore/core/include/firebase/firestore/transaction.h @@ -0,0 +1,32 @@ +/* + * 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_INCLUDE_FIREBASE_FIRESTORE_TRANSACTION_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_TRANSACTION_H_ + +namespace firebase { +namespace firestore { + +/** + * Transaction provides methods to read and write data within a transaction. + */ +// TODO(zxu123): add more methods to complete the class and make it useful. +class Transaction {}; + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_TRANSACTION_H_ diff --git a/Firestore/core/include/firebase/firestore/write_batch.h b/Firestore/core/include/firebase/firestore/write_batch.h new file mode 100644 index 0000000..bd2c12f --- /dev/null +++ b/Firestore/core/include/firebase/firestore/write_batch.h @@ -0,0 +1,39 @@ +/* + * 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_INCLUDE_FIREBASE_FIRESTORE_WRITE_BATCH_H_ +#define FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_WRITE_BATCH_H_ + +namespace firebase { +namespace firestore { + +/** + * A write batch is used to perform multiple writes as a single atomic unit. + * + * A WriteBatch object provides methods for adding writes to the write batch. + * None of the writes will be committed (or visible locally) until commit() is + * called. + * + * Unlike transactions, write batches are persisted offline and therefore are + * preferable when you don't need to condition your writes on read data. + */ +// TODO(zxu123): add more methods to complete the class and make it useful. +class WriteBatch {}; + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_INCLUDE_FIREBASE_FIRESTORE_WRITE_BATCH_H_ diff --git a/Firestore/core/src/firebase/firestore/nanopb/reader.cc b/Firestore/core/src/firebase/firestore/nanopb/reader.cc index 7a12900..69e3d83 100644 --- a/Firestore/core/src/firebase/firestore/nanopb/reader.cc +++ b/Firestore/core/src/firebase/firestore/nanopb/reader.cc @@ -16,7 +16,7 @@ #include "Firestore/core/src/firebase/firestore/nanopb/reader.h" -#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h" +#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.nanopb.h" namespace firebase { namespace firestore { @@ -136,6 +136,14 @@ std::string Reader::ReadString() { return result; } +void Reader::SkipField(const Tag& tag) { + if (!status_.ok()) return; + + if (!pb_skip_field(&stream_, tag.wire_type)) { + status_ = Status(FirestoreErrorCode::DataLoss, PB_GET_ERROR(&stream_)); + } +} + } // namespace nanopb } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/nanopb/reader.h b/Firestore/core/src/firebase/firestore/nanopb/reader.h index 930211a..2c16ec4 100644 --- a/Firestore/core/src/firebase/firestore/nanopb/reader.h +++ b/Firestore/core/src/firebase/firestore/nanopb/reader.h @@ -89,6 +89,15 @@ class Reader { template <typename T> T ReadNestedMessage(const std::function<T(Reader*)>& read_message_fn); + /** + * Discards the bytes associated with the given tag. + * + * @param tag The tag associated with the field that is otherwise about to be + * read. This method uses the tag to determine how many bytes should be + * discarded. + */ + void SkipField(const Tag& tag); + size_t bytes_left() const { return stream_.bytes_left; } diff --git a/Firestore/core/src/firebase/firestore/nanopb/writer.cc b/Firestore/core/src/firebase/firestore/nanopb/writer.cc index c3ffaac..7f32d4e 100644 --- a/Firestore/core/src/firebase/firestore/nanopb/writer.cc +++ b/Firestore/core/src/firebase/firestore/nanopb/writer.cc @@ -16,7 +16,7 @@ #include "Firestore/core/src/firebase/firestore/nanopb/writer.h" -#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h" +#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.nanopb.h" namespace firebase { namespace firestore { diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.cc b/Firestore/core/src/firebase/firestore/remote/serializer.cc index 6ea5844..19a068b 100644 --- a/Firestore/core/src/firebase/firestore/remote/serializer.cc +++ b/Firestore/core/src/firebase/firestore/remote/serializer.cc @@ -24,8 +24,8 @@ #include <string> #include <utility> -#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h" -#include "Firestore/Protos/nanopb/google/firestore/v1beta1/firestore.pb.h" +#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.nanopb.h" +#include "Firestore/Protos/nanopb/google/firestore/v1beta1/firestore.nanopb.h" #include "Firestore/core/include/firebase/firestore/firestore_errors.h" #include "Firestore/core/include/firebase/firestore/timestamp.h" #include "Firestore/core/src/firebase/firestore/model/document.h" @@ -167,77 +167,102 @@ void EncodeFieldValueImpl(Writer* writer, const FieldValue& field_value) { FieldValue DecodeFieldValueImpl(Reader* reader) { if (!reader->status().ok()) return FieldValue::NullValue(); - Tag tag = reader->ReadTag(); - if (!reader->status().ok()) return FieldValue::NullValue(); + // There needs to be at least one entry in the FieldValue. + if (reader->bytes_left() == 0) { + reader->set_status(Status(FirestoreErrorCode::DataLoss, + "Input Value proto missing contents")); + return FieldValue::NullValue(); + } - // Ensure the tag matches the wire type - switch (tag.field_number) { - case google_firestore_v1beta1_Value_null_value_tag: - case google_firestore_v1beta1_Value_boolean_value_tag: - case google_firestore_v1beta1_Value_integer_value_tag: - if (tag.wire_type != PB_WT_VARINT) { - reader->set_status( - Status(FirestoreErrorCode::DataLoss, - "Input proto bytes cannot be parsed (mismatch between " - "the wiretype and the field number (tag))")); - } - break; + FieldValue result = FieldValue::NullValue(); - case google_firestore_v1beta1_Value_string_value_tag: - case google_firestore_v1beta1_Value_timestamp_value_tag: - case google_firestore_v1beta1_Value_map_value_tag: - if (tag.wire_type != PB_WT_STRING) { - reader->set_status( - Status(FirestoreErrorCode::DataLoss, - "Input proto bytes cannot be parsed (mismatch between " - "the wiretype and the field number (tag))")); - } - break; + while (reader->bytes_left()) { + Tag tag = reader->ReadTag(); + if (!reader->status().ok()) return FieldValue::NullValue(); - default: - // We could get here for one of two reasons; either because the input - // bytes are corrupt, or because we're attempting to parse a tag that we - // haven't implemented yet. Long term, the latter reason should become - // less likely (especially in production), so we'll assume former. - - // TODO(rsgowman): While still in development, we'll contradict the above - // and assume the latter. Remove the following assertion when we're - // confident that we're handling all the tags in the protos. - HARD_FAIL( - "Unhandled message field number (tag): %s. (Or possibly " - "corrupt input bytes)", - tag.field_number); - reader->set_status(Status( - FirestoreErrorCode::DataLoss, - "Input proto bytes cannot be parsed (invalid field number (tag))")); - } + // Ensure the tag matches the wire type + switch (tag.field_number) { + case google_firestore_v1beta1_Value_null_value_tag: + case google_firestore_v1beta1_Value_boolean_value_tag: + case google_firestore_v1beta1_Value_integer_value_tag: + if (tag.wire_type != PB_WT_VARINT) { + reader->set_status( + Status(FirestoreErrorCode::DataLoss, + "Input proto bytes cannot be parsed (mismatch between " + "the wiretype and the field number (tag))")); + } + break; - if (!reader->status().ok()) return FieldValue::NullValue(); + case google_firestore_v1beta1_Value_string_value_tag: + case google_firestore_v1beta1_Value_timestamp_value_tag: + case google_firestore_v1beta1_Value_map_value_tag: + if (tag.wire_type != PB_WT_STRING) { + reader->set_status( + Status(FirestoreErrorCode::DataLoss, + "Input proto bytes cannot be parsed (mismatch between " + "the wiretype and the field number (tag))")); + } + break; - switch (tag.field_number) { - case google_firestore_v1beta1_Value_null_value_tag: - reader->ReadNull(); - return FieldValue::NullValue(); - case google_firestore_v1beta1_Value_boolean_value_tag: - return FieldValue::BooleanValue(reader->ReadBool()); - case google_firestore_v1beta1_Value_integer_value_tag: - return FieldValue::IntegerValue(reader->ReadInteger()); - case google_firestore_v1beta1_Value_string_value_tag: - return FieldValue::StringValue(reader->ReadString()); - case google_firestore_v1beta1_Value_timestamp_value_tag: - return FieldValue::TimestampValue( - reader->ReadNestedMessage<Timestamp>(DecodeTimestamp)); - case google_firestore_v1beta1_Value_map_value_tag: - return FieldValue::ObjectValueFromMap( - reader->ReadNestedMessage<ObjectValue::Map>(DecodeMapValue)); + case google_firestore_v1beta1_Value_double_value_tag: + case google_firestore_v1beta1_Value_bytes_value_tag: + case google_firestore_v1beta1_Value_reference_value_tag: + case google_firestore_v1beta1_Value_geo_point_value_tag: + case google_firestore_v1beta1_Value_array_value_tag: + // TODO(b/74243929): Implement remaining types. + HARD_FAIL("Unhandled message field number (tag): %i.", + tag.field_number); - default: - // This indicates an internal error as we've already ensured that this is - // a valid field_number. - HARD_FAIL( - "Somehow got an unexpected field number (tag) after verifying that " - "the field number was expected."); + default: + // Unknown tag. According to the proto spec, we need to ignore these. No + // action required here, though we'll need to skip the relevant bytes + // below. + break; + } + + if (!reader->status().ok()) return FieldValue::NullValue(); + + switch (tag.field_number) { + case google_firestore_v1beta1_Value_null_value_tag: + reader->ReadNull(); + result = FieldValue::NullValue(); + break; + case google_firestore_v1beta1_Value_boolean_value_tag: + result = FieldValue::BooleanValue(reader->ReadBool()); + break; + case google_firestore_v1beta1_Value_integer_value_tag: + result = FieldValue::IntegerValue(reader->ReadInteger()); + break; + case google_firestore_v1beta1_Value_string_value_tag: + result = FieldValue::StringValue(reader->ReadString()); + break; + case google_firestore_v1beta1_Value_timestamp_value_tag: + result = FieldValue::TimestampValue( + reader->ReadNestedMessage<Timestamp>(DecodeTimestamp)); + break; + case google_firestore_v1beta1_Value_map_value_tag: + // TODO(rsgowman): We should merge the existing map (if any) with the + // newly parsed map. + result = FieldValue::ObjectValueFromMap( + reader->ReadNestedMessage<ObjectValue::Map>(DecodeMapValue)); + break; + + case google_firestore_v1beta1_Value_double_value_tag: + case google_firestore_v1beta1_Value_bytes_value_tag: + case google_firestore_v1beta1_Value_reference_value_tag: + case google_firestore_v1beta1_Value_geo_point_value_tag: + case google_firestore_v1beta1_Value_array_value_tag: + // TODO(b/74243929): Implement remaining types. + HARD_FAIL("Unhandled message field number (tag): %i.", + tag.field_number); + + default: + // Unknown tag. According to the proto spec, we need to ignore these. + reader->SkipField(tag); + } } + + return result; } /** @@ -504,7 +529,7 @@ std::unique_ptr<MaybeDocument> Serializer::DecodeBatchGetDocumentsResponse( // Initialize BatchGetDocumentsResponse fields to their default values std::unique_ptr<MaybeDocument> found; std::string missing; - // TODO(rsgowman): transaction + // We explicitly ignore the 'transaction' field SnapshotVersion read_time = SnapshotVersion::None(); while (reader->bytes_left()) { @@ -515,6 +540,7 @@ std::unique_ptr<MaybeDocument> Serializer::DecodeBatchGetDocumentsResponse( switch (tag.field_number) { case google_firestore_v1beta1_BatchGetDocumentsResponse_found_tag: case google_firestore_v1beta1_BatchGetDocumentsResponse_missing_tag: + case google_firestore_v1beta1_BatchGetDocumentsResponse_transaction_tag: case google_firestore_v1beta1_BatchGetDocumentsResponse_read_time_tag: if (tag.wire_type != PB_WT_STRING) { reader->set_status( @@ -524,14 +550,11 @@ std::unique_ptr<MaybeDocument> Serializer::DecodeBatchGetDocumentsResponse( } break; - case google_firestore_v1beta1_BatchGetDocumentsResponse_transaction_tag: - // TODO(rsgowman) - abort(); - default: - reader->set_status(Status( - FirestoreErrorCode::DataLoss, - "Input proto bytes cannot be parsed (invalid field number (tag))")); + // Unknown tag. According to the proto spec, we need to ignore these. No + // action required here, though we'll need to skip the relevant bytes + // below. + break; } if (!reader->status().ok()) return nullptr; @@ -559,8 +582,13 @@ std::unique_ptr<MaybeDocument> Serializer::DecodeBatchGetDocumentsResponse( break; case google_firestore_v1beta1_BatchGetDocumentsResponse_transaction_tag: - // TODO(rsgowman) - abort(); + // This field is ignored by the client sdk, but we still need to extract + // it. + // TODO(rsgowman) switch this to reader->SkipField() (or whatever we end + // up calling it) once that exists. Possibly group this with other + // ignored and/or unknown fields + reader->ReadString(); + break; case google_firestore_v1beta1_BatchGetDocumentsResponse_read_time_tag: read_time = SnapshotVersion{ @@ -568,11 +596,8 @@ std::unique_ptr<MaybeDocument> Serializer::DecodeBatchGetDocumentsResponse( break; default: - // This indicates an internal error as we've already ensured that this - // is a valid field_number. - HARD_FAIL( - "Somehow got an unexpected field number (tag) after verifying that " - "the field number was expected."); + // Unknown tag. According to the proto spec, we need to ignore these. + reader->SkipField(tag); } } @@ -581,9 +606,10 @@ std::unique_ptr<MaybeDocument> Serializer::DecodeBatchGetDocumentsResponse( } else if (!missing.empty()) { return absl::make_unique<NoDocument>(DecodeKey(missing), read_time); } else { - // Neither 'found' nor 'missing' fields were set. - // TODO(rsgowman): Handle the error case. - abort(); + reader->set_status(Status(FirestoreErrorCode::DataLoss, + "Invalid BatchGetDocumentsReponse message: " + "Neither 'found' nor 'missing' fields set.")); + return nullptr; } } diff --git a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt index 043713f..30589a0 100644 --- a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt @@ -29,6 +29,7 @@ cc_library( absl_strings ) + ## assert and log cc_library( @@ -158,16 +159,6 @@ else() endif() -cc_library( - firebase_firestore_util_async_queue - SOURCES - async_queue.cc - async_queue.h - DEPENDS - ${FIREBASE_FIRESTORE_UTIL_EXECUTOR} - ${FIREBASE_FIRESTORE_UTIL_LOG} - EXCLUDE_FROM_ALL -) ## main library @@ -179,6 +170,8 @@ configure_file( cc_library( firebase_firestore_util SOURCES + async_queue.cc + async_queue.h autoid.cc autoid.h bits.cc @@ -200,10 +193,11 @@ cc_library( statusor_internals.h string_util.cc string_util.h + type_traits.h DEPENDS absl_base firebase_firestore_util_base - firebase_firestore_util_async_queue + ${FIREBASE_FIRESTORE_UTIL_EXECUTOR} ${FIREBASE_FIRESTORE_UTIL_LOG} ${FIREBASE_FIRESTORE_UTIL_RANDOM} ) diff --git a/Firestore/core/src/firebase/firestore/util/comparison.cc b/Firestore/core/src/firebase/firestore/util/comparison.cc index 5ac4c27..d1cdbfa 100644 --- a/Firestore/core/src/firebase/firestore/util/comparison.cc +++ b/Firestore/core/src/firebase/firestore/util/comparison.cc @@ -31,6 +31,12 @@ bool Comparator<absl::string_view>::operator()( return left < right; } +bool Comparator<std::string>::operator()(const std::string& left, + const std::string& right) const { + // TODO(wilhuff): truncation aware comparison + return left < right; +} + bool Comparator<double>::operator()(double left, double right) const { // NaN sorts equal to itself and before any other number. if (left < right) { diff --git a/Firestore/core/src/firebase/firestore/util/comparison.h b/Firestore/core/src/firebase/firestore/util/comparison.h index d7f4dfd..a7d7944 100644 --- a/Firestore/core/src/firebase/firestore/util/comparison.h +++ b/Firestore/core/src/firebase/firestore/util/comparison.h @@ -86,6 +86,11 @@ struct Comparator<absl::string_view> { const absl::string_view& right) const; }; +template <> +struct Comparator<std::string> { + bool operator()(const std::string& left, const std::string& right) const; +}; + /** Compares two bools: false < true. */ template <> struct Comparator<bool> : public std::less<bool> {}; diff --git a/Firestore/core/src/firebase/firestore/util/hard_assert_apple.mm b/Firestore/core/src/firebase/firestore/util/hard_assert_apple.mm index 3324fe8..6abd324 100644 --- a/Firestore/core/src/firebase/firestore/util/hard_assert_apple.mm +++ b/Firestore/core/src/firebase/firestore/util/hard_assert_apple.mm @@ -32,8 +32,8 @@ void Fail(const char* file, const int line, const std::string& message) { [[NSAssertionHandler currentHandler] - handleFailureInFunction:WrapNSStringNoCopy(func) - file:WrapNSStringNoCopy(file) + handleFailureInFunction:WrapNSString(func) + file:WrapNSString(file) lineNumber:line description:@"FIRESTORE INTERNAL ASSERTION FAILED: %s", message.c_str()]; diff --git a/Firestore/core/src/firebase/firestore/util/hashing.h b/Firestore/core/src/firebase/firestore/util/hashing.h index d8058c8..21c0bd6 100644 --- a/Firestore/core/src/firebase/firestore/util/hashing.h +++ b/Firestore/core/src/firebase/firestore/util/hashing.h @@ -18,6 +18,7 @@ #define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_HASHING_H_ #include <iterator> +#include <string> #include <type_traits> namespace firebase { @@ -49,6 +50,41 @@ namespace util { namespace impl { /** + * A type trait that identifies whether or not std::hash is available for a + * given type. + * + * This type should not be necessary since specialization failure on an + * expression like `decltype(std::hash<K>{}(value)` should be enough to disable + * overloads that require `std::hash` to be defined but unfortunately some + * standard libraries ship with std::hash defined for all types that only + * fail later (e.g. via static_assert). One such implementation is the libc++ + * that ships with Xcode 8.3.3, which is a supported platform. + */ +template <typename T> +struct has_std_hash { + // There may be other types for which std::hash is defined but they don't + // matter for our purposes. + enum { + value = std::is_arithmetic<T>{} || std::is_pointer<T>{} || + std::is_same<T, std::string>{} + }; + + constexpr operator bool() const { + return value; + } +}; + +/** + * A type that's equivalent to size_t if std::hash<T> is defined or a compile + * error otherwise. + * + * This is effectively just a safe implementation of + * `decltype(std::hash<T>{}(std::declval<T>()))`. + */ +template <typename T> +using std_hash_type = typename std::enable_if<has_std_hash<T>{}, size_t>::type; + +/** * Combines a hash_value with whatever accumulated state there is so far. */ inline size_t Combine(size_t state, size_t hash_value) { @@ -100,8 +136,7 @@ auto RankedInvokeHash(const K& value, HashChoice<0>) -> decltype(value.Hash()) { * @return The result of `std::hash<K>{}(value)` */ template <typename K> -auto RankedInvokeHash(const K& value, HashChoice<1>) - -> decltype(std::hash<K>{}(value)) { +std_hash_type<K> RankedInvokeHash(const K& value, HashChoice<1>) { return std::hash<K>{}(value); } diff --git a/Firestore/core/src/firebase/firestore/util/string_format.h b/Firestore/core/src/firebase/firestore/util/string_format.h index d691984..01776a9 100644 --- a/Firestore/core/src/firebase/firestore/util/string_format.h +++ b/Firestore/core/src/firebase/firestore/util/string_format.h @@ -22,6 +22,7 @@ #include <utility> #include "Firestore/core/src/firebase/firestore/util/string_apple.h" +#include "Firestore/core/src/firebase/firestore/util/type_traits.h" #include "absl/base/attributes.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" @@ -43,7 +44,7 @@ template <int I> struct FormatChoice : FormatChoice<I + 1> {}; template <> -struct FormatChoice<4> {}; +struct FormatChoice<5> {}; } // namespace internal @@ -87,8 +88,8 @@ class FormatArg : public absl::AlphaNum { */ template < typename T, - typename = typename std::enable_if<std::is_base_of<NSObject, T>{}>::type> - FormatArg(T* object, internal::FormatChoice<0>) + typename = typename std::enable_if<is_objective_c_pointer<T>{}>::type> + FormatArg(T object, internal::FormatChoice<1>) : AlphaNum{MakeStringView([object description])} { } @@ -96,20 +97,9 @@ class FormatArg : public absl::AlphaNum { * Creates a FormatArg from any Objective-C Class type. Objective-C Class * types are a special struct that aren't of a type derived from NSObject. */ - FormatArg(Class object, internal::FormatChoice<0>) + FormatArg(Class object, internal::FormatChoice<1>) : AlphaNum{MakeStringView(NSStringFromClass(object))} { } - - /** - * Creates a FormatArg from any id pointer. Note that instances of `id<Foo>` - * (which means "pointer conforming to the protocol Foo") do not match this - * without first casting to type `id`. There's no way to express a template of - * `id<T>` since `id<Foo>` isn't actually a C++ template and `id` isn't a - * parameterized C++ class. - */ - FormatArg(id object, internal::FormatChoice<0>) - : AlphaNum{MakeStringView([object description])} { - } #endif /** @@ -117,7 +107,7 @@ class FormatArg : public absl::AlphaNum { * handled specially to avoid ambiguity with generic pointers, which are * handled differently. */ - FormatArg(std::nullptr_t, internal::FormatChoice<1>) : AlphaNum{"null"} { + FormatArg(std::nullptr_t, internal::FormatChoice<2>) : AlphaNum{"null"} { } /** @@ -125,7 +115,7 @@ class FormatArg : public absl::AlphaNum { * handled specially to avoid ambiguity with generic pointers, which are * handled differently. */ - FormatArg(const char* string_value, internal::FormatChoice<2>) + FormatArg(const char* string_value, internal::FormatChoice<3>) : AlphaNum{string_value == nullptr ? "null" : string_value} { } @@ -134,7 +124,7 @@ class FormatArg : public absl::AlphaNum { * hexidecimal integer literal. */ template <typename T> - FormatArg(T* pointer_value, internal::FormatChoice<3>) + FormatArg(T* pointer_value, internal::FormatChoice<4>) : AlphaNum{absl::Hex{reinterpret_cast<uintptr_t>(pointer_value)}} { } @@ -143,7 +133,7 @@ class FormatArg : public absl::AlphaNum { * absl::AlphaNum accepts. */ template <typename T> - FormatArg(T&& value, internal::FormatChoice<4>) + FormatArg(T&& value, internal::FormatChoice<5>) : AlphaNum{std::forward<T>(value)} { } }; diff --git a/Firestore/core/src/firebase/firestore/util/string_util.h b/Firestore/core/src/firebase/firestore/util/string_util.h index 96ba0b0..86acc56 100644 --- a/Firestore/core/src/firebase/firestore/util/string_util.h +++ b/Firestore/core/src/firebase/firestore/util/string_util.h @@ -65,11 +65,6 @@ std::string PrefixSuccessor(absl::string_view prefix); */ std::string ImmediateSuccessor(absl::string_view s); -/** - * Returns true if the given value starts with the given prefix. - */ -bool StartsWith(const std::string &value, const std::string &prefix); - } // namespace util } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/util/type_traits.h b/Firestore/core/src/firebase/firestore/util/type_traits.h new file mode 100644 index 0000000..52feb6b --- /dev/null +++ b/Firestore/core/src/firebase/firestore/util/type_traits.h @@ -0,0 +1,90 @@ +/* + * 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_UTIL_TYPE_TRAITS_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_TYPE_TRAITS_H_ + +#if __OBJC__ +#import <objc/objc.h> // for id +#endif + +#include <type_traits> + +namespace firebase { +namespace firestore { +namespace util { + +#if __OBJC__ + +/** + * A type trait that identifies whether or not the given pointer points to an + * Objective-C object. + * + * is_objective_c_pointer<NSObject*>::value == true + * is_objective_c_pointer<NSArray<NSString*>*>::value == true + * + * // id is a dynamically typed pointer to an Objective-C object. + * is_objective_c_pointer<id>::value == true + * + * // pointers to C++ classes are not Objective-C pointers. + * is_objective_c_pointer<void*>::value == false + * is_objective_c_pointer<std::string*>::value == false + * is_objective_c_pointer<std::unique_ptr<int>>::value == false + */ +template <typename T> +struct is_objective_c_pointer { + private: + using yes_type = char (&)[10]; + using no_type = char (&)[1]; + + /** + * A non-existent function declared to produce a pointer to type T (which is + * consistent with the way Objective-C objects are referenced). + * + * Note that there is no definition for this function but that's okay because + * we only need it to reason about the function's type at compile type. + */ + static T Pointer(); + + static yes_type Choose(id value); + static no_type Choose(...); + + public: + using value_type = bool; + + enum { value = sizeof(Choose(Pointer())) == sizeof(yes_type) }; + + constexpr operator bool() const { + return value; + } + + constexpr bool operator()() const { + return value; + } +}; + +// Hard-code the answer for `void` because you can't pass arguments of type +// `void` to another function. +template <> +struct is_objective_c_pointer<void> : public std::false_type {}; + +#endif // __OBJC__ + +} // namespace util +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_TYPE_TRAITS_H_ diff --git a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc index ba9ea47..1125fb4 100644 --- a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc +++ b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc @@ -146,13 +146,21 @@ class SerializerTest : public ::testing::Test { * * @param status the expected (failed) status. Only the code() is verified. */ - void ExpectFailedStatusDuringDecode(Status status, - const std::vector<uint8_t>& bytes) { + void ExpectFailedStatusDuringFieldValueDecode( + Status status, const std::vector<uint8_t>& bytes) { StatusOr<FieldValue> bad_status = serializer.DecodeFieldValue(bytes); ASSERT_NOT_OK(bad_status); EXPECT_EQ(status.code(), bad_status.status().code()); } + void ExpectFailedStatusDuringMaybeDocumentDecode( + Status status, const std::vector<uint8_t>& bytes) { + StatusOr<std::unique_ptr<MaybeDocument>> bad_status = + serializer.DecodeMaybeDocument(bytes); + ASSERT_NOT_OK(bad_status); + EXPECT_EQ(status.code(), bad_status.status().code()); + } + v1beta1::Value ValueProto(nullptr_t) { std::vector<uint8_t> bytes = EncodeFieldValue(&serializer, FieldValue::NullValue()); @@ -230,20 +238,21 @@ class SerializerTest : public ::testing::Test { /** * Creates entries in the proto that we don't care about. * - * We ignore create time in our serializer. We never set it, and never read it - * (other than to throw it away). But the server could (and probably does) set - * it, so we need to be able to discard it properly. The ExpectRoundTrip deals - * with this asymmetry. + * We ignore certain fields in our serializer. We never set them, and never + * read them (other than to throw them away). But the server could (and + * probably does) set them, so we need to be able to discard them properly. + * The ExpectRoundTrip deals with this asymmetry. * * This method adds these ignored fields to the proto. */ void TouchIgnoredBatchGetDocumentsResponseFields( v1beta1::BatchGetDocumentsResponse* proto) { + proto->set_transaction("random bytes"); + // TODO(rsgowman): This method currently assumes that this is a 'found' // document. We (probably) will need to adjust this to work with NoDocuments // too. v1beta1::Document* doc_proto = proto->mutable_found(); - google::protobuf::Timestamp* create_time_proto = doc_proto->mutable_create_time(); create_time_proto->set_seconds(8765); @@ -465,6 +474,63 @@ TEST_F(SerializerTest, EncodesNestedObjects) { ExpectRoundTrip(model, proto, FieldValue::Type::Object); } +TEST_F(SerializerTest, EncodesFieldValuesWithRepeatedEntries) { + // Technically, serialized Value protos can contain multiple values. (The last + // one "wins".) However, well-behaved proto emitters (such as libprotobuf) + // won't generate that, so to test, we either need to use hand-crafted, raw + // bytes or use a proto message that's *almost* the same as the real one, such + // that when it's encoded, you can generate these repeated fields. (This is + // how libprotobuf tests itself.) + // + // Using libprotobuf for this purpose is mildly inconvenient for us, since we + // don't run protoc as part of the build process, so we'd need to either add + // these fake messages to our protos tree (Protos/testprotos?) and then check + // in the results (which isn't great when writing new tests). Fortunately, we + // have another alternative: nanopb. + // + // So we'll create a nanopb struct that *looks* like + // google_firestore_v1beta1_Value, and then populate and serialize it using + // the normal nanopb mechanisms. This should give us a wire-compatible Value + // message, but with multiple values set. + + // Copy of the real one (from the nanopb generated document.pb.h), but with + // only boolean_value and integer_value. + struct google_firestore_v1beta1_Value_Fake { + bool boolean_value; + int64_t integer_value; + }; + + // Copy of the real one (from the nanopb generated document.pb.c), but with + // only boolean_value and integer_value. + const pb_field_t google_firestore_v1beta1_Value_fields_Fake[3] = { + PB_FIELD(1, BOOL, SINGULAR, STATIC, FIRST, + google_firestore_v1beta1_Value_Fake, boolean_value, + boolean_value, 0), + PB_FIELD(2, INT64, SINGULAR, STATIC, OTHER, + google_firestore_v1beta1_Value_Fake, integer_value, + boolean_value, 0), + PB_LAST_FIELD, + }; + + // Craft the bytes. boolean_value has a smaller tag, so it'll get encoded + // first. Implying integer_value should "win". + google_firestore_v1beta1_Value_Fake crafty_value{false, int64_t{42}}; + std::vector<uint8_t> bytes(128); + pb_ostream_t stream = pb_ostream_from_buffer(bytes.data(), bytes.size()); + pb_encode(&stream, google_firestore_v1beta1_Value_fields_Fake, &crafty_value); + bytes.resize(stream.bytes_written); + + // Decode the bytes into the model + StatusOr<FieldValue> actual_model_status = serializer.DecodeFieldValue(bytes); + EXPECT_OK(actual_model_status); + FieldValue actual_model = actual_model_status.ValueOrDie(); + + // Ensure the decoded model is as expected. + FieldValue expected_model = FieldValue::IntegerValue(42); + EXPECT_EQ(FieldValue::Type::Integer, actual_model.type()); + EXPECT_EQ(expected_model, actual_model); +} + TEST_F(SerializerTest, BadNullValue) { std::vector<uint8_t> bytes = EncodeFieldValue(&serializer, FieldValue::NullValue()); @@ -472,7 +538,7 @@ TEST_F(SerializerTest, BadNullValue) { // Alter the null value from 0 to 1. Mutate(&bytes[1], /*expected_initial_value=*/0, /*new_value=*/1); - ExpectFailedStatusDuringDecode( + ExpectFailedStatusDuringFieldValueDecode( Status(FirestoreErrorCode::DataLoss, "ignored"), bytes); } @@ -483,7 +549,7 @@ TEST_F(SerializerTest, BadBoolValue) { // Alter the bool value from 1 to 2. (Value values are 0,1) Mutate(&bytes[1], /*expected_initial_value=*/1, /*new_value=*/2); - ExpectFailedStatusDuringDecode( + ExpectFailedStatusDuringFieldValueDecode( Status(FirestoreErrorCode::DataLoss, "ignored"), bytes); } @@ -502,7 +568,7 @@ TEST_F(SerializerTest, BadIntegerValue) { bytes.resize(12); bytes[11] = 0x7f; - ExpectFailedStatusDuringDecode( + ExpectFailedStatusDuringFieldValueDecode( Status(FirestoreErrorCode::DataLoss, "ignored"), bytes); } @@ -514,7 +580,7 @@ TEST_F(SerializerTest, BadStringValue) { // used by the encoded tag.) Mutate(&bytes[2], /*expected_initial_value=*/1, /*new_value=*/5); - ExpectFailedStatusDuringDecode( + ExpectFailedStatusDuringFieldValueDecode( Status(FirestoreErrorCode::DataLoss, "ignored"), bytes); } @@ -525,7 +591,7 @@ TEST_F(SerializerTest, BadTimestampValue_TooLarge) { // Add some time, which should push us above the maximum allowed timestamp. Mutate(&bytes[4], 0x82, 0x83); - ExpectFailedStatusDuringDecode( + ExpectFailedStatusDuringFieldValueDecode( Status(FirestoreErrorCode::DataLoss, "ignored"), bytes); } @@ -536,11 +602,16 @@ TEST_F(SerializerTest, BadTimestampValue_TooSmall) { // Remove some time, which should push us below the minimum allowed timestamp. Mutate(&bytes[4], 0x92, 0x91); - ExpectFailedStatusDuringDecode( + ExpectFailedStatusDuringFieldValueDecode( Status(FirestoreErrorCode::DataLoss, "ignored"), bytes); } -TEST_F(SerializerTest, BadTag) { +TEST_F(SerializerTest, BadFieldValueTagAndNoOtherTagPresent) { + // A bad tag should be ignored. But if there are *no* valid tags, then we + // don't know the type of the FieldValue. Although it might be reasonable to + // assume some sort of default type in this situation, we've decided to fail + // the deserialization process in this case instead. + std::vector<uint8_t> bytes = EncodeFieldValue(&serializer, FieldValue::NullValue()); @@ -550,15 +621,57 @@ TEST_F(SerializerTest, BadTag) { // Specifically 31. 0xf8 represents field number 31 encoded as a varint. Mutate(&bytes[0], /*expected_initial_value=*/0x58, /*new_value=*/0xf8); - // TODO(rsgowman): The behaviour is *temporarily* slightly different during - // development; this will cause a failed assertion rather than a failed - // status. Remove this EXPECT_ANY_THROW statement (and reenable the - // following commented out statement) once the corresponding assert has been - // removed from serializer.cc. - EXPECT_ANY_THROW(ExpectFailedStatusDuringDecode( - Status(FirestoreErrorCode::DataLoss, "ignored"), bytes)); - // ExpectFailedStatusDuringDecode( - // Status(FirestoreErrorCode::DataLoss, "ignored"), bytes); + ExpectFailedStatusDuringFieldValueDecode( + Status(FirestoreErrorCode::DataLoss, "ignored"), bytes); +} + +TEST_F(SerializerTest, BadFieldValueTagWithOtherValidTagsPresent) { + // A bad tag should be ignored, in which case, we should successfully + // deserialize the rest of the bytes as if it wasn't there. To craft these + // bytes, we'll use the same technique as + // EncodesFieldValuesWithRepeatedEntries (so go read the comments there + // first). + + // Copy of the real one (from the nanopb generated document.pb.h), but with + // only boolean_value and integer_value. + struct google_firestore_v1beta1_Value_Fake { + bool boolean_value; + int64_t integer_value; + }; + + // Copy of the real one (from the nanopb generated document.pb.c), but with + // only boolean_value and integer_value. Also modified such that integer_value + // now has an invalid tag (instead of 2). + const int invalid_tag = 31; + const pb_field_t google_firestore_v1beta1_Value_fields_Fake[3] = { + PB_FIELD(1, BOOL, SINGULAR, STATIC, FIRST, + google_firestore_v1beta1_Value_Fake, boolean_value, + boolean_value, 0), + PB_FIELD(invalid_tag, INT64, SINGULAR, STATIC, OTHER, + google_firestore_v1beta1_Value_Fake, integer_value, + boolean_value, 0), + PB_LAST_FIELD, + }; + + // Craft the bytes. boolean_value has a smaller tag, so it'll get encoded + // first, normally implying integer_value should "win". Except that + // integer_value isn't a valid tag, so it should be ignored here. + google_firestore_v1beta1_Value_Fake crafty_value{true, int64_t{42}}; + std::vector<uint8_t> bytes(128); + pb_ostream_t stream = pb_ostream_from_buffer(bytes.data(), bytes.size()); + pb_encode(&stream, google_firestore_v1beta1_Value_fields_Fake, &crafty_value); + bytes.resize(stream.bytes_written); + + // Decode the bytes into the model + StatusOr<FieldValue> actual_model_status = serializer.DecodeFieldValue(bytes); + Status s = actual_model_status.status(); + EXPECT_OK(actual_model_status); + FieldValue actual_model = actual_model_status.ValueOrDie(); + + // Ensure the decoded model is as expected. + FieldValue expected_model = FieldValue::BooleanValue(true); + EXPECT_EQ(FieldValue::Type::Boolean, actual_model.type()); + EXPECT_EQ(expected_model, actual_model); } TEST_F(SerializerTest, TagVarintWiretypeStringMismatch) { @@ -570,7 +683,7 @@ TEST_F(SerializerTest, TagVarintWiretypeStringMismatch) { // would do.) Mutate(&bytes[0], /*expected_initial_value=*/0x08, /*new_value=*/0x0a); - ExpectFailedStatusDuringDecode( + ExpectFailedStatusDuringFieldValueDecode( Status(FirestoreErrorCode::DataLoss, "ignored"), bytes); } @@ -581,7 +694,7 @@ TEST_F(SerializerTest, TagStringWiretypeVarintMismatch) { // 0x88 represents a string value encoded as a varint. Mutate(&bytes[0], /*expected_initial_value=*/0x8a, /*new_value=*/0x88); - ExpectFailedStatusDuringDecode( + ExpectFailedStatusDuringFieldValueDecode( Status(FirestoreErrorCode::DataLoss, "ignored"), bytes); } @@ -594,13 +707,13 @@ TEST_F(SerializerTest, IncompleteFieldValue) { ASSERT_EQ(0x00, bytes[1]); bytes.pop_back(); - ExpectFailedStatusDuringDecode( + ExpectFailedStatusDuringFieldValueDecode( Status(FirestoreErrorCode::DataLoss, "ignored"), bytes); } TEST_F(SerializerTest, IncompleteTag) { std::vector<uint8_t> bytes; - ExpectFailedStatusDuringDecode( + ExpectFailedStatusDuringFieldValueDecode( Status(FirestoreErrorCode::DataLoss, "ignored"), bytes); } @@ -694,6 +807,69 @@ TEST_F(SerializerTest, EncodesNonEmptyDocument) { ExpectRoundTrip(key, fields, update_time, proto); } +TEST_F(SerializerTest, + BadBatchGetDocumentsResponseTagWithOtherValidTagsPresent) { + // A bad tag should be ignored, in which case, we should successfully + // deserialize the rest of the bytes as if it wasn't there. To craft these + // bytes, we'll use the same technique as + // EncodesFieldValuesWithRepeatedEntries (so go read the comments there + // first). + + // Copy of the real one (from the nanopb generated firestore.pb.h), but with + // only "missing" (a field from the original proto) and an extra_field. + struct google_firestore_v1beta1_BatchGetDocumentsResponse_Fake { + pb_callback_t missing; + int64_t extra_field; + }; + + // Copy of the real one (from the nanopb generated firestore.pb.c), but with + // only missing and an extra_field. Also modified such that extra_field + // now has a tag of 31. + const int invalid_tag = 31; + const pb_field_t + google_firestore_v1beta1_BatchGetDocumentsResponse_fields_Fake[3] = { + PB_FIELD(2, STRING, SINGULAR, CALLBACK, FIRST, + google_firestore_v1beta1_BatchGetDocumentsResponse_Fake, + missing, missing, 0), + PB_FIELD(invalid_tag, INT64, SINGULAR, STATIC, OTHER, + google_firestore_v1beta1_BatchGetDocumentsResponse_Fake, + extra_field, missing, 0), + PB_LAST_FIELD, + }; + + const char* missing_value = "projects/p/databases/d/documents/one/two"; + google_firestore_v1beta1_BatchGetDocumentsResponse_Fake crafty_value; + crafty_value.missing.funcs.encode = + [](pb_ostream_t* stream, const pb_field_t* field, void* const* arg) { + const char* missing_value = static_cast<const char*>(*arg); + if (!pb_encode_tag_for_field(stream, field)) return false; + return pb_encode_string(stream, + reinterpret_cast<const uint8_t*>(missing_value), + strlen(missing_value)); + }; + crafty_value.missing.arg = const_cast<char*>(missing_value); + crafty_value.extra_field = 42; + + std::vector<uint8_t> bytes(128); + pb_ostream_t stream = pb_ostream_from_buffer(bytes.data(), bytes.size()); + pb_encode(&stream, + google_firestore_v1beta1_BatchGetDocumentsResponse_fields_Fake, + &crafty_value); + bytes.resize(stream.bytes_written); + + // Decode the bytes into the model + StatusOr<std::unique_ptr<MaybeDocument>> actual_model_status = + serializer.DecodeMaybeDocument(bytes); + EXPECT_OK(actual_model_status); + std::unique_ptr<MaybeDocument> actual_model = + std::move(actual_model_status).ValueOrDie(); + + // Ensure the decoded model is as expected. + NoDocument expected_model = + NoDocument(Key("one/two"), SnapshotVersion::None()); + EXPECT_EQ(expected_model, *actual_model.get()); +} + TEST_F(SerializerTest, DecodesNoDocument) { // We can't actually *encode* a NoDocument; the method exposed by the // serializer requires both the document key and contents (as an ObjectValue, @@ -713,5 +889,16 @@ TEST_F(SerializerTest, DecodesNoDocument) { ExpectNoDocumentDeserializationRoundTrip(key, read_time, proto); } +TEST_F(SerializerTest, DecodeMaybeDocWithoutFoundOrMissingSetShouldFail) { + v1beta1::BatchGetDocumentsResponse proto; + + std::vector<uint8_t> bytes(proto.ByteSizeLong()); + bool status = proto.SerializeToArray(bytes.data(), bytes.size()); + EXPECT_TRUE(status); + + ExpectFailedStatusDuringMaybeDocumentDecode( + Status(FirestoreErrorCode::DataLoss, "ignored"), bytes); +} + // TODO(rsgowman): Test [en|de]coding multiple protos into the same output // vector. diff --git a/Firestore/core/test/firebase/firestore/util/CMakeLists.txt b/Firestore/core/test/firebase/firestore/util/CMakeLists.txt index bcb1c84..44a3b61 100644 --- a/Firestore/core/test/firebase/firestore/util/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/util/CMakeLists.txt @@ -98,7 +98,7 @@ cc_test( async_tests_util.h DEPENDS firebase_firestore_util_executor_std - firebase_firestore_util_async_queue + firebase_firestore_util ) if(HAVE_LIBDISPATCH) @@ -111,7 +111,7 @@ if(HAVE_LIBDISPATCH) async_tests_util.h DEPENDS firebase_firestore_util_executor_libdispatch - firebase_firestore_util_async_queue + firebase_firestore_util ) endif() @@ -137,3 +137,12 @@ cc_test( firebase_firestore_util gmock ) + +if(APPLE) + target_sources( + firebase_firestore_util_test + PUBLIC + string_format_apple_test.mm + type_traits_apple_test.mm + ) +endif() diff --git a/Firestore/core/test/firebase/firestore/util/hashing_test.cc b/Firestore/core/test/firebase/firestore/util/hashing_test.cc index e5d9ff8..2c5c2f7 100644 --- a/Firestore/core/test/firebase/firestore/util/hashing_test.cc +++ b/Firestore/core/test/firebase/firestore/util/hashing_test.cc @@ -16,6 +16,9 @@ #include "Firestore/core/src/firebase/firestore/util/hashing.h" +#include <map> +#include <string> + #include "absl/strings/string_view.h" #include "gtest/gtest.h" @@ -29,6 +32,21 @@ struct HasHashMember { } }; +TEST(HashingTest, HasStdHash) { + EXPECT_TRUE(impl::has_std_hash<float>::value); + EXPECT_TRUE(impl::has_std_hash<double>::value); + EXPECT_TRUE(impl::has_std_hash<int>::value); + EXPECT_TRUE(impl::has_std_hash<int64_t>::value); + EXPECT_TRUE(impl::has_std_hash<std::string>::value); + EXPECT_TRUE(impl::has_std_hash<void*>::value); + EXPECT_TRUE(impl::has_std_hash<const char*>::value); + + struct Foo {}; + EXPECT_FALSE(impl::has_std_hash<Foo>::value); + EXPECT_FALSE(impl::has_std_hash<absl::string_view>::value); + EXPECT_FALSE((impl::has_std_hash<std::map<std::string, std::string>>::value)); +} + TEST(HashingTest, Int) { ASSERT_EQ(std::hash<int>{}(0), Hash(0)); } diff --git a/Firestore/core/test/firebase/firestore/util/string_format_apple_test.mm b/Firestore/core/test/firebase/firestore/util/string_format_apple_test.mm new file mode 100644 index 0000000..f0bcd35 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/util/string_format_apple_test.mm @@ -0,0 +1,60 @@ +/* + * 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/util/string_format.h" + +#import <Foundation/NSString.h> + +#include "gtest/gtest.h" + +@interface FSTDescribable : NSObject +@end + +@implementation FSTDescribable + +- (NSString*)description { + return @"description"; +} + +@end + +namespace firebase { +namespace firestore { +namespace util { + +TEST(StringFormatTest, NSString) { + EXPECT_EQ("Hello World", StringFormat("Hello %s", @"World")); + + NSString* hello = [NSString stringWithUTF8String:"Hello"]; + EXPECT_EQ("Hello World", StringFormat("%s World", hello)); + + // NOLINTNEXTLINE false positive on "string" + NSMutableString* world = [NSMutableString string]; + [world appendString:@"World"]; + EXPECT_EQ("Hello World", StringFormat("Hello %s", world)); +} + +TEST(StringFormatTest, FSTDescribable) { + FSTDescribable* desc = [[FSTDescribable alloc] init]; + EXPECT_EQ("Hello description", StringFormat("Hello %s", desc)); + + id desc_id = desc; + EXPECT_EQ("Hello description", StringFormat("Hello %s", desc_id)); +} + +} // namespace util +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/util/type_traits_apple_test.mm b/Firestore/core/test/firebase/firestore/util/type_traits_apple_test.mm new file mode 100644 index 0000000..dfb03bb --- /dev/null +++ b/Firestore/core/test/firebase/firestore/util/type_traits_apple_test.mm @@ -0,0 +1,50 @@ +/* + * 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/util/type_traits.h" + +#import <Foundation/NSArray.h> +#import <Foundation/NSObject.h> +#import <Foundation/NSString.h> + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace util { + +TEST(TypeTraitsTest, IsObjectiveCPointer) { + static_assert(is_objective_c_pointer<NSObject*>{}, "NSObject"); + static_assert(is_objective_c_pointer<NSString*>{}, "NSString"); + static_assert(is_objective_c_pointer<NSArray<NSString*>*>{}, + "NSArray<NSString*>"); + + static_assert(is_objective_c_pointer<id>{}, "id"); + static_assert(is_objective_c_pointer<id<NSCopying>>{}, "id<NSCopying>"); + + static_assert(!is_objective_c_pointer<int*>{}, "int*"); + static_assert(!is_objective_c_pointer<void*>{}, "void*"); + static_assert(!is_objective_c_pointer<int>{}, "int"); + static_assert(!is_objective_c_pointer<void>{}, "void"); + + struct Foo {}; + static_assert(!is_objective_c_pointer<Foo>{}, "Foo"); + static_assert(!is_objective_c_pointer<Foo*>{}, "Foo"); +} + +} // namespace util +} // namespace firestore +} // namespace firebase |