Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Add FirestoreConfig struct with long_contention_timeout field and update FirestoreProject initialization to include this field. #51

Merged
merged 4 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,25 @@ tonic = "0.11"
tower = "0.4.13"
tracing = "0.1.40"

[workspace.lints.rust]
unsafe_code = "forbid"
explicit_outlives_requirements = "forbid"
let_underscore_drop = "warn"
missing_copy_implementations = "warn"
missing_debug_implementations = "warn"
non_ascii_idents = "forbid"
single_use_lifetimes = "warn"
unit_bindings = "warn"
unreachable_pub = "warn"
unused_crate_dependencies = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
unused_qualifications = "warn"

[workspace.lints.clippy]
# Until this is resolved: https://github.com/rust-lang/rust-clippy/issues/12281
blocks_in_conditions = "allow"

[dependencies]
axum = { workspace = true }
clap = { version = "4.5.1", features = ["derive", "env"] }
Expand All @@ -53,6 +72,7 @@ console = { version = "0.15.8", optional = true }
console-subscriber = { version = "0.2.0", optional = true }
emulator-grpc = { path = "crates/emulator-grpc" }
emulator-ui = { path = "crates/emulator-ui" }
firestore-database = { workspace = true }
hybrid-axum-tonic = { path = "crates/hybrid-axum-tonic" }
tikv-jemallocator = "0.5.4"
time = { workspace = true, optional = true }
Expand Down
5 changes: 2 additions & 3 deletions crates/emulator-grpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,5 @@ tokio-stream = { workspace = true }
tonic = { workspace = true, features = ["gzip"] }
tracing = { workspace = true }

[lints.clippy]
# Until this is resolved: https://github.com/rust-lang/rust-clippy/issues/12281
blocks_in_conditions = "allow"
[lints]
workspace = true
22 changes: 9 additions & 13 deletions crates/emulator-grpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ use firestore_database::{
};
use futures::{future::try_join_all, stream::BoxStream, TryStreamExt};
use googleapis::google::{
firestore::v1::{firestore_server::FirestoreServer, structured_query::CollectionSelector, *},
firestore::v1::{
firestore_server::{Firestore, FirestoreServer},
structured_query::CollectionSelector,
*,
},
protobuf::{Empty, Timestamp},
};
use itertools::Itertools;
Expand All @@ -26,27 +30,19 @@ mod utils;

const MAX_MESSAGE_SIZE: usize = 50 * 1024 * 1024;

pub fn service() -> FirestoreServer<FirestoreEmulator> {
FirestoreServer::new(FirestoreEmulator::default())
pub fn service(project: &'static FirestoreProject) -> FirestoreServer<impl Firestore> {
FirestoreServer::new(FirestoreEmulator { project })
.accept_compressed(CompressionEncoding::Gzip)
.send_compressed(CompressionEncoding::Gzip)
.max_decoding_message_size(MAX_MESSAGE_SIZE)
}

pub struct FirestoreEmulator {
struct FirestoreEmulator {
project: &'static FirestoreProject,
}

impl Default for FirestoreEmulator {
fn default() -> Self {
Self {
project: FirestoreProject::get(),
}
}
}

#[async_trait]
impl firestore_server::Firestore for FirestoreEmulator {
impl Firestore for FirestoreEmulator {
/// Gets a single document.
#[instrument(level = Level::TRACE, skip_all, err)]
async fn get_document(
Expand Down
4 changes: 3 additions & 1 deletion crates/emulator-grpc/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ macro_rules! mandatory {
/// test-suite from 7s to 3ms.
///
/// The returned Result never fails.
pub async fn error_in_stream<T, F, S>(stream: F) -> Result<Response<BoxStream<'static, Result<T>>>>
pub(crate) async fn error_in_stream<T, F, S>(
stream: F,
) -> Result<Response<BoxStream<'static, Result<T>>>>
where
F: Future<Output = Result<S>>,
S: Stream<Item = Result<T>> + Send + 'static,
Expand Down
3 changes: 3 additions & 0 deletions crates/emulator-ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ axum = { workspace = true, features = ["macros"] }
firestore-database = { workspace = true }
serde_json = "1.0.114"
tower-http = { version = "0.4.4", features = ["full"] }

[lints]
workspace = true
4 changes: 2 additions & 2 deletions crates/emulator-ui/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ const HTML: &str = concat!(
"\"</script>",
);

pub fn router() -> Router {
pub fn router(project: &'static FirestoreProject) -> Router {
let router = Router::new()
.nest("/emulator/v1", emulator::router())
.with_state(FirestoreProject::get());
.with_state(project);

#[cfg(feature = "ui")]
let router = router
Expand Down
4 changes: 2 additions & 2 deletions crates/emulator-ui/src/routes/emulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::error::{RestError, Result};
#[allow(clippy::declare_interior_mutable_const)]
const NO_CACHE: HeaderValue = HeaderValue::from_static("no-cache");

pub fn router() -> Router<&'static FirestoreProject> {
pub(crate) fn router() -> Router<&'static FirestoreProject> {
Router::new()
.route("/", get(list_databases))
.route("/*ref", get(get_by_ref).delete(delete_by_ref))
Expand All @@ -29,7 +29,7 @@ async fn list_databases(State(project): State<&FirestoreProject>) -> impl IntoRe
}

async fn get_by_ref(
State(project): State<&FirestoreProject>,
State(project): State<&'static FirestoreProject>,
Path(r): Path<Ref>,
) -> Result<Response> {
let database = project.database(r.root()).await;
Expand Down
3 changes: 3 additions & 0 deletions crates/firestore-database/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ tracing = { workspace = true }

[dev-dependencies]
rstest = "0.18.2"

[lints]
workspace = true
9 changes: 9 additions & 0 deletions crates/firestore-database/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#[derive(Clone, Copy, Debug)]
pub struct FirestoreConfig {
/// Enable more accurate lock timeouts.
///
/// In Cloud Firestore, transactions can take up to 15 seconds before aborting because of
/// contention. By default, in the emulator, this is reduced to 1 second for faster unit-tests.
/// Enable this feature to simulate the Cloud Firestore more accurately.
pub long_contention_timeout: bool,
}
12 changes: 8 additions & 4 deletions crates/firestore-database/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use self::{
};
use crate::{
database::field_path::FieldReference, error::Result, unimplemented, unimplemented_collection,
unimplemented_option, utils::RwLockHashMapExt, GenericDatabaseError,
unimplemented_option, utils::RwLockHashMapExt, FirestoreProject, GenericDatabaseError,
};

mod collection;
Expand All @@ -48,20 +48,24 @@ pub mod projection;
pub mod query;
pub mod read_consistency;
pub mod reference;
pub(crate) mod timeouts;
mod transaction;

const MAX_EVENT_BACKLOG: usize = 1024;

#[derive(Debug)]
pub struct FirestoreDatabase {
project: &'static FirestoreProject,
pub name: RootRef,
collections: RwLock<HashMap<DefaultAtom, Arc<Collection>>>,
transactions: RunningTransactions,
events: broadcast::Sender<Arc<DatabaseEvent>>,
}

impl FirestoreDatabase {
pub fn new(name: RootRef) -> Arc<Self> {
pub fn new(project: &'static FirestoreProject, name: RootRef) -> Arc<Self> {
Arc::new_cyclic(|database| FirestoreDatabase {
project,
name,
collections: Default::default(),
transactions: RunningTransactions::new(Weak::clone(database)),
Expand Down Expand Up @@ -106,7 +110,7 @@ impl FirestoreDatabase {
&*self
.collections
.get_or_insert(&collection_name.collection_id, || {
Arc::new(Collection::new(collection_name.clone()))
Arc::new(Collection::new(self.project, collection_name.clone()))
})
.await,
)
Expand Down Expand Up @@ -471,7 +475,7 @@ impl FirestoreDatabase {
pub fn send_event(&self, event: DatabaseEvent) {
// A send operation can only fail if there are no active receivers,
// which is ok by me.
let _ = self.events.send(event.into());
let _unused = self.events.send(event.into());
}

pub fn subscribe(&self) -> Receiver<Arc<DatabaseEvent>> {
Expand Down
9 changes: 6 additions & 3 deletions crates/firestore-database/src/database/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@ use super::{
document::DocumentMeta,
reference::{CollectionRef, DocumentRef},
};
use crate::{error::Result, utils::RwLockHashMapExt};
use crate::{error::Result, utils::RwLockHashMapExt, FirestoreProject};

#[derive(Debug)]
pub struct Collection {
project: &'static FirestoreProject,
pub name: CollectionRef,
documents: RwLock<HashMap<DefaultAtom, Arc<DocumentMeta>>>,
}

impl Collection {
#[instrument(level = Level::TRACE, skip_all)]
pub fn new(name: CollectionRef) -> Self {
pub fn new(project: &'static FirestoreProject, name: CollectionRef) -> Self {
Self {
project,
name,
documents: Default::default(),
}
Expand All @@ -29,7 +32,7 @@ impl Collection {
Arc::clone(
self.documents
.get_or_insert(&name.document_id, || {
Arc::new(DocumentMeta::new(name.clone()))
Arc::new(DocumentMeta::new(self.project, name.clone()))
})
.await
.deref(),
Expand Down
Loading