Skip to content

Commit

Permalink
Implement most the rest of AddDocument
Browse files Browse the repository at this point in the history
  • Loading branch information
obmarg committed Jul 24, 2021
1 parent 898548d commit 32857de
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 59 deletions.
File renamed without changes.
74 changes: 35 additions & 39 deletions ingle/src/connection/mod.rs → ingle/src/database/builder.rs
Original file line number Diff line number Diff line change
@@ -1,113 +1,102 @@
use std::time::Duration;

use tonic::{
codegen::InterceptedService,
metadata::MetadataValue,
server::ClientStreamingService,
transport::{self, Channel, Endpoint},
transport::{self, Endpoint},
Request,
};

use super::{auth::Token, Database, Interceptor};
use crate::google::firestore::v1::firestore_client::FirestoreClient;

mod auth;
use crate::paths::ProjectPath;

static FIRESTORE_ENDPOINT: &str = "https://firestore.googleapis.com";
static FIRESTORE_TOKEN_AUDIENCE: &str =
"https://firestore.googleapis.com/google.firestore.v1beta1.Firestore";
static DEFAULT_DATABASE: &str = "(default)";

// TODO: Connection or client?
// Probably client tbh, but can rename later
struct Connection {
client: FirestoreClient<InterceptedService<Channel, Interceptor>>,
}

enum Credentials {
Default,
Emulator,
EmulatorOwner,
}

struct ConnectionBuilder {
pub struct DatabaseBuilder {
endpoint: Endpoint,
credentials: Credentials,
project_id: String,
database_id: String,
}

impl ConnectionBuilder {
pub fn new() -> ConnectionBuilder {
// TODO: Use env vars to guess emulator?
ConnectionBuilder {
impl DatabaseBuilder {
pub fn new(project_id: impl Into<String>) -> DatabaseBuilder {
DatabaseBuilder {
endpoint: Endpoint::from_static(FIRESTORE_ENDPOINT),
credentials: Credentials::Default,
project_id: project_id.into(),
database_id: DEFAULT_DATABASE.to_string(),
}
}

pub fn https_endpoint(self, url: &str) -> Self {
ConnectionBuilder {
DatabaseBuilder {
endpoint: Endpoint::from_shared(format!("https://{}", url))
.expect("Invalid firestore URL"),
..self
}
}

pub fn http_endpoint(self, url: &str) -> Self {
ConnectionBuilder {
DatabaseBuilder {
endpoint: Endpoint::from_shared(format!("http://{}", url))
.expect("Invalid firestore URL"),
..self
}
}

/// Set whether TCP keepalive messages are enabled on connections.
/// Set whether TCP keepalive messages are enabled on Databases.
///
/// If None is specified, keepalive is disabled, otherwise the duration
/// specified will be the time to remain idle before sending TCP keepalive
/// probes.
///
/// Default is no keepalive (None)
pub fn tcp_keepalive(self, duration: impl Into<Option<Duration>>) -> Self {
ConnectionBuilder {
DatabaseBuilder {
endpoint: self.endpoint.tcp_keepalive(duration.into()),
..self
}
}

/// Apply a timeout to each request.
pub fn timeout(self, duration: Duration) -> Self {
ConnectionBuilder {
DatabaseBuilder {
endpoint: self.endpoint.timeout(duration),
..self
}
}

pub fn default_credentials(self) -> Self {
ConnectionBuilder {
DatabaseBuilder {
credentials: Credentials::Default,
..self
}
}

pub fn emulator_credentials(self) -> Self {
ConnectionBuilder {
DatabaseBuilder {
credentials: Credentials::Emulator,
..self
}
}

pub fn emulator_owner_credentials(self) -> Self {
ConnectionBuilder {
DatabaseBuilder {
credentials: Credentials::EmulatorOwner,
..self
}
}

#[allow(clippy::redundant_closure)]
pub async fn connect(self) -> Result<Connection, ConnectionError> {
// TODO: Probably want retries at some point?
pub async fn connect(self) -> Result<Database, ConnectError> {
let channel = self.endpoint.connect().await?;
let interceptor: Interceptor = match self.credentials {
Credentials::Default => {
let token = auth::Token::from_default_credentials(FIRESTORE_TOKEN_AUDIENCE)?;
let token = Token::from_default_credentials(FIRESTORE_TOKEN_AUDIENCE)?;
Box::new(move |mut req: Request<()>| {
req.metadata_mut().insert(
"authorization",
Expand All @@ -127,22 +116,29 @@ impl ConnectionBuilder {
};

let client = FirestoreClient::with_interceptor(channel, interceptor);
Ok(Connection { client })
Ok(Database {
client,
project_path: ProjectPath::new(self.project_id, self.database_id),
})
}
}

type Interceptor = Box<dyn FnMut(tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status>>;

#[derive(thiserror::Error, Debug)]
enum ConnectionError {
pub enum ConnectError {
#[error("Error encoding JWT from credentials: {0}")]
JwtError(String),
#[error("gRPC transport error: {0}")]
TransportError(#[from] transport::Error),
}

impl From<frank_jwt::Error> for ConnectionError {
impl From<frank_jwt::Error> for ConnectError {
fn from(e: frank_jwt::Error) -> Self {
ConnectionError::JwtError(e.to_string())
ConnectError::JwtError(e.to_string())
}
}

enum Credentials {
Default,
Emulator,
EmulatorOwner,
}
40 changes: 40 additions & 0 deletions ingle/src/database/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use tonic::{codegen::InterceptedService, transport::Channel};

use crate::{
document::DocumentResponse,
executors::WriteExecutor,
google::firestore::v1::firestore_client::FirestoreClient,
operations::{self, IntoRequest},
paths::ProjectPath,
values::DocumentValues,
};

mod auth;
mod builder;

pub use builder::{ConnectError, DatabaseBuilder};

pub struct Database {
client: FirestoreClient<InterceptedService<Channel, Interceptor>>,
project_path: ProjectPath,
}

type Interceptor =
Box<dyn FnMut(tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> + Send + Sync>;

#[async_trait::async_trait]
impl WriteExecutor for Database {
async fn add_document(
&self,
input: operations::AddDocumentRequest,
) -> Result<DocumentResponse<DocumentValues>, ()> {
let mut client = self.client.clone();

Ok(client
.create_document(input.into_firestore_request(self.project_path.clone()))
.await
.expect("TODO: errors")
.into_inner()
.try_into_document_response()?)
}
}
23 changes: 22 additions & 1 deletion ingle/src/document.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::values::DocumentValues;
use crate::{google::firestore::v1 as firestore, values::DocumentValues};

pub trait Document: Sized {
fn to_values(&self) -> Result<DocumentValues, ()>;
Expand All @@ -15,3 +15,24 @@ impl Document for DocumentValues {
Ok(values)
}
}

pub struct DocumentResponse<D> {
pub document: D,
}

impl DocumentResponse<DocumentValues> {
pub(crate) fn try_from_firestore(
doc: firestore::Document,
) -> Result<DocumentResponse<DocumentValues>, ()> {
Ok(DocumentResponse {
name: doc.name,
document: DocumentValues::try_from_firestore(doc.fields)?,
})
}
}

impl firestore::Document {
pub(crate) fn try_into_document_response(self) -> Result<DocumentResponse<DocumentValues>, ()> {
DocumentResponse::try_from_firestore(self)
}
}
4 changes: 2 additions & 2 deletions ingle/src/executors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::values::DocumentValues;

use crate::{operations, requests};
use crate::{document::DocumentResponse, operations};

enum Error {}

Expand All @@ -11,7 +11,7 @@ pub trait WriteExecutor {
async fn add_document(
&self,
input: operations::AddDocumentRequest,
) -> Result<requests::DocumentResponse<DocumentValues>, ()>;
) -> Result<DocumentResponse<DocumentValues>, ()>;
}

#[cfg(test)]
Expand Down
5 changes: 2 additions & 3 deletions ingle/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
mod connection;
mod database;
mod document;
mod executors;
mod google;
mod path;
mod paths;
mod refs;
mod requests;
mod values;

pub mod operations;
Expand Down
28 changes: 27 additions & 1 deletion ingle/src/operations/add_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ use std::marker::PhantomData;

use super::IntoRequest;
use crate::{
document::Document, executors::WriteExecutor, path::CollectionPath, requests::DocumentResponse,
document::{Document, DocumentResponse},
executors::WriteExecutor,
google::firestore::v1 as firestore,
paths::CollectionPath,
paths::ProjectPath,
values::DocumentValues,
};

Expand Down Expand Up @@ -76,3 +80,25 @@ pub struct AddDocumentRequest {
document_id: String,
document: DocumentValues,
}

impl AddDocumentRequest {
pub fn into_firestore_request(
self,
project_path: ProjectPath,
) -> firestore::CreateDocumentRequest {
let (parent, collection_id) = self.collection_path.parent_and_collection_id(project_path);

firestore::CreateDocumentRequest {
parent,
collection_id,
document_id: self.document_id,
document: Some(firestore::Document {
name: String::new(),
fields: self.document.into_firestore(),
create_time: None,
update_time: None,
}),
mask: None,
}
}
}
25 changes: 25 additions & 0 deletions ingle/src/path.rs → ingle/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ impl CollectionPath {

DocumentPath { path }
}

pub fn parent_and_collection_id(self, project_path: ProjectPath) -> (String, String) {
let current_parent = self.parent.unwrap_or_default();
let mut parent = String::with_capacity(current_parent.len() + project_path.path.len());
parent.push_str(&project_path.path);
parent.push_str(&current_parent);

(parent, self.id)
}
}

#[derive(Clone, Debug)]
Expand All @@ -45,6 +54,22 @@ impl DocumentPath {
}
}

#[derive(Clone)]
pub struct ProjectPath {
path: String,
}

impl ProjectPath {
pub fn new(project_id: String, database_id: String) -> ProjectPath {
ProjectPath {
path: format!(
"projects/{}/databases/{}/documents/",
project_id, database_id
),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
6 changes: 1 addition & 5 deletions ingle/src/refs.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
use crate::{
document::Document,
operations::AddDocumentOperation,
path::{CollectionPath, DocumentPath},
};
use crate::paths::{CollectionPath, DocumentPath};

pub struct CollectionRef {
pub(crate) path: CollectionPath,
Expand Down
7 changes: 0 additions & 7 deletions ingle/src/requests.rs

This file was deleted.

Loading

0 comments on commit 32857de

Please sign in to comment.