Skip to content

Commit

Permalink
Merge pull request #10 from abdolence/feature/firestore-native-serial…
Browse files Browse the repository at this point in the history
…izer-support

Native Firestore Serializer
  • Loading branch information
abdolence authored Sep 7, 2022
2 parents 590b827 + c630049 commit cf7eb7f
Show file tree
Hide file tree
Showing 15 changed files with 1,647 additions and 194 deletions.
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[package]
name = "firestore"
version = "0.8.0"
version = "0.9.0"
authors = ["Abdulla Abdurakhmanov <[email protected]>"]
edition = "2021"
license = "Apache-2.0"
description = "Library provides a simple API for Google Firestore for create/update/query/stream/listen data"
description = "Library provides a simple API for Google Firestore for create/update/query/stream/listen data and Serde serializer"
homepage = "https://github.com/abdolence/firestore-rs"
repository = "https://github.com/abdolence/firestore-rs"
documentation = "https://docs.rs/firestore"
Expand All @@ -29,7 +29,7 @@ convert_case = "0.5"
rvstruct = "0.3"
rsb_derive = "0.5"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
prost-types = "0.11"
tokio = { version = "1.20", features = ["full"] }
tokio-stream = "0.1"
futures = "0.3"
Expand Down
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ Library provides a simple API for Google Firestore:
- Listening changes from Firestore;
- 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 values;
- Supports for Firestore timestamp with `#[serde(with)]`
- 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;

## Quick start


Cargo.toml:
```toml
[dependencies]
firestore = "0.8"
firestore = "0.9"
```

Example code:
Expand Down Expand Up @@ -96,13 +97,28 @@ Example code:
println!("Now in the list: {:?}", objects);
```

All examples available at examples directory.
All examples available at [examples](examples) directory.

To run example use with environment variables:
To run example use it with environment variables:
```
# PROJECT_ID=<your-google-project-id> cargo run --example simple-crud
```

## Timestamps support
By default, the types such as DateTime<Utc> serializes as a string
to Firestore (while deserialization works from Timestamps and Strings).
To change it to support Timestamp natively use `#[serde(with)]`:

```
#[derive(Debug, Clone, Deserialize, Serialize)]
struct MyTestStructure {
#[serde(with = "firestore::serialize_as_timestamp")]
created_at: DateTime<Utc>,
}
```
This will change it only for firestore serialization and it still serializes as string
to JSON (so you can reuse the same model for JSON and Firestore).

## Licence
Apache Software License (ASL)

Expand Down
60 changes: 60 additions & 0 deletions examples/composite-query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use chrono::{DateTime, Utc};
use firestore::*;
use futures_util::stream::BoxStream;
use serde::{Deserialize, Serialize};
use tokio_stream::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,
created_at: DateTime<Utc>,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Logging with debug enabled
let subscriber = tracing_subscriber::fmt()
.with_env_filter("firestore=debug")
.finish();
tracing::subscriber::set_global_default(subscriber)?;

// Create an instance
let db = FirestoreDb::new(&config_env_var("PROJECT_ID")?).await?;

const TEST_COLLECTION_NAME: &'static str = "test";

println!("Querying a test collection as a stream");
// Query as a stream our data
let mut object_stream: BoxStream<MyTestStructure> = db
.stream_query_obj(
FirestoreQueryParams::new(TEST_COLLECTION_NAME.into()).with_filter(
FirestoreQueryFilter::Composite(FirestoreQueryFilterComposite::new(vec![
FirestoreQueryFilter::Compare(Some(FirestoreQueryFilterCompare::Equal(
path!(MyTestStructure::some_num),
42.into(),
))),
FirestoreQueryFilter::Compare(Some(
FirestoreQueryFilterCompare::LessThanOrEqual(
path!(MyTestStructure::created_at),
Utc::now().into(),
),
)),
])),
),
)
.await?;

while let Some(object) = object_stream.next().await {
println!("Object in stream: {:?}", object);
}

Ok(())
}
3 changes: 3 additions & 0 deletions examples/query-as-stream.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use chrono::{DateTime, Utc};
use firestore::*;
use futures_util::stream::BoxStream;
use serde::{Deserialize, Serialize};
Expand All @@ -14,6 +15,7 @@ struct MyTestStructure {
some_string: String,
one_more_string: String,
some_num: u64,
created_at: DateTime<Utc>,
}

#[tokio::main]
Expand All @@ -36,6 +38,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
some_string: "Test".to_string(),
one_more_string: "Test2".to_string(),
some_num: 42,
created_at: Utc::now(),
};

// Remove if it already exist
Expand Down
3 changes: 3 additions & 0 deletions examples/simple-crud.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use chrono::{DateTime, Utc};
use firestore::*;
use serde::{Deserialize, Serialize};

Expand All @@ -12,6 +13,7 @@ struct MyTestStructure {
some_string: String,
one_more_string: String,
some_num: u64,
created_at: DateTime<Utc>,
}

#[tokio::main]
Expand All @@ -32,6 +34,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
some_string: "Test".to_string(),
one_more_string: "Test2".to_string(),
some_num: 41,
created_at: Utc::now(),
};

// Remove if it already exist
Expand Down
118 changes: 118 additions & 0 deletions examples/timestamp-serialize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use chrono::{DateTime, Utc};
use firestore::*;
use serde::{Deserialize, Serialize};

pub fn config_env_var(name: &str) -> Result<String, String> {
std::env::var(name).map_err(|e| format!("{}: {}", name, e))
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Test1(u8);

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Test2 {
some_id: String,
some_bool: Option<bool>,
}

// 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,
#[serde(with = "firestore::serialize_as_timestamp")]
created_at: DateTime<Utc>,
test1: Test1,
test11: Option<Test1>,
test2: Option<Test2>,
test3: Vec<Test2>,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Logging with debug enabled
let subscriber = tracing_subscriber::fmt()
.with_env_filter("firestore=debug")
.finish();
tracing::subscriber::set_global_default(subscriber)?;

// Create an instance
let db = FirestoreDb::new(&config_env_var("PROJECT_ID")?).await?;

const TEST_COLLECTION_NAME: &'static str = "test-ts";

let my_struct = MyTestStructure {
some_id: "test-1".to_string(),
some_string: "Test".to_string(),
one_more_string: "Test2".to_string(),
some_num: 41,
created_at: Utc::now(),
test1: Test1(1),
test11: Some(Test1(1)),
test2: Some(Test2 {
some_id: "test-1".to_string(),
some_bool: Some(true),
}),
test3: vec![
Test2 {
some_id: "test-2".to_string(),
some_bool: Some(false),
},
Test2 {
some_id: "test-2".to_string(),
some_bool: Some(true),
},
],
};

// Remove if it already exist
db.delete_by_id(TEST_COLLECTION_NAME, &my_struct.some_id)
.await?;

// Let's insert some data
db.create_obj(TEST_COLLECTION_NAME, &my_struct.some_id, &my_struct)
.await?;

// Update some field in it
let updated_obj = db
.update_obj(
TEST_COLLECTION_NAME,
&my_struct.some_id,
&MyTestStructure {
some_num: my_struct.some_num + 1,
some_string: "updated-value".to_string(),
..my_struct.clone()
},
Some(paths!(MyTestStructure::{
some_num,
some_string
})),
)
.await?;

println!("Updated object: {:?}", updated_obj);

// Get object by id
let find_it_again: MyTestStructure =
db.get_obj(TEST_COLLECTION_NAME, &my_struct.some_id).await?;

println!("Should be the same: {:?}", find_it_again);

// Query our data
let objects: Vec<MyTestStructure> = db
.query_obj(
FirestoreQueryParams::new(TEST_COLLECTION_NAME.into()).with_filter(
FirestoreQueryFilter::Compare(Some(FirestoreQueryFilterCompare::Equal(
path!(MyTestStructure::some_num),
find_it_again.some_num.into(),
))),
),
)
.await?;

println!("Now in the list: {:?}", objects);

Ok(())
}
3 changes: 2 additions & 1 deletion src/db.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::errors::*;
use crate::query::*;
use crate::serde::*;
use chrono::prelude::*;
use std::collections::HashMap;

use crate::serde_deserializer::firestore_document_to_serializable;
use crate::serde_serializer::firestore_document_from_serializable;
use crate::{FirestoreListDocParams, FirestoreListDocResult, FirestoreResult};
use futures::future::{BoxFuture, FutureExt};
use futures::TryFutureExt;
Expand Down
Loading

0 comments on commit cf7eb7f

Please sign in to comment.