use displaydoc::Display;
use rand::distributions::Standard;
use rand::prelude::Distribution;
use rand::Rng;
use redis::AsyncCommands as _;
use redis::Client;
use redis::RedisError;
use redis_lock::MultiResourceLock;
use std::error::Error;
use std::time::Duration;
use thiserror::Error;
#[derive(Debug, Display, Error)]
enum TransferError {
Connection(RedisError),
GetFrom(RedisError),
GetTo(RedisError),
SetFrom(RedisError),
SetTo(RedisError),
}
#[expect(
clippy::arithmetic_side_effects,
reason = "We check the arithmetic is valid with the if statement."
)]
async fn transfer(client: Client, from: &str, to: &str, amount: i64) -> Result<(), TransferError> {
let mut conn = client
.get_multiplexed_async_connection()
.await
.map_err(TransferError::Connection)?;
let from_balance: i64 = conn.get(from).await.map_err(TransferError::GetFrom)?;
if from_balance >= amount {
let to_balance: i64 = conn.get(to).await.map_err(TransferError::GetTo)?;
conn.set::<_, _, ()>(from, from_balance - amount)
.await
.map_err(TransferError::SetFrom)?;
conn.set::<_, _, ()>(to, to_balance + amount)
.await
.map_err(TransferError::SetTo)?;
}
Ok(())
}
enum TransferType {
Account1ToAccount2,
Account2ToAccount3,
Account1ToAccount3,
}
#[expect(clippy::unreachable, reason = "It's unreachable and it's a test")]
impl Distribution<TransferType> for Standard {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TransferType {
match rng.gen_range(0..3) {
0 => TransferType::Account1ToAccount2,
1 => TransferType::Account2ToAccount3,
2 => TransferType::Account1ToAccount3,
_ => unreachable!(),
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let redis_url = "redis://127.0.0.1/";
let client = Client::open(redis_url)?;
let mut lock = MultiResourceLock::new(client.clone())?;
let mut rng = rand::thread_rng();
for _ in 0..10usize {
let amount = rng.gen_range(10..=100);
let transfer_type = rng.gen();
let (from, to, resources) = match transfer_type {
TransferType::Account1ToAccount2 => (
"account1",
"account2",
vec![String::from("account1"), String::from("account2")],
),
TransferType::Account2ToAccount3 => (
"account2",
"account3",
vec![String::from("account2"), String::from("account3")],
),
TransferType::Account1ToAccount3 => (
"account1",
"account3",
vec![String::from("account1"), String::from("account3")],
),
};
let cloned_client = client.clone();
lock.map(
&resources,
Duration::from_secs(60),
Duration::from_secs(2),
Duration::from_millis(100),
async move { transfer(cloned_client, from, to, amount).await },
)
.await??;
}
Ok(())
}