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

fix: doc tests #232

Merged
merged 4 commits into from
Jan 5, 2025
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
38 changes: 38 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ test *args:

INSTA_FORCE_PASS=1 cargo llvm-cov clean --workspace
INSTA_FORCE_PASS=1 cargo llvm-cov nextest --branch --include-build-script --no-report {{args}}
cargo test --doc {{args}}

# Do not generate the coverage report on CI
cargo insta review
Expand Down
1 change: 1 addition & 0 deletions crates/batching/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ scuffle-workspace-hack.workspace = true
[dev-dependencies]
criterion = { version = "0.5.1", features = ["async_tokio"] }
futures = "0.3"
tokio-test = "0.4.4"
26 changes: 13 additions & 13 deletions crates/batching/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,18 @@ impl DataLoaderFetcher for MyUserLoader {

async fn load(&self, keys: HashSet<Self::Key>) -> Option<HashMap<Self::Key, Self::Value>> {
let users = self.0.fetch("SELECT * FROM users WHERE id IN ($1)").bind(keys).await.map_err(|e| {
error!("Failed to fetch users: {}", e);
})?;
eprintln!("Failed to fetch users: {}", e);
}).ok()?;

Some(users.into_iter().map(|user| (user.id, user)).collect())
}
}

let loader = DataLoader::new(MyUserLoader(database));
let loader = DataLoaderBuilder::new().build(MyUserLoader(database));

// Will only make a single request to the database and load both users
// You can also use `loader.load_many` if you have more then one item to load.
let (user1, user2) = join!(loader.load(1), loader.load(2));
let (user1, user2): (Result<_, _>, Result<_, _>) = tokio::join!(loader.load(1), loader.load(2));
```

Another use case might be to batch multiple writes to a database.
Expand All @@ -56,11 +56,11 @@ impl BatchExecutor for MyUserUpdater {
type Response = bool;

async fn execute(&self, requests: Vec<(Self::Request, BatchResponse<Self::Response>)>) {
let (users, responses) = requests.into_iter().unzip();
let (users, responses): (Vec<Self::Request>, Vec<BatchResponse<Self::Response>>) = requests.into_iter().unzip();

// You would need to build the query somehow, this is just an example
if let Err(e) = self.0.update("INSERT INTO users (id, name) VALUES ($1, $2), ($3, $4)").bind(users).await {
error!("Failed to insert users: {}", e);
eprintln!("Failed to insert users: {}", e);

for response in responses {
// Reply back saying we failed
Expand All @@ -77,17 +77,17 @@ impl BatchExecutor for MyUserUpdater {
}
}

let batcher = Batcher::new(MyUserUpdater(database));

let batcher = BatcherBuilder::new().build(MyUserUpdater(database));
// Will only make a single request to the database and insert both users
// You can also use `batcher.execute_many` if you have more then one item to insert.
let (success1, success2) = join!(batcher.execute(user1), batcher.execute(user2));
if !success1 {
error!("Failed to insert user 1");
let (success1, success2) = tokio::join!(batcher.execute(user1), batcher.execute(user2));

if success1.is_some_and(|s| !s) {
eprintln!("Failed to insert user 1");
}

if !success2 {
error!("Failed to insert user 2");
if success2.is_some_and(|s| !s) {
eprintln!("Failed to insert user 2");
}
```

Expand Down
157 changes: 156 additions & 1 deletion crates/batching/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,159 @@
#![doc = include_str!("../README.md")]
//! # scuffle-batching
//!
//! > WARNING
//! > This crate is under active development and may not be stable.
//!
//! [![crates.io](https://img.shields.io/crates/v/scuffle-batching.svg)](https://crates.io/crates/scuffle-batching) [![docs.rs](https://img.shields.io/docsrs/scuffle-batching)](https://docs.rs/scuffle-batching)
//!
//! ---
//!
//! A crate designed to batch multiple requests into a single request.
//!
//! ## Why do we need this?
//!
//! Often when we are building applications we need to load multiple items from
//! a database or some other external resource. It is often expensive to load
//! each item individually, and this is typically why most drivers have some
//! form of multi-item loading or executing. This crate provides an improved
//! version of this functionality by combining multiple calls from different
//! scopes into a single batched request.
//!
//! ## Tradeoffs
//!
//! Because we are buffering requests for a short period of time we do see
//! higher latencies when there are not many requests. This is because the
//! overhead from just processing the requests is lower then the time we spend
//! buffering.
//!
//! However, this is often negated when we have a large number of requests as we
//! see on average lower latencies due to more efficient use of resources.
//! Latency is also more consistent as we are doing fewer requests to the
//! external resource.
//!
//! ## Usage
//!
//! Here is an example of how to use the `DataLoader` interface to batch
//! multiple reads from a database.
//!
//! ```rust
//! # use std::collections::{HashSet, HashMap};
//! # use scuffle_batching::{DataLoaderFetcher, DataLoader, dataloader::DataLoaderBuilder};
//! # tokio_test::block_on(async {
//! # #[derive(Clone, Hash, Eq, PartialEq)]
//! # struct User {
//! # pub id: i64,
//! # }
//! # struct SomeDatabase;
//! # impl SomeDatabase {
//! # fn fetch(&self, query: &str) -> Fetch {
//! # Fetch
//! # }
//! # }
//! # struct Fetch;
//! # impl Fetch {
//! # async fn bind(&self, user_ids: HashSet<i64>) -> Result<Vec<User>, &'static str> {
//! # Ok(vec![])
//! # }
//! # }
//! # let database = SomeDatabase;
//! struct MyUserLoader(SomeDatabase);
//!
//! impl DataLoaderFetcher for MyUserLoader {
//! type Key = i64;
//! type Value = User;
//!
//! async fn load(&self, keys: HashSet<Self::Key>) -> Option<HashMap<Self::Key, Self::Value>> {
//! let users = self.0.fetch("SELECT * FROM users WHERE id IN ($1)").bind(keys).await.map_err(|e| {
//! eprintln!("Failed to fetch users: {}", e);
//! }).ok()?;
//!
//! Some(users.into_iter().map(|user| (user.id, user)).collect())
//! }
//! }
//!
//! let loader = DataLoaderBuilder::new().build(MyUserLoader(database));
//!
//! // Will only make a single request to the database and load both users
//! // You can also use `loader.load_many` if you have more then one item to load.
//! let (user1, user2): (Result<_, _>, Result<_, _>) = tokio::join!(loader.load(1), loader.load(2));
//! # });
//! ```
//!
//! Another use case might be to batch multiple writes to a database.
//!
//! ```rust
//! # use std::collections::HashSet;
//! # use scuffle_batching::{DataLoaderFetcher, BatchExecutor, Batcher, batch::{BatchResponse, BatcherBuilder}, DataLoader};
//! # tokio_test::block_on(async move {
//! # #[derive(Clone, Hash, Eq, PartialEq)]
//! # struct User {
//! # pub id: i64,
//! # }
//! # struct SomeDatabase;
//! # impl SomeDatabase {
//! # fn update(&self, query: &str) -> Update {
//! # Update
//! # }
//! # }
//! # struct Update;
//! # impl Update {
//! # async fn bind(&self, users: Vec<User>) -> Result<Vec<User>, &'static str> {
//! # Ok(vec![])
//! # }
//! # }
//! # let database = SomeDatabase;
//! struct MyUserUpdater(SomeDatabase);
//!
//! impl BatchExecutor for MyUserUpdater {
//! type Request = User;
//! type Response = bool;
//!
//! async fn execute(&self, requests: Vec<(Self::Request, BatchResponse<Self::Response>)>) {
//! let (users, responses): (Vec<Self::Request>, Vec<BatchResponse<Self::Response>>) = requests.into_iter().unzip();
//!
//! // You would need to build the query somehow, this is just an example
//! if let Err(e) = self.0.update("INSERT INTO users (id, name) VALUES ($1, $2), ($3, $4)").bind(users).await {
//! eprintln!("Failed to insert users: {}", e);
//!
//! for response in responses {
//! // Reply back saying we failed
//! response.send(false);
//! }
//!
//! return;
//! }
//!
//! // Reply back to the client that we successfully inserted the users
//! for response in responses {
//! response.send(true);
//! }
//! }
//! }
//!
//! let batcher = BatcherBuilder::new().build(MyUserUpdater(database));
//! # let user1 = User { id: 1 };
//! # let user2 = User { id: 2 };
//! // Will only make a single request to the database and insert both users
//! // You can also use `batcher.execute_many` if you have more then one item to insert.
//! let (success1, success2) = tokio::join!(batcher.execute(user1), batcher.execute(user2));
//!
//! if success1.is_some_and(|s| !s) {
//! eprintln!("Failed to insert user 1");
//! }
//!
//! if success2.is_some_and(|s| !s) {
//! eprintln!("Failed to insert user 2");
//! }
//! # });
//! ```
//!
//! ## License
//!
//! This project is licensed under the [MIT](./LICENSE.MIT) or
//! [Apache-2.0](./LICENSE.Apache-2.0) license. You can choose between one of
//! them if you use this work.
//!
//! `SPDX-License-Identifier: MIT OR Apache-2.0`
#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]

pub mod batch;
Expand Down
3 changes: 3 additions & 0 deletions crates/context/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ pin-project-lite = "0.2"
tokio-util = "0.7"
tokio = "1"
scuffle-workspace-hack.workspace = true

[dev-dependencies]
tokio-test = "0.4.4"
3 changes: 2 additions & 1 deletion crates/context/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ Here is an example of how to use the `Context` to cancel a spawned task.
```rust
let (ctx, handler) = Context::new();

tokio::spawn(async move {
tokio::spawn(async {
// Do some work
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
}.with_context(ctx));

// Will stop the spawned task and cancel all associated futures.
Expand Down
46 changes: 45 additions & 1 deletion crates/context/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,48 @@
#![doc = include_str!("../README.md")]
//! # scuffle-context
//!
//! > WARNING
//! > This crate is under active development and may not be stable.
//!
//! [![crates.io](https://img.shields.io/crates/v/scuffle-context.svg)](https://crates.io/crates/scuffle-context) [![docs.rs](https://img.shields.io/docsrs/scuffle-context)](https://docs.rs/scuffle-context)
//!
//! ---
//!
//! A crate designed to provide the ability to cancel futures using a context
//! go-like approach, allowing for graceful shutdowns and cancellations.
//!
//! ## Why do we need this?
//!
//! Its often useful to wait for all the futures to shutdown or to cancel them
//! when we no longer care about the results. This crate provides an interface
//! to cancel all futures associated with a context or wait for them to finish
//! before shutting down. Allowing for graceful shutdowns and cancellations.
//!
//! ## Usage
//!
//! Here is an example of how to use the `Context` to cancel a spawned task.
//!
//! ```rust
//! # use scuffle_context::{Context, ContextFutExt};
//! # tokio_test::block_on(async {
//! let (ctx, handler) = Context::new();
//!
//! tokio::spawn(async {
//! // Do some work
//! tokio::time::sleep(std::time::Duration::from_secs(10)).await;
//! }.with_context(ctx));
//!
//! // Will stop the spawned task and cancel all associated futures.
//! handler.cancel();
//! # });
//! ```
//!
//! ## License
//!
//! This project is licensed under the [MIT](./LICENSE.MIT) or
//! [Apache-2.0](./LICENSE.Apache-2.0) license. You can choose between one of
//! them if you use this work.
//!
//! `SPDX-License-Identifier: MIT OR Apache-2.0`

use std::future::{Future, IntoFuture};
use std::pin::Pin;
Expand Down
3 changes: 3 additions & 0 deletions crates/metrics/derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ quote = "1"
darling = "0.20"
proc-macro2 = "1"
scuffle-workspace-hack.workspace = true

[dev-dependencies]
scuffle-metrics = { workspace = true }
Loading