Skip to content

Commit

Permalink
Foundations for async/await support
Browse files Browse the repository at this point in the history
Signed-off-by: David Graeff <[email protected]>
  • Loading branch information
David Graeff committed Jan 22, 2020
1 parent ebce58f commit fe2e094
Show file tree
Hide file tree
Showing 14 changed files with 226 additions and 111 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.6] - 2020-01-22

### Changed

- Update dependencies
- Support for reqwest 0.10 with async/await.
- Delete operation also available as async variant (unstable API for now)

## [0.5] - 2019-09-12

### Changed
Expand Down
22 changes: 12 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@ maintenance = { status = "passively-maintained" }
repository = "https://github.com/davidgraeff/firestore-db-and-auth-rs"

[dependencies]
reqwest = { version ="^0.9", default-features = false }
serde_derive = "^1.0"
serde = "^1.0"
serde_json = "^1.0"
chrono = { version = "^0.4", features = ["serde"] }
biscuit = "^0.3"
ring = "^0.16"
base64 = "0.10.1"
reqwest = { version = "0.10", default-features = false, features = ["json", "blocking"] }
serde = {version ="1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
biscuit = "0.4"
ring = "0.16"
base64 = "0.11"

[dependencies.rocket]
version = "^0.4"
version = "0.4.2"
default-features = false
optional = true

Expand All @@ -31,8 +30,11 @@ optional = true
features = [ "external_doc", "rocket_support" ]

[features]
default = ["default-tls"]
default = ["rustls-tls", "unstable"]
rocket_support = ["rocket"]
rustls-tls = ["reqwest/rustls-tls"]
default-tls = ["reqwest/default-tls"]
native-tls = ["reqwest/native-tls"]
native-tls-vendored = ["reqwest/native-tls-vendored"]
unstable = []
external_doc = []
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
[![](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT)

This crate allows easy access to your Google Firestore DB via service account or OAuth impersonated Google Firebase Auth credentials.
Minimum Rust version: 1.38

Features:
* Subset of the Firestore v1 API
Expand Down
77 changes: 77 additions & 0 deletions src/documents/delete.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use super::*;
use crate::errors::extract_google_api_error_async;

///
/// Deletes the document at the given path.
///
/// You cannot use this directly with paths from [`list`] and [`query`] document metadata objects.
/// Those contain an absolute document path. Use [`abs_to_rel`] to convert to a relative path.
///
/// ## Arguments
/// * 'auth' The authentication token
/// * 'path' The relative collection path and document id, for example "my_collection/document_id"
/// * 'fail_if_not_existing' If true this method will return an error if the document does not exist.
pub fn delete(auth: &impl FirebaseAuthBearer, path: &str, fail_if_not_existing: bool) -> Result<()> {
let url = firebase_url(auth.project_id(), path);

let query_request = dto::Write {
current_document: Some(dto::Precondition {
exists: match fail_if_not_existing {
true => Some(true),
false => None,
},
..Default::default()
}),
..Default::default()
};

let resp = auth
.client()
.delete(&url)
.bearer_auth(auth.access_token().to_owned())
.json(&query_request)
.send()?;

extract_google_api_error(resp, || path.to_owned())?;

Ok({})
}

//#[unstable(feature = "unstable", issue = "1234", reason = "Not yet decided if _async suffix or own module namespace")]
///
/// Deletes the document at the given path.
///
/// You cannot use this directly with paths from [`list`] and [`query`] document metadata objects.
/// Those contain an absolute document path. Use [`abs_to_rel`] to convert to a relative path.
///
/// ## Arguments
/// * 'auth' The authentication token
/// * 'path' The relative collection path and document id, for example "my_collection/document_id"
/// * 'fail_if_not_existing' If true this method will return an error if the document does not exist.
#[cfg(feature = "unstable")]
pub async fn delete_async(auth: &impl FirebaseAuthBearer, path: &str, fail_if_not_existing: bool) -> Result<()> {
let url = firebase_url(auth.project_id(), path);

let query_request = dto::Write {
current_document: Some(dto::Precondition {
exists: match fail_if_not_existing {
true => Some(true),
false => None,
},
..Default::default()
}),
..Default::default()
};

let resp = auth
.client_async()
.delete(&url)
.bearer_auth(auth.access_token().to_owned())
.json(&query_request)
.send()
.await?;

extract_google_api_error_async(resp, || path.to_owned()).await?;

Ok({})
}
4 changes: 2 additions & 2 deletions src/documents/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ fn get_new_data<'a>(
url: &str,
auth: &'a impl FirebaseAuthBearer,
) -> Result<dto::ListDocumentsResponse> {
let mut resp = auth
let resp = auth
.client()
.get(url)
.bearer_auth(auth.access_token().to_owned())
.send()?;

extract_google_api_error(&mut resp, || collection_id.to_owned())?;
let resp = extract_google_api_error(resp, || collection_id.to_owned())?;

let json: dto::ListDocumentsResponse = resp.json()?;
Ok(json)
Expand Down
52 changes: 6 additions & 46 deletions src/documents/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,17 @@ use super::FirebaseAuthBearer;
use serde::{Deserialize, Serialize};
use std::path::Path;

mod delete;
mod list;

pub use list::*;

mod write;

pub use write::*;

mod query;

pub use query::*;

mod read;
mod write;

pub use delete::*;
pub use list::*;
pub use query::*;
pub use read::*;
pub use write::*;

/// An [`Iterator`] implementation that provides a join method
///
Expand Down Expand Up @@ -99,39 +95,3 @@ fn abs_to_rel_test() {
"my_collection/document_id"
);
}

///
/// Deletes the document at the given path.
///
/// You cannot use this directly with paths from [`list`] and [`query`] document metadata objects.
/// Those contain an absolute document path. Use [`abs_to_rel`] to convert to a relative path.
///
/// ## Arguments
/// * 'auth' The authentication token
/// * 'path' The relative collection path and document id, for example "my_collection/document_id"
/// * 'fail_if_not_existing' If true this method will return an error if the document does not exist.
pub fn delete(auth: &impl FirebaseAuthBearer, path: &str, fail_if_not_existing: bool) -> Result<()> {
let url = firebase_url(auth.project_id(), path);

let query_request = dto::Write {
current_document: Some(dto::Precondition {
exists: match fail_if_not_existing {
true => Some(true),
false => None,
},
..Default::default()
}),
..Default::default()
};

let mut resp = auth
.client()
.delete(&url)
.bearer_auth(auth.access_token().to_owned())
.json(&query_request)
.send()?;

extract_google_api_error(&mut resp, || path.to_owned())?;

Ok({})
}
4 changes: 2 additions & 2 deletions src/documents/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ pub fn query(
..Default::default()
};

let mut resp = auth
let resp = auth
.client()
.post(&url)
.bearer_auth(auth.access_token().to_owned())
.json(&query_request)
.send()?;

extract_google_api_error(&mut resp, || collection_id.to_owned())?;
let resp = extract_google_api_error(resp, || collection_id.to_owned())?;

let json: Option<Vec<dto::RunQueryResponse>> = resp.json()?;

Expand Down
4 changes: 2 additions & 2 deletions src/documents/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ where
{
let url = firebase_url_base(document_name.as_ref());

let mut resp = auth
let resp = auth
.client()
.get(&url)
.bearer_auth(auth.access_token().to_owned())
.send()?;

extract_google_api_error(&mut resp, || document_name.as_ref().to_owned())?;
let resp = extract_google_api_error(resp, || document_name.as_ref().to_owned())?;

let json: dto::Document = resp.json()?;
Ok(document_to_pod(&json)?)
Expand Down
4 changes: 2 additions & 2 deletions src/documents/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ where
auth.client().post(&url)
};

let mut resp = builder
let resp = builder
.bearer_auth(auth.access_token().to_owned())
.json(&firebase_document)
.send()?;

extract_google_api_error(&mut resp, || {
let resp = extract_google_api_error(resp, || {
document_id
.as_ref()
.and_then(|f| Some(f.as_ref().to_owned()))
Expand Down
68 changes: 46 additions & 22 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::error;
use std::fmt;

use reqwest;
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};

/// A result type that uses [`FirebaseError`] as an error type
Expand Down Expand Up @@ -152,32 +153,55 @@ struct GoogleRESTApiErrorWrapper {
/// Arguments:
/// - response: The http requests response. Must be mutable, because the contained value will be extracted in an error case
/// - context: A function that will be called in an error case that returns a context string
pub(crate) fn extract_google_api_error(response: &mut reqwest::Response, context: impl Fn() -> String) -> Result<()> {
pub(crate) fn extract_google_api_error(
response: reqwest::blocking::Response,
context: impl Fn() -> String,
) -> Result<reqwest::blocking::Response> {
if response.status() == 200 {
// The boring case
return Ok(());
return Ok(response);
}

let google_api_error_wrapper: std::result::Result<GoogleRESTApiErrorWrapper, _> =
serde_json::from_str(&response.text()?);

match google_api_error_wrapper {
Ok(google_api_error_wrapper) => {
if let Some(google_api_error) = google_api_error_wrapper.error {
return Err(FirebaseError::APIError(
google_api_error.code,
google_api_error.message.to_owned(),
context(),
));
}
Err(extract_google_api_error_intern(
response.status().clone(),
response.text()?,
context,
))
}

/// If the given reqwest response is status code 200, nothing happens
/// Otherwise the response will be analysed if it contains a Google API Error response.
/// See https://firebase.google.com/docs/reference/rest/auth#section-error-response
///
/// Arguments:
/// - response: The http requests response. Must be mutable, because the contained value will be extracted in an error case
/// - context: A function that will be called in an error case that returns a context string
pub(crate) async fn extract_google_api_error_async(
response: reqwest::Response,
context: impl Fn() -> String,
) -> Result<reqwest::Response> {
if response.status() == 200 {
return Ok(response);
}

Err(extract_google_api_error_intern(
response.status().clone(),
response.text().await?,
context,
))
}

fn extract_google_api_error_intern(
status: StatusCode,
http_body: String,
context: impl Fn() -> String,
) -> FirebaseError {
let google_api_error_wrapper: std::result::Result<GoogleRESTApiErrorWrapper, serde_json::Error> =
serde_json::from_str(&http_body);
if let Ok(google_api_error_wrapper) = google_api_error_wrapper {
if let Some(google_api_error) = google_api_error_wrapper.error {
return FirebaseError::APIError(google_api_error.code, google_api_error.message.to_owned(), context());
}
Err(_) => {}
};

Err(FirebaseError::UnexpectedResponse(
"",
response.status(),
response.text()?,
context(),
))
FirebaseError::UnexpectedResponse("", status, http_body, context())
}
Loading

0 comments on commit fe2e094

Please sign in to comment.