├── .gitignore ├── src ├── entities │ ├── mod.rs │ ├── team.rs │ └── player.rs ├── repositories │ ├── mod.rs │ ├── in_memory │ │ ├── team │ │ │ ├── mod.rs │ │ │ ├── queries.rs │ │ │ └── commands.rs │ │ ├── player │ │ │ ├── mod.rs │ │ │ ├── queries.rs │ │ │ └── commands.rs │ │ └── mod.rs │ └── postgres │ │ ├── team │ │ ├── mod.rs │ │ ├── queries.rs │ │ └── commands.rs │ │ ├── player │ │ ├── mod.rs │ │ ├── queries.rs │ │ └── commands.rs │ │ └── mod.rs ├── services │ ├── commands │ │ ├── team │ │ │ ├── mod.rs │ │ │ └── create.rs │ │ ├── player │ │ │ ├── mod.rs │ │ │ ├── delete.rs │ │ │ ├── create.rs │ │ │ └── update.rs │ │ └── mod.rs │ ├── queries │ │ ├── team │ │ │ ├── mod.rs │ │ │ └── find.rs │ │ ├── player │ │ │ ├── mod.rs │ │ │ ├── find.rs │ │ │ └── all.rs │ │ └── mod.rs │ └── mod.rs └── main.rs ├── .rustup └── settings.toml ├── .codesandbox ├── tasks.json └── Dockerfile ├── Cargo.toml ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /src/entities/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod player; 2 | pub mod team; -------------------------------------------------------------------------------- /src/repositories/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod in_memory; 2 | pub mod postgres; 3 | -------------------------------------------------------------------------------- /src/repositories/in_memory/team/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod commands; 2 | pub mod queries; 3 | -------------------------------------------------------------------------------- /src/repositories/postgres/team/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod commands; 2 | pub mod queries; 3 | -------------------------------------------------------------------------------- /src/repositories/in_memory/player/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod commands; 2 | pub mod queries; 3 | -------------------------------------------------------------------------------- /src/repositories/postgres/player/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod commands; 2 | pub mod queries; 3 | -------------------------------------------------------------------------------- /.rustup/settings.toml: -------------------------------------------------------------------------------- 1 | profile = "default" 2 | version = "12" 3 | 4 | [overrides] 5 | -------------------------------------------------------------------------------- /src/services/commands/team/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create; 2 | 3 | #[derive(Debug)] 4 | pub struct TeamInput { 5 | pub name: String, 6 | } 7 | -------------------------------------------------------------------------------- /src/entities/team.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | pub struct Team { 3 | pub id: String, 4 | pub name: String, 5 | pub missing_players: u64, 6 | } 7 | -------------------------------------------------------------------------------- /src/entities/player.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Default)] 2 | pub struct Player { 3 | pub id: String, 4 | pub name: String, 5 | pub team_id: String, 6 | } 7 | -------------------------------------------------------------------------------- /src/services/queries/team/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod find; 2 | 3 | #[derive(Debug)] 4 | pub struct TeamAllInput { 5 | pub size: u64, 6 | pub limit: u64, 7 | } 8 | -------------------------------------------------------------------------------- /src/services/queries/player/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod all; 2 | pub mod find; 3 | 4 | #[derive(Debug)] 5 | pub struct PlayerAllInput { 6 | pub size: u64, 7 | pub limit: u64, 8 | } 9 | -------------------------------------------------------------------------------- /src/services/commands/player/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create; 2 | pub mod delete; 3 | pub mod update; 4 | 5 | #[derive(Debug, Default)] 6 | pub struct PlayerInput { 7 | pub name: String, 8 | pub team_id: String, 9 | } 10 | -------------------------------------------------------------------------------- /src/repositories/postgres/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | pub mod player; 4 | pub mod team; 5 | 6 | pub struct Repo { 7 | pub pool: Arc, 8 | } 9 | 10 | impl Repo { 11 | pub fn new(pool: Arc) -> Self { 12 | Self { pool } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/repositories/in_memory/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, sync::Arc}; 2 | 3 | pub mod player; 4 | pub mod team; 5 | 6 | pub struct Repo { 7 | pub pool: Arc>, 8 | } 9 | 10 | impl Repo { 11 | pub fn new() -> Self { 12 | let pool = Arc::new(BTreeMap::new()); 13 | 14 | Self { pool } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.codesandbox/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // These tasks will run in order when initializing your CodeSandbox project. 3 | "setupTasks": [], 4 | // These tasks can be run from CodeSandbox. Running one will open a log in the app. 5 | "tasks": { 6 | "cargo watch -x run": { 7 | "name": "cargo watch -x run", 8 | "command": "cargo watch -x run", 9 | "runAtStart": true 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "transaction" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | async-trait = { version = "0.1.68" } 10 | sqlx = { version = "0.6.3", default-features = false, features = [ 11 | "macros", 12 | "postgres", 13 | "runtime-tokio-rustls", 14 | "time", 15 | ] } 16 | tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] } 17 | -------------------------------------------------------------------------------- /src/services/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod commands; 2 | pub mod queries; 3 | 4 | pub struct App { 5 | pub commands: Commands, 6 | pub queries: Queries, 7 | } 8 | 9 | pub struct Commands { 10 | pub player_create: Box, 11 | pub player_delete: Box, 12 | pub player_update: Box, 13 | 14 | pub team_create: Box, 15 | } 16 | 17 | pub struct Queries { 18 | pub player_by_id: Box, 19 | pub player_all: Box, 20 | 21 | pub team_by_id: Box, 22 | } 23 | -------------------------------------------------------------------------------- /src/services/queries/team/find.rs: -------------------------------------------------------------------------------- 1 | use crate::entities::team::Team; 2 | use crate::{services::queries::RepoTrait, Deps}; 3 | use std::sync::Arc; 4 | 5 | struct ExecutorImpl { 6 | deps: Arc>, 7 | } 8 | 9 | pub fn new_executor(deps: Arc>) -> Box { 10 | Box::new(ExecutorImpl { deps }) 11 | } 12 | 13 | #[async_trait::async_trait] 14 | pub trait Executor: Send + Sync { 15 | async fn execute(&self, id: &str) -> Result, String>; 16 | } 17 | 18 | #[async_trait::async_trait] 19 | impl Executor for ExecutorImpl { 20 | async fn execute(&self, id: &str) -> Result, String> { 21 | let res = self.deps.queries_repo.team_by_id(id).await?; 22 | 23 | Ok(res) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/repositories/in_memory/team/queries.rs: -------------------------------------------------------------------------------- 1 | use crate::entities::team::Team; 2 | use crate::queries::team::TeamAllInput; 3 | use crate::{repositories::in_memory::Repo, services::queries::RepoTeam}; 4 | 5 | #[async_trait::async_trait] 6 | impl RepoTeam for Repo { 7 | async fn team_by_id(&self, id: &str) -> Result, String> { 8 | println!("id: {} - team_by_id in_memory repo", id); 9 | 10 | let obj = Team { 11 | ..Default::default() 12 | }; 13 | 14 | Ok(Some(obj)) 15 | } 16 | 17 | async fn team_all(&self, input: &TeamAllInput) -> Result, String> { 18 | println!("input: {:?} - team_all in_memory repo", input); 19 | 20 | let all = vec![Team { 21 | ..Default::default() 22 | }]; 23 | 24 | Ok(all) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/repositories/postgres/team/queries.rs: -------------------------------------------------------------------------------- 1 | use crate::entities::team::Team; 2 | use crate::queries::team::TeamAllInput; 3 | use crate::{repositories::postgres::Repo, services::queries::RepoTeam}; 4 | 5 | #[async_trait::async_trait] 6 | impl RepoTeam for Repo { 7 | async fn team_by_id(&self, id: &str) -> Result, String> { 8 | println!("id: {} - team_by_id postgres repo", id); 9 | 10 | let obj = Team { 11 | ..Default::default() 12 | }; 13 | 14 | Ok(Some(obj)) 15 | } 16 | 17 | async fn team_all(&self, input: &TeamAllInput) -> Result, String> { 18 | println!("input: {:?} - team_all postgres repo", input); 19 | 20 | let all = vec![Team { 21 | ..Default::default() 22 | }]; 23 | 24 | Ok(all) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/services/queries/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::entities::{player::Player, team::Team}; 2 | use crate::queries::{player::PlayerAllInput, team::TeamAllInput}; 3 | 4 | pub mod player; 5 | pub mod team; 6 | 7 | pub trait RepoTrait: Send + Sync + RepoPlayer + RepoTeam {} 8 | 9 | impl RepoTrait for T {} 10 | 11 | #[async_trait::async_trait] 12 | pub trait RepoPlayer: Send + Sync { 13 | async fn player_by_id(&self, id: &str) -> Result, String>; 14 | 15 | async fn player_all(&self, input: &PlayerAllInput) -> Result, String>; 16 | } 17 | 18 | #[async_trait::async_trait] 19 | pub trait RepoTeam: Send + Sync { 20 | async fn team_by_id(&self, id: &str) -> Result, String>; 21 | 22 | async fn team_all(&self, input: &TeamAllInput) -> Result, String>; 23 | } 24 | -------------------------------------------------------------------------------- /src/services/queries/player/find.rs: -------------------------------------------------------------------------------- 1 | use crate::entities::player::Player; 2 | use crate::{services::queries::RepoTrait, Deps}; 3 | use std::sync::Arc; 4 | 5 | struct ExecutorImpl { 6 | deps: Arc>, 7 | } 8 | 9 | pub fn new_executor(deps: Arc>) -> Box { 10 | Box::new(ExecutorImpl { deps }) 11 | } 12 | 13 | #[async_trait::async_trait] 14 | pub trait Executor: Send + Sync { 15 | async fn execute(&self, id: &str) -> Result, String>; 16 | } 17 | 18 | #[async_trait::async_trait] 19 | impl Executor for ExecutorImpl { 20 | async fn execute(&self, id: &str) -> Result, String> { 21 | let res = self.deps.queries_repo.player_by_id(id).await?; 22 | 23 | Ok(res) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/repositories/postgres/player/queries.rs: -------------------------------------------------------------------------------- 1 | use crate::entities::player::Player; 2 | use crate::queries::player::PlayerAllInput; 3 | use crate::{repositories::postgres::Repo, services::queries::RepoPlayer}; 4 | 5 | #[async_trait::async_trait] 6 | impl RepoPlayer for Repo { 7 | async fn player_by_id(&self, id: &str) -> Result, String> { 8 | println!("id: {} - player_by_id postgres repo", id); 9 | 10 | let obj = Player { 11 | ..Default::default() 12 | }; 13 | 14 | Ok(Some(obj)) 15 | } 16 | 17 | async fn player_all(&self, input: &PlayerAllInput) -> Result, String> { 18 | println!("input: {:?} - player_all postgres repo", input); 19 | 20 | let all = vec![Player { 21 | ..Default::default() 22 | }]; 23 | 24 | Ok(all) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/repositories/in_memory/player/queries.rs: -------------------------------------------------------------------------------- 1 | use crate::entities::player::Player; 2 | use crate::queries::player::PlayerAllInput; 3 | use crate::{repositories::in_memory::Repo, services::queries::RepoPlayer}; 4 | 5 | #[async_trait::async_trait] 6 | impl RepoPlayer for Repo { 7 | async fn player_by_id(&self, id: &str) -> Result, String> { 8 | println!("id: {} - player_by_id in_memory repo", id); 9 | 10 | let obj = Player { 11 | ..Default::default() 12 | }; 13 | 14 | Ok(Some(obj)) 15 | } 16 | 17 | async fn player_all(&self, input: &PlayerAllInput) -> Result, String> { 18 | println!("input: {:?} - player_all in_memory repo", input); 19 | 20 | let all = vec![Player { 21 | ..Default::default() 22 | }]; 23 | 24 | Ok(all) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/services/queries/player/all.rs: -------------------------------------------------------------------------------- 1 | use crate::entities::player::Player; 2 | use crate::queries::player::PlayerAllInput; 3 | use crate::{services::queries::RepoTrait, Deps}; 4 | use std::sync::Arc; 5 | 6 | struct ExecutorImpl { 7 | deps: Arc>, 8 | } 9 | 10 | pub fn new_executor(deps: Arc>) -> Box { 11 | Box::new(ExecutorImpl { deps }) 12 | } 13 | 14 | #[async_trait::async_trait] 15 | pub trait Executor: Send + Sync { 16 | async fn execute(&self, input: &PlayerAllInput) -> Result, String>; 17 | } 18 | 19 | #[async_trait::async_trait] 20 | impl Executor for ExecutorImpl { 21 | async fn execute(&self, input: &PlayerAllInput) -> Result, String> { 22 | let all = self.deps.queries_repo.player_all(input).await?; 23 | 24 | Ok(all) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.codesandbox/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye 2 | 3 | # common packages 4 | RUN apt-get update && \ 5 | apt-get install --no-install-recommends -y \ 6 | build-essential \ 7 | autoconf automake autotools-dev libtool xutils-dev \ 8 | ca-certificates curl file && \ 9 | rm -rf /var/lib/apt/lists/* 10 | 11 | # install toolchain 12 | RUN curl https://sh.rustup.rs -sSf | \ 13 | sh -s -- --default-toolchain stable -y 14 | 15 | ENV PATH=/root/.cargo/bin:$PATH 16 | 17 | # install cargo binstall to reduce image size 18 | WORKDIR /root/.cargo/bin 19 | RUN curl -L --output cargo-binstall.tgz https://github.com/cargo-bins/cargo-binstall/releases/download/v0.19.3/cargo-binstall-x86_64-unknown-linux-gnu.tgz && \ 20 | tar -xvzf cargo-binstall.tgz && \ 21 | chmod +x cargo-binstall && \ 22 | rm cargo-binstall.tgz 23 | 24 | RUN rustup component add rust-analyzer rustfmt rust-src clippy && \ 25 | cargo binstall -y cargo-watch 26 | 27 | WORKDIR /root 28 | -------------------------------------------------------------------------------- /src/repositories/postgres/team/commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | entities::team::Team, 3 | repositories::postgres::Repo, 4 | services::commands::{ 5 | self, 6 | team::{create::TeamCreateLambdaArgs, TeamInput}, 7 | Lambda, 8 | }, 9 | }; 10 | 11 | #[async_trait::async_trait] 12 | impl commands::RepoTeam for Repo { 13 | async fn team_create( 14 | &self, 15 | input: &TeamInput, 16 | lambda: &Lambda, 17 | ) -> Result { 18 | println!("input: {:?} - team_create postgres repo", input); 19 | 20 | // create a transaction here because I can use it for other repository methods calls 21 | let tx = self.pool.begin().await.unwrap(); 22 | 23 | // wait for lambda result 24 | let team = lambda(TeamCreateLambdaArgs {}).await?; 25 | 26 | // insert team here with appropriate code for this repository 27 | 28 | tx.commit().await.unwrap(); 29 | 30 | Ok(team) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/services/commands/player/delete.rs: -------------------------------------------------------------------------------- 1 | use crate::entities::player::Player; 2 | use crate::services::commands::RepoTrait; 3 | use crate::Deps; 4 | use std::sync::Arc; 5 | 6 | struct ExecutorImpl { 7 | deps: Arc>, 8 | } 9 | 10 | pub fn new_executor(deps: Arc>) -> Box { 11 | Box::new(ExecutorImpl { deps }) 12 | } 13 | 14 | #[async_trait::async_trait] 15 | pub trait Executor: Send + Sync { 16 | async fn execute(&self, id: &str) -> Result; 17 | } 18 | 19 | #[async_trait::async_trait] 20 | impl Executor for ExecutorImpl { 21 | async fn execute(&self, id: &str) -> Result { 22 | self.deps 23 | .commands_repo 24 | .player_delete(id, &|_actual| Box::pin(async { Ok(()) })) 25 | .await?; 26 | 27 | let res = true; 28 | 29 | Ok(res) 30 | } 31 | } 32 | 33 | pub struct PlayerDeleteLambdaArgs { 34 | pub actual: Player, 35 | } 36 | -------------------------------------------------------------------------------- /src/repositories/in_memory/team/commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | entities::team::Team, 3 | repositories::in_memory::Repo, 4 | services::commands::{ 5 | self, 6 | team::{create::TeamCreateLambdaArgs, TeamInput}, 7 | Lambda, 8 | }, 9 | }; 10 | 11 | #[async_trait::async_trait] 12 | impl commands::RepoTeam for Repo { 13 | async fn team_create( 14 | &self, 15 | input: &TeamInput, 16 | lambda: &Lambda, 17 | ) -> Result { 18 | println!("input: {:?} - team_create in_memory repo", input); 19 | 20 | // create a transaction here because I can use it for other repository methods calls 21 | // let mut tx = self.pool.begin().await?; 22 | 23 | // wait for lambda result 24 | let team = lambda(TeamCreateLambdaArgs {}).await?; 25 | 26 | // insert team here with appropriate code for this repository 27 | 28 | // commit DB transaction here 29 | // tx.commit().await?; 30 | 31 | Ok(team) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/services/commands/team/create.rs: -------------------------------------------------------------------------------- 1 | use crate::entities::team::Team; 2 | use crate::services::commands::{team::TeamInput, RepoTrait}; 3 | use crate::Deps; 4 | use std::sync::Arc; 5 | 6 | struct ExecutorImpl { 7 | deps: Arc>, 8 | } 9 | 10 | pub fn new_executor(deps: Arc>) -> Box { 11 | Box::new(ExecutorImpl { deps }) 12 | } 13 | 14 | #[async_trait::async_trait] 15 | pub trait Executor: Send + Sync { 16 | async fn execute(&self, input: &TeamInput) -> Result; 17 | } 18 | 19 | #[async_trait::async_trait] 20 | impl Executor for ExecutorImpl { 21 | async fn execute(&self, input: &TeamInput) -> Result { 22 | let res = self 23 | .deps 24 | .commands_repo 25 | .team_create(input, &|_| { 26 | Box::pin(async move { 27 | let obj = Team { 28 | id: "new_id".to_string(), 29 | name: input.name.to_owned(), 30 | missing_players: 11, 31 | }; 32 | 33 | Ok(obj) 34 | }) 35 | }) 36 | .await?; 37 | Ok(res) 38 | } 39 | } 40 | 41 | pub struct TeamCreateLambdaArgs {} 42 | -------------------------------------------------------------------------------- /src/services/commands/player/create.rs: -------------------------------------------------------------------------------- 1 | use crate::entities::player::Player; 2 | use crate::services::commands::{player::PlayerInput, RepoTrait}; 3 | use crate::Deps; 4 | use std::sync::Arc; 5 | 6 | struct ExecutorImpl { 7 | deps: Arc>, 8 | } 9 | 10 | pub fn new_executor(deps: Arc>) -> Box { 11 | Box::new(ExecutorImpl { deps }) 12 | } 13 | 14 | #[async_trait::async_trait] 15 | pub trait Executor: Send + Sync { 16 | async fn execute(&self, input: &PlayerInput) -> Result; 17 | } 18 | 19 | #[async_trait::async_trait] 20 | impl Executor for ExecutorImpl { 21 | async fn execute(&self, input: &PlayerInput) -> Result { 22 | let res = self 23 | .deps 24 | .commands_repo 25 | .player_create(input, &|_| { 26 | let input = input; 27 | 28 | Box::pin(async move { 29 | let obj = Player { 30 | id: "new_id".to_string(), 31 | name: input.name.to_owned(), 32 | team_id: input.team_id.to_owned(), 33 | }; 34 | 35 | Ok(obj) 36 | }) 37 | }) 38 | .await?; 39 | Ok(res) 40 | } 41 | } 42 | 43 | pub struct PlayerCreateLambdaArgs {} 44 | -------------------------------------------------------------------------------- /src/services/commands/mod.rs: -------------------------------------------------------------------------------- 1 | use self::player::delete::PlayerDeleteLambdaArgs; 2 | use self::player::update::PlayerUpdateLambdaArgs; 3 | use self::{player::create::PlayerCreateLambdaArgs, team::create::TeamCreateLambdaArgs}; 4 | use crate::entities::player::Player; 5 | use crate::entities::{team::Team}; 6 | use crate::services::commands::{player::PlayerInput, team::TeamInput}; 7 | use std::{future::Future, pin::Pin}; 8 | 9 | pub mod player; 10 | pub mod team; 11 | 12 | pub trait RepoTrait: Send + Sync + RepoPlayer + RepoTeam {} 13 | 14 | impl RepoTrait for T {} 15 | 16 | pub type Lambda<'a, ArgT, ResT> = 17 | dyn 'a + Fn(ArgT) -> Pin> + Send + 'a>> + Sync; 18 | 19 | #[async_trait::async_trait] 20 | pub trait RepoPlayer: Send + Sync { 21 | async fn player_create<'a>( 22 | &'a self, 23 | input: &PlayerInput, 24 | lambda: &Lambda, 25 | ) -> Result; 26 | 27 | async fn player_delete<'a>( 28 | &'a self, 29 | id: &str, 30 | lambda: &Lambda, 31 | ) -> Result<(), String>; 32 | 33 | async fn player_update<'a>( 34 | &'a self, 35 | input: &PlayerInput, 36 | lambda: &Lambda, 37 | ) -> Result; 38 | } 39 | 40 | #[async_trait::async_trait] 41 | pub trait RepoTeam: Send + Sync { 42 | async fn team_create<'a>( 43 | &'a self, 44 | input: &TeamInput, 45 | lambda: &Lambda, 46 | ) -> Result; 47 | } 48 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use services::{ 2 | commands::{self, player::PlayerInput}, 3 | queries, App, 4 | }; 5 | use std::sync::Arc; 6 | 7 | pub mod entities; 8 | pub mod repositories; 9 | pub mod services; 10 | 11 | pub struct Deps { 12 | pub commands_repo: Arc, 13 | pub queries_repo: Arc, 14 | } 15 | 16 | #[tokio::main] 17 | async fn main() -> Result<(), String> { 18 | // let use_postgres = false; 19 | 20 | // This obviously works if alone: 21 | let db_repo = Arc::new(repositories::in_memory::Repo::new()); 22 | 23 | // This obviously works if alone: 24 | // let pg_pool = Arc::new(sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres").await.unwrap()); 25 | // let db_repo = Arc::new(repositories::postgres::Repo::new(pg_pool)); 26 | 27 | // This doesn't work instead: 28 | // let db_repo = if use_postgres { 29 | // let pg_pool = Arc::new( 30 | // sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres") 31 | // .await 32 | // .unwrap(), 33 | // ); 34 | 35 | // Arc::new(repositories::postgres::Repo::new(pg_pool)) 36 | // } else { 37 | // Arc::new(repositories::in_memory::Repo::new()) 38 | // }; 39 | 40 | let deps = Arc::new(Deps { 41 | commands_repo: db_repo.clone(), 42 | queries_repo: db_repo, 43 | }); 44 | 45 | let app = App { 46 | commands: { 47 | services::Commands { 48 | player_create: commands::player::create::new_executor(deps.clone()), 49 | player_delete: commands::player::delete::new_executor(deps.clone()), 50 | player_update: commands::player::update::new_executor(deps.clone()), 51 | team_create: commands::team::create::new_executor(deps.clone()), 52 | } 53 | }, 54 | 55 | queries: { 56 | services::Queries { 57 | player_by_id: queries::player::find::new_executor(deps.clone()), 58 | player_all: queries::player::all::new_executor(deps.clone()), 59 | team_by_id: queries::team::find::new_executor(deps.clone()), 60 | } 61 | }, 62 | }; 63 | 64 | let new_player_input = PlayerInput { 65 | name: "Bob".to_string(), 66 | ..Default::default() 67 | }; 68 | 69 | let new_player = app 70 | .commands 71 | .player_create 72 | .execute(&new_player_input) 73 | .await?; 74 | 75 | dbg!(&new_player); 76 | 77 | Ok(()) 78 | } 79 | -------------------------------------------------------------------------------- /src/services/commands/player/update.rs: -------------------------------------------------------------------------------- 1 | use crate::entities::{player::Player, team::Team}; 2 | use crate::services::commands::{player::PlayerInput, RepoTrait}; 3 | use crate::Deps; 4 | use std::sync::Arc; 5 | 6 | struct ExecutorImpl { 7 | deps: Arc>, 8 | } 9 | 10 | pub fn new_executor(deps: Arc>) -> Box { 11 | Box::new(ExecutorImpl { deps }) 12 | } 13 | 14 | #[async_trait::async_trait] 15 | pub trait Executor: Send + Sync { 16 | async fn execute(&self, input: &PlayerInput) -> Result; 17 | } 18 | 19 | #[async_trait::async_trait] 20 | impl Executor for ExecutorImpl { 21 | async fn execute(&self, input: &PlayerInput) -> Result { 22 | let player = self 23 | .deps 24 | .commands_repo 25 | .player_update(input, &|args| { 26 | Box::pin(async { 27 | // I want to verify if there is any place for my player before updating it by using a method like the below 28 | // but I wanna check this in a DB transaction 29 | 30 | // I cannot pass transaction using lambda function because in the service layer I don't want to specify which DB I'm using and wich crate 31 | 32 | // So one way to do this is by passing the team in the lambda args in `PlayerUpdateLambdaArgs`. 33 | 34 | // The `team` is queried using the DB transaction on the repository level 35 | // but as you can imagine this is a mess: I'm writing code here and there, back and forth 36 | 37 | let team = self 38 | .deps 39 | .queries_repo 40 | .team_by_id(&input.team_id) 41 | .await 42 | .unwrap(); 43 | 44 | if let Some(team) = team { 45 | if team.missing_players == 0 { 46 | return Err("no place for your player!".to_string()); 47 | } 48 | } 49 | 50 | let obj = Player { 51 | id: args.actual.id, 52 | name: input.name.to_owned(), 53 | team_id: input.team_id.to_owned(), 54 | }; 55 | 56 | Ok(obj) 57 | }) 58 | }) 59 | .await?; 60 | 61 | Ok(player) 62 | } 63 | } 64 | 65 | pub struct PlayerUpdateLambdaArgs { 66 | pub actual: Player, 67 | pub actual_team: Team, 68 | } 69 | -------------------------------------------------------------------------------- /src/repositories/postgres/player/commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | entities::{player::Player, team::Team}, 3 | repositories::postgres::Repo, 4 | services::commands::{ 5 | self, 6 | player::{ 7 | create::PlayerCreateLambdaArgs, delete::PlayerDeleteLambdaArgs, 8 | update::PlayerUpdateLambdaArgs, PlayerInput, 9 | }, 10 | Lambda, 11 | }, 12 | }; 13 | 14 | #[async_trait::async_trait] 15 | impl commands::RepoPlayer for Repo { 16 | async fn player_create( 17 | &self, 18 | input: &PlayerInput, 19 | lambda: &Lambda, 20 | ) -> Result { 21 | println!("input: {:?} - player_create postgres repo", input); 22 | 23 | // create a transaction here because I can use it for other repository methods calls 24 | let tx = self.pool.begin().await.unwrap(); 25 | 26 | // wait for lambda result 27 | let player = lambda(PlayerCreateLambdaArgs {}).await?; 28 | 29 | // insert player here with appropriate code for this repository 30 | 31 | tx.commit().await.unwrap(); 32 | 33 | Ok(player) 34 | } 35 | 36 | async fn player_delete( 37 | &self, 38 | id: &str, 39 | lambda: &Lambda, 40 | ) -> Result<(), String> { 41 | println!("id: {:?} - player_delete postgres repo", id); 42 | 43 | // create a transaction here because I can use it for other repository methods calls 44 | let tx = self.pool.begin().await.unwrap(); 45 | 46 | // fetch current player here with appropriate code for this repository 47 | let actual = Player { 48 | ..Default::default() 49 | }; 50 | 51 | // wait for lambda result 52 | lambda(PlayerDeleteLambdaArgs { 53 | actual: actual.into(), 54 | }) 55 | .await?; 56 | 57 | // delete player here with appropriate code for this repository 58 | 59 | tx.commit().await.unwrap(); 60 | 61 | Ok(()) 62 | } 63 | 64 | async fn player_update( 65 | &self, 66 | input: &PlayerInput, 67 | lambda: &Lambda, 68 | ) -> Result { 69 | println!("input: {:?} - player_update postgres repo", input); 70 | 71 | // create a transaction here because I can use it for other repository methods calls 72 | let tx = self.pool.begin().await.unwrap(); 73 | 74 | // fetch current player here with appropriate code for this repository 75 | let actual = Player { 76 | ..Default::default() 77 | }; 78 | 79 | // fetch current team here with appropriate code for this repository (a fake one now) 80 | let actual_team = Team { 81 | ..Default::default() 82 | }; 83 | 84 | // wait for lambda result 85 | let player = lambda(PlayerUpdateLambdaArgs { 86 | actual, 87 | actual_team, 88 | }) 89 | .await?; 90 | 91 | // update player here with appropriate code for this repository 92 | 93 | tx.commit().await.unwrap(); 94 | 95 | Ok(player) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/repositories/in_memory/player/commands.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | entities::{player::Player, team::Team}, 3 | repositories::in_memory::Repo, 4 | services::commands::{ 5 | self, 6 | player::{ 7 | create::PlayerCreateLambdaArgs, delete::PlayerDeleteLambdaArgs, 8 | update::PlayerUpdateLambdaArgs, PlayerInput, 9 | }, 10 | Lambda, 11 | }, 12 | }; 13 | 14 | #[async_trait::async_trait] 15 | impl commands::RepoPlayer for Repo { 16 | async fn player_create( 17 | &self, 18 | input: &PlayerInput, 19 | lambda: &Lambda, 20 | ) -> Result { 21 | println!("input: {:?} - player_create in_memory repo", input); 22 | 23 | // create a transaction here because I can use it for other repository methods calls 24 | // let mut tx = self.pool.begin().await?; 25 | 26 | // wait for lambda result 27 | let player = lambda(PlayerCreateLambdaArgs {}).await?; 28 | 29 | // insert player here with appropriate code for this repository 30 | 31 | // commit DB transaction here 32 | // tx.commit().await?; 33 | 34 | Ok(player) 35 | } 36 | 37 | async fn player_delete( 38 | &self, 39 | id: &str, 40 | lambda: &Lambda, 41 | ) -> Result<(), String> { 42 | println!("id: {:?} - player_delete in_memory repo", id); 43 | 44 | // create a transaction here because I can use it for other repository methods calls 45 | // let mut tx = self.pool.begin().await?; 46 | 47 | // fetch current player here with appropriate code for this repository 48 | let actual = Player { 49 | ..Default::default() 50 | }; 51 | 52 | // wait for lambda result 53 | lambda(PlayerDeleteLambdaArgs { 54 | actual: actual.into(), 55 | }) 56 | .await?; 57 | 58 | // delete player here with appropriate code for this repository 59 | 60 | // commit DB transaction here 61 | // tx.commit().await?; 62 | 63 | Ok(()) 64 | } 65 | 66 | async fn player_update( 67 | &self, 68 | input: &PlayerInput, 69 | lambda: &Lambda, 70 | ) -> Result { 71 | println!("input: {:?} - player_update in_memory repo", input); 72 | 73 | // create a transaction here because I can use it for other repository methods calls 74 | // let mut tx = self.pool.begin().await?; 75 | 76 | // fetch current player here with appropriate code for this repository (a fake one now) 77 | let actual = Player { 78 | ..Default::default() 79 | }; 80 | 81 | // fetch current team here with appropriate code for this repository (a fake one now) 82 | let actual_team = Team { 83 | ..Default::default() 84 | }; 85 | 86 | // wait for lambda result 87 | let player = lambda(PlayerUpdateLambdaArgs { 88 | actual, 89 | actual_team, 90 | }) 91 | .await?; 92 | 93 | // update player here with appropriate code for this repository 94 | 95 | // commit DB transaction here 96 | // tx.commit().await?; 97 | 98 | Ok(player) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is just an example of an _(still incomplete)_ real-world project written in Rust using a [clean architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html). 2 | 3 | # Goals 4 | 5 | My intent is to have an app build in 4 layers: 6 | 7 | - `entities`: 8 | 9 | - _some call this layer "domain"_, not important for now, just the minimum 10 | 11 | - `services`: 12 | 13 | - _some call this layer "use cases"_, this is where business logic lives (just CRUD methods for now) 14 | 15 | - `repositories`: 16 | 17 | - _some call this layer "adapters"_, this is where concrete implementation of DB/cache/mail drivers lives 18 | 19 | - `ports`: 20 | - _some call this layer "controllers or presenters"_, still not present and not important for now, I'm using `main.rs` for this 21 | 22 | ## Reproduction 23 | 24 | https://codesandbox.io/p/github/frederikhors/rust-clean-architecture-with-db-transactions/main 25 | 26 | # The issues 27 | 28 | ## Number 1 29 | 30 | If you open the [`main.rs`](https://github.com/frederikhors/rust-clean-architecture-with-db-transactions/blob/main/src/main.rs#L18-L38) file you can see the first issue: 31 | 32 |
33 | Expand the code 34 | 35 | ```rust 36 | // This obviously works if alone: 37 | // let db_repo = Arc::new(repositories::in_memory::Repo::new()); 38 | 39 | // This obviously works if alone: 40 | // let pg_pool = Arc::new(sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres").await.unwrap()); 41 | // let db_repo = Arc::new(repositories::postgres::Repo::new(pg_pool)); 42 | 43 | // This doesn't work instead: 44 | let db_repo = if use_postgres { 45 | let pg_pool = Arc::new(sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres").await.unwrap()); 46 | 47 | Arc::new(repositories::postgres::Repo::new(pg_pool)) 48 | } else { 49 | Arc::new(repositories::in_memory::Repo::new()) 50 | }; 51 | ``` 52 | 53 |
54 | 55 | My intent here is to change repository in use based on a variable, but Rust doesn't like it, this is the error: 56 | 57 |
58 | Expand the error 59 | 60 | ``` 61 | error[E0308]: `if` and `else` have incompatible types 62 | --> src\main.rs:37:9 63 | | 64 | 28 | let db_repo = if use_postgres { 65 | | ___________________- 66 | 29 | | let pg_pool = Arc::new( 67 | 30 | | sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres") 68 | 31 | | .await 69 | ... | 70 | 35 | | Arc::new(repositories::postgres::Repo::new(pg_pool)) 71 | | | ---------------------------------------------------- expected because of this 72 | 36 | | } else { 73 | 37 | | Arc::new(repositories::in_memory::Repo::new()) 74 | | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `repositories::postgres::Repo`, found struct `in_memory::Repo` 75 | 38 | | }; 76 | | |_____- `if` and `else` have incompatible types 77 | | 78 | = note: struct `in_memory::Repo` and struct `repositories::postgres::Repo` have similar names, but are actually distinct types 79 | note: struct `in_memory::Repo` is defined in module `crate::repositories::in_memory` of the current crate 80 | --> src\repositories\in_memory\mod.rs:6:1 81 | | 82 | 6 | pub struct Repo { 83 | | ^^^^^^^^^^^^^^^ 84 | note: struct `repositories::postgres::Repo` is defined in module `crate::repositories::postgres` of the current crate 85 | --> src\repositories\postgres\mod.rs:6:1 86 | | 87 | 6 | pub struct Repo { 88 | | ^^^^^^^^^^^^^^^ 89 | ``` 90 | 91 |
92 | 93 | ## Issue number 2 94 | 95 | The second issue is about the [usage of a DB transaction in a service](https://github.com/frederikhors/rust-clean-architecture-with-db-transactions/blob/main/src/services/commands/player/update.rs#L26-L56) (of the same [bounded context](https://martinfowler.com/bliki/BoundedContext.html)): 96 | 97 |
98 | Expand the code 99 | 100 | ```rust 101 | async fn execute(&self, input: &PlayerInput) -> Result { 102 | let player = self 103 | .deps 104 | .commands_repo 105 | .player_update(input, &|args| { 106 | Box::pin(async { 107 | // I want to verify if there is any place for my player before updating it by using a method like the below 108 | // but I wanna check this in a DB transaction 109 | 110 | // I cannot pass transaction using lambda function because in the service layer I don't want to specify which DB I'm using and wich crate 111 | 112 | // So one way to do this is by passing the team in the lambda args in `PlayerUpdateLambdaArgs`. 113 | 114 | // The `team` is queried using the DB transaction on the repository level 115 | // but as you can imagine this is a mess: I'm writing code here and there, back and forth 116 | 117 | let team = self 118 | .deps 119 | .queries_repo 120 | .team_by_id(&input.team_id) 121 | .await 122 | .unwrap(); 123 | 124 | if let Some(team) = team { 125 | if team.missing_players == 0 { 126 | return Err("no place for your player!".to_string()); 127 | } 128 | } 129 | 130 | let obj = Player { 131 | id: args.actual.id, 132 | name: input.name.to_owned(), 133 | team_id: input.team_id.to_owned(), 134 | }; 135 | 136 | Ok(obj) 137 | }) 138 | }) 139 | .await?; 140 | 141 | Ok(player) 142 | } 143 | ``` 144 | 145 |
146 | 147 | As you can see I'm using a lambda function with a struct as argument because this is the only way I can fetch in the repository level the objects I need on the business logic level. 148 | 149 | But as you can imagine the code is not linear and I have to go back & forth. 150 | 151 | I think I should have something (but I don't know what) on the service layer to start (and commit/rollback) a DB transaction from there: but - as properly established by the rules of Clean architecture - the service layer cannot know the implementation details of the underlying levels (repositories). 152 | 153 | I would like to use in my services something like (pseudo code): 154 | 155 | ```rust 156 | // Start a new DB transaction now to use with the below methods 157 | 158 | let transaction = [DONT_KNOW_HOW_PLEASE_START_A_NEW_DB_TRANSACTION](); 159 | 160 | let team = self.repo.team_by_id(transaction, team_id).await?; 161 | 162 | if !team.has_free_places() { return }; 163 | 164 | let mut player = self.repo.player_by_id(transaction, player_id).await?; 165 | 166 | player.team_id = team.id; 167 | 168 | let player = self.repo.player_update(player).await?; 169 | 170 | Ok(player) 171 | ``` 172 | 173 | Is there a way to fix this? 174 | 175 | Maybe yes and [there is a project](https://github.com/dpc/sniper) I found searching about this, but the code is too complex for me to completely understand how to do this in my project **and if there is something better** or even **if I'm wrong and why**. 176 | 177 | The (maybe) interesting code is here: https://github.com/dpc/sniper/blob/master/src/persistence.rs. 178 | 179 | **Another way** I found to fix this **is using state machines**. I created [a dedicated branch](https://github.com/frederikhors/rust-clean-architecture-with-db-transactions/tree/using-state-machines) with [one state machine usage for the `player_create` method](https://github.com/frederikhors/rust-clean-architecture-with-db-transactions/compare/using-state-machines?expand=1). like this: 180 | 181 |
182 | Expand the code 183 | 184 | ```rust 185 | // in the repository 186 | 187 | pub struct PlayerCreate<'a> { 188 | tx: sqlx::Transaction<'a, sqlx::Postgres>, 189 | pub input: &'a PlayerInput, 190 | } 191 | 192 | #[async_trait::async_trait] 193 | impl<'a> PlayerCreateTrait for PlayerCreate<'a> { 194 | async fn check_for_team_free_spaces(&mut self, team_id: &str) -> Result { 195 | let team = self::Repo::team_by_id_using_tx(&mut self.tx, team_id).await?; 196 | 197 | Ok(team.missing_players > 0) 198 | } 199 | 200 | async fn commit(mut self, _player: &Player) -> Result { 201 | // update the player here 202 | 203 | let saved_player = Player { 204 | ..Default::default() 205 | }; 206 | 207 | self.tx.commit().await.unwrap(); 208 | 209 | Ok(saved_player) 210 | } 211 | 212 | } 213 | 214 | #[async_trait::async_trait] 215 | impl commands::RepoPlayer for Repo { 216 | type PlayerCreate<'a> = PlayerCreate<'a>; 217 | 218 | async fn player_create_start<'a>( 219 | &self, 220 | input: &'a PlayerInput, 221 | ) -> Result, String> { 222 | let tx = self.pool.begin().await.unwrap(); 223 | 224 | Ok(PlayerCreate { tx, input }) 225 | } 226 | 227 | } 228 | 229 | // in the service 230 | 231 | async fn execute(&self, input: &PlayerInput) -> Result { 232 | let mut state_machine = self.deps.commands_repo.player_create_start(input).await?; 233 | 234 | if !(state_machine.check_for_team_free_spaces(&input.team_id)).await? { 235 | return Err("no free space available for this team".to_string()); 236 | } 237 | 238 | let obj = Player { 239 | id: "new_id".to_string(), 240 | name: input.name.to_owned(), 241 | team_id: input.team_id.to_owned(), 242 | }; 243 | 244 | let res = state_machine.commit(&obj).await?; 245 | 246 | Ok(res) 247 | 248 | } 249 | 250 | ``` 251 | 252 |
253 | 254 | But there are two big cons to this: 255 | 256 | - a lot of code to write (also very repetitive); 257 | 258 | - the same concepts must be used and repeated both in the repository layer and in the service layer or in any case the synthesis work to be done is not profitable for business logic but only for finding an intelligent way to avoid repeating code; 259 | 260 | - you have to write repository methods which are very similar with the only difference that some take a db transaction as an argument and the other doesn't. 261 | 262 | # Alternative ways 263 | 264 | ## Bloom legacy 265 | 266 | I found the post: https://kerkour.com/rust-web-application-clean-architecture with the code here: https://github.com/skerkour/bloom-legacy. 267 | 268 | I really like this code except for: 269 | 270 | 1. the service layer knows about repository implementation details 271 | 272 | 1. (and for this reason) it is impossible to change at runtime (or just to mock during tests) the DB driver. 273 | 274 | I opened the issue: https://github.com/skerkour/bloom-legacy/issues/70 and I'm waiting for the author. 275 | 276 | ### Question 277 | 278 | 1. Can you help me solve issues 1 and 2? 279 | 280 | 1. Can you suggest an alternative way? I'm open to everything! 281 | 282 | Thanks in advance. 283 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.7.6" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 10 | dependencies = [ 11 | "getrandom", 12 | "once_cell", 13 | "version_check", 14 | ] 15 | 16 | [[package]] 17 | name = "async-trait" 18 | version = "0.1.68" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" 21 | dependencies = [ 22 | "proc-macro2", 23 | "quote", 24 | "syn 2.0.12", 25 | ] 26 | 27 | [[package]] 28 | name = "atoi" 29 | version = "1.0.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" 32 | dependencies = [ 33 | "num-traits", 34 | ] 35 | 36 | [[package]] 37 | name = "autocfg" 38 | version = "1.1.0" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 41 | 42 | [[package]] 43 | name = "base64" 44 | version = "0.13.1" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 47 | 48 | [[package]] 49 | name = "base64" 50 | version = "0.21.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" 53 | 54 | [[package]] 55 | name = "bitflags" 56 | version = "1.3.2" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 59 | 60 | [[package]] 61 | name = "block-buffer" 62 | version = "0.10.4" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 65 | dependencies = [ 66 | "generic-array", 67 | ] 68 | 69 | [[package]] 70 | name = "bumpalo" 71 | version = "3.12.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" 74 | 75 | [[package]] 76 | name = "byteorder" 77 | version = "1.4.3" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 80 | 81 | [[package]] 82 | name = "bytes" 83 | version = "1.4.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 86 | 87 | [[package]] 88 | name = "cc" 89 | version = "1.0.79" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 92 | 93 | [[package]] 94 | name = "cfg-if" 95 | version = "1.0.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 98 | 99 | [[package]] 100 | name = "cpufeatures" 101 | version = "0.2.6" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" 104 | dependencies = [ 105 | "libc", 106 | ] 107 | 108 | [[package]] 109 | name = "crossbeam-queue" 110 | version = "0.3.8" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" 113 | dependencies = [ 114 | "cfg-if", 115 | "crossbeam-utils", 116 | ] 117 | 118 | [[package]] 119 | name = "crossbeam-utils" 120 | version = "0.8.15" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" 123 | dependencies = [ 124 | "cfg-if", 125 | ] 126 | 127 | [[package]] 128 | name = "crypto-common" 129 | version = "0.1.6" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 132 | dependencies = [ 133 | "generic-array", 134 | "typenum", 135 | ] 136 | 137 | [[package]] 138 | name = "digest" 139 | version = "0.10.6" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 142 | dependencies = [ 143 | "block-buffer", 144 | "crypto-common", 145 | "subtle", 146 | ] 147 | 148 | [[package]] 149 | name = "dirs" 150 | version = "4.0.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 153 | dependencies = [ 154 | "dirs-sys", 155 | ] 156 | 157 | [[package]] 158 | name = "dirs-sys" 159 | version = "0.3.7" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 162 | dependencies = [ 163 | "libc", 164 | "redox_users", 165 | "winapi", 166 | ] 167 | 168 | [[package]] 169 | name = "dotenvy" 170 | version = "0.15.7" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 173 | 174 | [[package]] 175 | name = "either" 176 | version = "1.8.1" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 179 | 180 | [[package]] 181 | name = "event-listener" 182 | version = "2.5.3" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 185 | 186 | [[package]] 187 | name = "form_urlencoded" 188 | version = "1.1.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 191 | dependencies = [ 192 | "percent-encoding", 193 | ] 194 | 195 | [[package]] 196 | name = "futures-channel" 197 | version = "0.3.28" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 200 | dependencies = [ 201 | "futures-core", 202 | "futures-sink", 203 | ] 204 | 205 | [[package]] 206 | name = "futures-core" 207 | version = "0.3.28" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 210 | 211 | [[package]] 212 | name = "futures-intrusive" 213 | version = "0.4.2" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" 216 | dependencies = [ 217 | "futures-core", 218 | "lock_api", 219 | "parking_lot", 220 | ] 221 | 222 | [[package]] 223 | name = "futures-sink" 224 | version = "0.3.28" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 227 | 228 | [[package]] 229 | name = "futures-task" 230 | version = "0.3.28" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 233 | 234 | [[package]] 235 | name = "futures-util" 236 | version = "0.3.28" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 239 | dependencies = [ 240 | "futures-core", 241 | "futures-sink", 242 | "futures-task", 243 | "pin-project-lite", 244 | "pin-utils", 245 | ] 246 | 247 | [[package]] 248 | name = "generic-array" 249 | version = "0.14.7" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 252 | dependencies = [ 253 | "typenum", 254 | "version_check", 255 | ] 256 | 257 | [[package]] 258 | name = "getrandom" 259 | version = "0.2.8" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 262 | dependencies = [ 263 | "cfg-if", 264 | "libc", 265 | "wasi", 266 | ] 267 | 268 | [[package]] 269 | name = "hashbrown" 270 | version = "0.12.3" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 273 | dependencies = [ 274 | "ahash", 275 | ] 276 | 277 | [[package]] 278 | name = "hashlink" 279 | version = "0.8.1" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" 282 | dependencies = [ 283 | "hashbrown", 284 | ] 285 | 286 | [[package]] 287 | name = "heck" 288 | version = "0.4.1" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 291 | dependencies = [ 292 | "unicode-segmentation", 293 | ] 294 | 295 | [[package]] 296 | name = "hermit-abi" 297 | version = "0.2.6" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 300 | dependencies = [ 301 | "libc", 302 | ] 303 | 304 | [[package]] 305 | name = "hex" 306 | version = "0.4.3" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 309 | 310 | [[package]] 311 | name = "hkdf" 312 | version = "0.12.3" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" 315 | dependencies = [ 316 | "hmac", 317 | ] 318 | 319 | [[package]] 320 | name = "hmac" 321 | version = "0.12.1" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 324 | dependencies = [ 325 | "digest", 326 | ] 327 | 328 | [[package]] 329 | name = "idna" 330 | version = "0.3.0" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 333 | dependencies = [ 334 | "unicode-bidi", 335 | "unicode-normalization", 336 | ] 337 | 338 | [[package]] 339 | name = "indexmap" 340 | version = "1.9.3" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 343 | dependencies = [ 344 | "autocfg", 345 | "hashbrown", 346 | ] 347 | 348 | [[package]] 349 | name = "instant" 350 | version = "0.1.12" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 353 | dependencies = [ 354 | "cfg-if", 355 | ] 356 | 357 | [[package]] 358 | name = "itertools" 359 | version = "0.10.5" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 362 | dependencies = [ 363 | "either", 364 | ] 365 | 366 | [[package]] 367 | name = "itoa" 368 | version = "1.0.6" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 371 | 372 | [[package]] 373 | name = "js-sys" 374 | version = "0.3.61" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" 377 | dependencies = [ 378 | "wasm-bindgen", 379 | ] 380 | 381 | [[package]] 382 | name = "libc" 383 | version = "0.2.140" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" 386 | 387 | [[package]] 388 | name = "lock_api" 389 | version = "0.4.9" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 392 | dependencies = [ 393 | "autocfg", 394 | "scopeguard", 395 | ] 396 | 397 | [[package]] 398 | name = "log" 399 | version = "0.4.17" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 402 | dependencies = [ 403 | "cfg-if", 404 | ] 405 | 406 | [[package]] 407 | name = "md-5" 408 | version = "0.10.5" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" 411 | dependencies = [ 412 | "digest", 413 | ] 414 | 415 | [[package]] 416 | name = "memchr" 417 | version = "2.5.0" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 420 | 421 | [[package]] 422 | name = "minimal-lexical" 423 | version = "0.2.1" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 426 | 427 | [[package]] 428 | name = "mio" 429 | version = "0.8.6" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" 432 | dependencies = [ 433 | "libc", 434 | "log", 435 | "wasi", 436 | "windows-sys", 437 | ] 438 | 439 | [[package]] 440 | name = "nom" 441 | version = "7.1.3" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 444 | dependencies = [ 445 | "memchr", 446 | "minimal-lexical", 447 | ] 448 | 449 | [[package]] 450 | name = "num-traits" 451 | version = "0.2.15" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 454 | dependencies = [ 455 | "autocfg", 456 | ] 457 | 458 | [[package]] 459 | name = "num_cpus" 460 | version = "1.15.0" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 463 | dependencies = [ 464 | "hermit-abi", 465 | "libc", 466 | ] 467 | 468 | [[package]] 469 | name = "once_cell" 470 | version = "1.17.1" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" 473 | 474 | [[package]] 475 | name = "parking_lot" 476 | version = "0.11.2" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 479 | dependencies = [ 480 | "instant", 481 | "lock_api", 482 | "parking_lot_core", 483 | ] 484 | 485 | [[package]] 486 | name = "parking_lot_core" 487 | version = "0.8.6" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" 490 | dependencies = [ 491 | "cfg-if", 492 | "instant", 493 | "libc", 494 | "redox_syscall", 495 | "smallvec", 496 | "winapi", 497 | ] 498 | 499 | [[package]] 500 | name = "paste" 501 | version = "1.0.12" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" 504 | 505 | [[package]] 506 | name = "percent-encoding" 507 | version = "2.2.0" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 510 | 511 | [[package]] 512 | name = "pin-project-lite" 513 | version = "0.2.9" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 516 | 517 | [[package]] 518 | name = "pin-utils" 519 | version = "0.1.0" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 522 | 523 | [[package]] 524 | name = "ppv-lite86" 525 | version = "0.2.17" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 528 | 529 | [[package]] 530 | name = "proc-macro2" 531 | version = "1.0.54" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" 534 | dependencies = [ 535 | "unicode-ident", 536 | ] 537 | 538 | [[package]] 539 | name = "quote" 540 | version = "1.0.26" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" 543 | dependencies = [ 544 | "proc-macro2", 545 | ] 546 | 547 | [[package]] 548 | name = "rand" 549 | version = "0.8.5" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 552 | dependencies = [ 553 | "libc", 554 | "rand_chacha", 555 | "rand_core", 556 | ] 557 | 558 | [[package]] 559 | name = "rand_chacha" 560 | version = "0.3.1" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 563 | dependencies = [ 564 | "ppv-lite86", 565 | "rand_core", 566 | ] 567 | 568 | [[package]] 569 | name = "rand_core" 570 | version = "0.6.4" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 573 | dependencies = [ 574 | "getrandom", 575 | ] 576 | 577 | [[package]] 578 | name = "redox_syscall" 579 | version = "0.2.16" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 582 | dependencies = [ 583 | "bitflags", 584 | ] 585 | 586 | [[package]] 587 | name = "redox_users" 588 | version = "0.4.3" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 591 | dependencies = [ 592 | "getrandom", 593 | "redox_syscall", 594 | "thiserror", 595 | ] 596 | 597 | [[package]] 598 | name = "ring" 599 | version = "0.16.20" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 602 | dependencies = [ 603 | "cc", 604 | "libc", 605 | "once_cell", 606 | "spin", 607 | "untrusted", 608 | "web-sys", 609 | "winapi", 610 | ] 611 | 612 | [[package]] 613 | name = "rustls" 614 | version = "0.20.8" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" 617 | dependencies = [ 618 | "log", 619 | "ring", 620 | "sct", 621 | "webpki", 622 | ] 623 | 624 | [[package]] 625 | name = "rustls-pemfile" 626 | version = "1.0.2" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" 629 | dependencies = [ 630 | "base64 0.21.0", 631 | ] 632 | 633 | [[package]] 634 | name = "ryu" 635 | version = "1.0.13" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 638 | 639 | [[package]] 640 | name = "scopeguard" 641 | version = "1.1.0" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 644 | 645 | [[package]] 646 | name = "sct" 647 | version = "0.7.0" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" 650 | dependencies = [ 651 | "ring", 652 | "untrusted", 653 | ] 654 | 655 | [[package]] 656 | name = "serde" 657 | version = "1.0.159" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" 660 | dependencies = [ 661 | "serde_derive", 662 | ] 663 | 664 | [[package]] 665 | name = "serde_derive" 666 | version = "1.0.159" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" 669 | dependencies = [ 670 | "proc-macro2", 671 | "quote", 672 | "syn 2.0.12", 673 | ] 674 | 675 | [[package]] 676 | name = "serde_json" 677 | version = "1.0.95" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" 680 | dependencies = [ 681 | "itoa", 682 | "ryu", 683 | "serde", 684 | ] 685 | 686 | [[package]] 687 | name = "sha1" 688 | version = "0.10.5" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 691 | dependencies = [ 692 | "cfg-if", 693 | "cpufeatures", 694 | "digest", 695 | ] 696 | 697 | [[package]] 698 | name = "sha2" 699 | version = "0.10.6" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" 702 | dependencies = [ 703 | "cfg-if", 704 | "cpufeatures", 705 | "digest", 706 | ] 707 | 708 | [[package]] 709 | name = "smallvec" 710 | version = "1.10.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 713 | 714 | [[package]] 715 | name = "socket2" 716 | version = "0.4.9" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 719 | dependencies = [ 720 | "libc", 721 | "winapi", 722 | ] 723 | 724 | [[package]] 725 | name = "spin" 726 | version = "0.5.2" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 729 | 730 | [[package]] 731 | name = "sqlformat" 732 | version = "0.2.1" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" 735 | dependencies = [ 736 | "itertools", 737 | "nom", 738 | "unicode_categories", 739 | ] 740 | 741 | [[package]] 742 | name = "sqlx" 743 | version = "0.6.3" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" 746 | dependencies = [ 747 | "sqlx-core", 748 | "sqlx-macros", 749 | ] 750 | 751 | [[package]] 752 | name = "sqlx-core" 753 | version = "0.6.3" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" 756 | dependencies = [ 757 | "ahash", 758 | "atoi", 759 | "base64 0.13.1", 760 | "bitflags", 761 | "byteorder", 762 | "bytes", 763 | "crossbeam-queue", 764 | "dirs", 765 | "dotenvy", 766 | "either", 767 | "event-listener", 768 | "futures-channel", 769 | "futures-core", 770 | "futures-intrusive", 771 | "futures-util", 772 | "hashlink", 773 | "hex", 774 | "hkdf", 775 | "hmac", 776 | "indexmap", 777 | "itoa", 778 | "libc", 779 | "log", 780 | "md-5", 781 | "memchr", 782 | "once_cell", 783 | "paste", 784 | "percent-encoding", 785 | "rand", 786 | "rustls", 787 | "rustls-pemfile", 788 | "serde", 789 | "serde_json", 790 | "sha1", 791 | "sha2", 792 | "smallvec", 793 | "sqlformat", 794 | "sqlx-rt", 795 | "stringprep", 796 | "thiserror", 797 | "time", 798 | "tokio-stream", 799 | "url", 800 | "webpki-roots", 801 | "whoami", 802 | ] 803 | 804 | [[package]] 805 | name = "sqlx-macros" 806 | version = "0.6.3" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" 809 | dependencies = [ 810 | "dotenvy", 811 | "either", 812 | "heck", 813 | "once_cell", 814 | "proc-macro2", 815 | "quote", 816 | "sqlx-core", 817 | "sqlx-rt", 818 | "syn 1.0.109", 819 | "url", 820 | ] 821 | 822 | [[package]] 823 | name = "sqlx-rt" 824 | version = "0.6.3" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" 827 | dependencies = [ 828 | "once_cell", 829 | "tokio", 830 | "tokio-rustls", 831 | ] 832 | 833 | [[package]] 834 | name = "stringprep" 835 | version = "0.1.2" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" 838 | dependencies = [ 839 | "unicode-bidi", 840 | "unicode-normalization", 841 | ] 842 | 843 | [[package]] 844 | name = "subtle" 845 | version = "2.4.1" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 848 | 849 | [[package]] 850 | name = "syn" 851 | version = "1.0.109" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 854 | dependencies = [ 855 | "proc-macro2", 856 | "quote", 857 | "unicode-ident", 858 | ] 859 | 860 | [[package]] 861 | name = "syn" 862 | version = "2.0.12" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" 865 | dependencies = [ 866 | "proc-macro2", 867 | "quote", 868 | "unicode-ident", 869 | ] 870 | 871 | [[package]] 872 | name = "thiserror" 873 | version = "1.0.40" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 876 | dependencies = [ 877 | "thiserror-impl", 878 | ] 879 | 880 | [[package]] 881 | name = "thiserror-impl" 882 | version = "1.0.40" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 885 | dependencies = [ 886 | "proc-macro2", 887 | "quote", 888 | "syn 2.0.12", 889 | ] 890 | 891 | [[package]] 892 | name = "time" 893 | version = "0.3.20" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" 896 | dependencies = [ 897 | "itoa", 898 | "serde", 899 | "time-core", 900 | "time-macros", 901 | ] 902 | 903 | [[package]] 904 | name = "time-core" 905 | version = "0.1.0" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" 908 | 909 | [[package]] 910 | name = "time-macros" 911 | version = "0.2.8" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" 914 | dependencies = [ 915 | "time-core", 916 | ] 917 | 918 | [[package]] 919 | name = "tinyvec" 920 | version = "1.6.0" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 923 | dependencies = [ 924 | "tinyvec_macros", 925 | ] 926 | 927 | [[package]] 928 | name = "tinyvec_macros" 929 | version = "0.1.1" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 932 | 933 | [[package]] 934 | name = "tokio" 935 | version = "1.27.0" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" 938 | dependencies = [ 939 | "autocfg", 940 | "bytes", 941 | "libc", 942 | "mio", 943 | "num_cpus", 944 | "pin-project-lite", 945 | "socket2", 946 | "tokio-macros", 947 | "windows-sys", 948 | ] 949 | 950 | [[package]] 951 | name = "tokio-macros" 952 | version = "2.0.0" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" 955 | dependencies = [ 956 | "proc-macro2", 957 | "quote", 958 | "syn 2.0.12", 959 | ] 960 | 961 | [[package]] 962 | name = "tokio-rustls" 963 | version = "0.23.4" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" 966 | dependencies = [ 967 | "rustls", 968 | "tokio", 969 | "webpki", 970 | ] 971 | 972 | [[package]] 973 | name = "tokio-stream" 974 | version = "0.1.12" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" 977 | dependencies = [ 978 | "futures-core", 979 | "pin-project-lite", 980 | "tokio", 981 | ] 982 | 983 | [[package]] 984 | name = "transaction" 985 | version = "0.1.0" 986 | dependencies = [ 987 | "async-trait", 988 | "sqlx", 989 | "tokio", 990 | ] 991 | 992 | [[package]] 993 | name = "typenum" 994 | version = "1.16.0" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 997 | 998 | [[package]] 999 | name = "unicode-bidi" 1000 | version = "0.3.13" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 1003 | 1004 | [[package]] 1005 | name = "unicode-ident" 1006 | version = "1.0.8" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 1009 | 1010 | [[package]] 1011 | name = "unicode-normalization" 1012 | version = "0.1.22" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1015 | dependencies = [ 1016 | "tinyvec", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "unicode-segmentation" 1021 | version = "1.10.1" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 1024 | 1025 | [[package]] 1026 | name = "unicode_categories" 1027 | version = "0.1.1" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 1030 | 1031 | [[package]] 1032 | name = "untrusted" 1033 | version = "0.7.1" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 1036 | 1037 | [[package]] 1038 | name = "url" 1039 | version = "2.3.1" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 1042 | dependencies = [ 1043 | "form_urlencoded", 1044 | "idna", 1045 | "percent-encoding", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "version_check" 1050 | version = "0.9.4" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1053 | 1054 | [[package]] 1055 | name = "wasi" 1056 | version = "0.11.0+wasi-snapshot-preview1" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1059 | 1060 | [[package]] 1061 | name = "wasm-bindgen" 1062 | version = "0.2.84" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" 1065 | dependencies = [ 1066 | "cfg-if", 1067 | "wasm-bindgen-macro", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "wasm-bindgen-backend" 1072 | version = "0.2.84" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" 1075 | dependencies = [ 1076 | "bumpalo", 1077 | "log", 1078 | "once_cell", 1079 | "proc-macro2", 1080 | "quote", 1081 | "syn 1.0.109", 1082 | "wasm-bindgen-shared", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "wasm-bindgen-macro" 1087 | version = "0.2.84" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" 1090 | dependencies = [ 1091 | "quote", 1092 | "wasm-bindgen-macro-support", 1093 | ] 1094 | 1095 | [[package]] 1096 | name = "wasm-bindgen-macro-support" 1097 | version = "0.2.84" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" 1100 | dependencies = [ 1101 | "proc-macro2", 1102 | "quote", 1103 | "syn 1.0.109", 1104 | "wasm-bindgen-backend", 1105 | "wasm-bindgen-shared", 1106 | ] 1107 | 1108 | [[package]] 1109 | name = "wasm-bindgen-shared" 1110 | version = "0.2.84" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" 1113 | 1114 | [[package]] 1115 | name = "web-sys" 1116 | version = "0.3.61" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" 1119 | dependencies = [ 1120 | "js-sys", 1121 | "wasm-bindgen", 1122 | ] 1123 | 1124 | [[package]] 1125 | name = "webpki" 1126 | version = "0.22.0" 1127 | source = "registry+https://github.com/rust-lang/crates.io-index" 1128 | checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" 1129 | dependencies = [ 1130 | "ring", 1131 | "untrusted", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "webpki-roots" 1136 | version = "0.22.6" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" 1139 | dependencies = [ 1140 | "webpki", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "whoami" 1145 | version = "1.4.0" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68" 1148 | dependencies = [ 1149 | "wasm-bindgen", 1150 | "web-sys", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "winapi" 1155 | version = "0.3.9" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1158 | dependencies = [ 1159 | "winapi-i686-pc-windows-gnu", 1160 | "winapi-x86_64-pc-windows-gnu", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "winapi-i686-pc-windows-gnu" 1165 | version = "0.4.0" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1168 | 1169 | [[package]] 1170 | name = "winapi-x86_64-pc-windows-gnu" 1171 | version = "0.4.0" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1174 | 1175 | [[package]] 1176 | name = "windows-sys" 1177 | version = "0.45.0" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1180 | dependencies = [ 1181 | "windows-targets", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "windows-targets" 1186 | version = "0.42.2" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1189 | dependencies = [ 1190 | "windows_aarch64_gnullvm", 1191 | "windows_aarch64_msvc", 1192 | "windows_i686_gnu", 1193 | "windows_i686_msvc", 1194 | "windows_x86_64_gnu", 1195 | "windows_x86_64_gnullvm", 1196 | "windows_x86_64_msvc", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "windows_aarch64_gnullvm" 1201 | version = "0.42.2" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1204 | 1205 | [[package]] 1206 | name = "windows_aarch64_msvc" 1207 | version = "0.42.2" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1210 | 1211 | [[package]] 1212 | name = "windows_i686_gnu" 1213 | version = "0.42.2" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1216 | 1217 | [[package]] 1218 | name = "windows_i686_msvc" 1219 | version = "0.42.2" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1222 | 1223 | [[package]] 1224 | name = "windows_x86_64_gnu" 1225 | version = "0.42.2" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1228 | 1229 | [[package]] 1230 | name = "windows_x86_64_gnullvm" 1231 | version = "0.42.2" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1234 | 1235 | [[package]] 1236 | name = "windows_x86_64_msvc" 1237 | version = "0.42.2" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1240 | --------------------------------------------------------------------------------