1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! # Firestore for Rust
//!
//! Library provides a simple API for Google Firestore:
//! - Create or update documents using Rust structures and Serde;
//! - Support for querying / streaming / listing / listening changes / aggregated queries of documents from Firestore;
//! - Fluent high-level and strongly typed API;
//! - Full async based on Tokio runtime;
//! - Macro that helps you use JSON paths as references to your structure fields;
//! - Implements own Serde serializer to Firestore gRPC values;
//! - Supports for Firestore timestamp with `#[serde(with)]`;
//! - Transactions support;
//! - Streaming batch writes with automatic throttling to avoid time limits from Firestore;
//! - Aggregated Queries;
//! - Google client based on [gcloud-sdk library](https://github.com/abdolence/gcloud-sdk-rs)
//!   that automatically detects GKE environment or application default accounts for local development;
//!
//! ## Example using the Fluent API:
//!
//! ```rust,no_run
//!
//!use firestore::*;
//!use serde::{Deserialize, Serialize};
//!use futures::stream::BoxStream;
//!use futures::StreamExt;
//!
//!pub fn config_env_var(name: &str) -> Result<String, String> {
//!    std::env::var(name).map_err(|e| format!("{}: {}", name, e))
//!}
//!
//!// Example structure to play with
//!#[derive(Debug, Clone, Deserialize, Serialize)]
//!struct MyTestStructure {
//!    some_id: String,
//!    some_string: String,
//!    one_more_string: String,
//!    some_num: u64,
//!}
//!
//!#[tokio::main]
//!async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {//!
//!   // Create an instance
//!   let db = FirestoreDb::new(&config_env_var("PROJECT_ID")?).await?;
//!
//!   const TEST_COLLECTION_NAME: &'static str = "test";
//!
//!   let my_struct = MyTestStructure {
//!        some_id: "test-1".to_string(),
//!        some_string: "Test".to_string(),
//!        one_more_string: "Test2".to_string(),
//!        some_num: 42,
//!   };
//!
//!   // Create documents
//!   let object_returned: MyTestStructure = db.fluent()
//!       .insert()
//!       .into(TEST_COLLECTION_NAME)
//!       .document_id(&my_struct.some_id)
//!       .object(&my_struct)
//!       .execute()
//!       .await?;
//!
//!   // Update documents
//!   let object_updated: MyTestStructure = db.fluent()
//!       .update()
//!       .fields(paths!(MyTestStructure::{some_num, one_more_string})) // Update only specified fields
//!       .in_col(TEST_COLLECTION_NAME)
//!       .document_id(&my_struct.some_id)
//!       .object(&MyTestStructure {
//!           some_num: my_struct.some_num + 1,
//!          one_more_string: "updated-value".to_string(),
//!           ..my_struct.clone()
//!       })
//!       .execute()
//!      .await?;
//!  
//!   // Get a document as an object by id
//!   let find_it_again: Option<MyTestStructure> = db.fluent()
//!         .select()
//!         .by_id_in(TEST_COLLECTION_NAME)
//!         .obj()
//!         .one(&my_struct.some_id)
//!         .await?;
//!
//!   // Query and read stream of objects
//!   let object_stream: BoxStream<MyTestStructure> = db.fluent()
//!     .select()
//!     .fields(paths!(MyTestStructure::{some_id, some_num, some_string, one_more_string})) // Optionally select the fields needed
//!     .from(TEST_COLLECTION_NAME)
//!     .filter(|q| { // Fluent filter API example
//!         q.for_all([
//!             q.field(path!(MyTestStructure::some_num)).is_not_null(),
//!             q.field(path!(MyTestStructure::some_string)).eq("Test"),
//!             // Sometimes you have optional filters
//!             Some("Test2")
//!                 .and_then(|value| q.field(path!(MyTestStructure::one_more_string)).eq(value)),
//!         ])
//!     })
//!     .order_by([(
//!         path!(MyTestStructure::some_num),
//!         FirestoreQueryDirection::Descending,
//!     )])
//!     .obj() // Reading documents as structures using Serde gRPC deserializer
//!     .stream_query()
//!     .await?;
//!
//!     let as_vec: Vec<MyTestStructure> = object_stream.collect().await;
//!     println!("{:?}", as_vec);
//!
//!     // Delete documents
//!     db.fluent()
//!         .delete()
//!         .from(TEST_COLLECTION_NAME)
//!         .document_id(&my_struct.some_id)
//!         .execute()
//!         .await?;
//!
//!     Ok(())
//! }
//! ```
//!
//! All examples and more docs available at: [github](https://github.com/abdolence/firestore-rs/tree/master/examples)
//!

#![allow(clippy::new_without_default)]
#![forbid(unsafe_code)]

pub mod errors;
mod firestore_value;
pub use firestore_value::*;

mod db;
pub use db::*;

mod firestore_serde;
pub use firestore_serde::*;

mod struct_path_macro;
use crate::errors::FirestoreError;
pub use struct_path_macro::*;

pub mod timestamp_utils;

pub type FirestoreResult<T> = std::result::Result<T, FirestoreError>;

pub type FirestoreDocument = gcloud_sdk::google::firestore::v1::Document;

mod fluent_api;
pub use fluent_api::*;

pub extern crate struct_path;