├── .dockerignore ├── buckpal-application ├── src │ ├── application │ │ ├── mod.rs │ │ ├── port │ │ │ ├── mod.rs │ │ │ ├── incoming │ │ │ │ ├── mod.rs │ │ │ │ ├── get_account_balance_query.rs │ │ │ │ └── send_money_use_case.rs │ │ │ └── outgoing │ │ │ │ ├── mod.rs │ │ │ │ ├── account_lock.rs │ │ │ │ ├── update_account_state_port.rs │ │ │ │ └── load_account_port.rs │ │ └── service │ │ │ ├── mod.rs │ │ │ ├── money_transfer_properties.rs │ │ │ ├── error.rs │ │ │ ├── no_op_account_lock.rs │ │ │ ├── get_account_balance_service.rs │ │ │ └── send_money_service.rs │ ├── domain │ │ ├── mod.rs │ │ ├── activity.rs │ │ ├── activity_window.rs │ │ └── account.rs │ └── lib.rs └── Cargo.toml ├── migrations ├── 20200715224355_create_account.sql.sql └── 20200715224357_create_activity.sql.sql ├── Cargo.toml ├── adapters ├── buckpal-persistence │ ├── src │ │ ├── lib.rs │ │ ├── account_entity.rs │ │ ├── activity_entity.rs │ │ ├── account_repository.rs │ │ ├── account_mapper.rs │ │ ├── activity_repository.rs │ │ └── account_persistence_adapter.rs │ ├── Cargo.toml │ └── sqlx-data.json └── buckpal-web │ ├── Cargo.toml │ └── src │ ├── utils.rs │ └── main.rs ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── Dockerfile ├── README.md └── Cargo.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /buckpal-application/src/application/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod port; 2 | pub mod service; 3 | -------------------------------------------------------------------------------- /buckpal-application/src/application/port/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod incoming; 2 | pub mod outgoing; 3 | -------------------------------------------------------------------------------- /buckpal-application/src/domain/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod account; 2 | pub mod activity; 3 | pub mod activity_window; 4 | -------------------------------------------------------------------------------- /buckpal-application/src/application/port/incoming/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod get_account_balance_query; 2 | pub mod send_money_use_case; 3 | -------------------------------------------------------------------------------- /migrations/20200715224355_create_account.sql.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS account ( 2 | id SERIAL PRIMARY KEY 3 | ); 4 | -------------------------------------------------------------------------------- /buckpal-application/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(test, feature(proc_macro_hygiene))] 2 | 3 | pub mod application; 4 | pub mod domain; 5 | -------------------------------------------------------------------------------- /buckpal-application/src/application/port/outgoing/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod account_lock; 2 | pub mod load_account_port; 3 | pub mod update_account_state_port; 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "buckpal-application", 5 | "adapters/buckpal-persistence", 6 | "adapters/buckpal-web" 7 | ] 8 | -------------------------------------------------------------------------------- /adapters/buckpal-persistence/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod account_entity; 2 | mod account_mapper; 3 | pub mod account_persistence_adapter; 4 | mod account_repository; 5 | mod activity_entity; 6 | mod activity_repository; 7 | -------------------------------------------------------------------------------- /buckpal-application/src/application/service/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod get_account_balance_service; 3 | pub mod money_transfer_properties; 4 | pub mod no_op_account_lock; 5 | pub mod send_money_service; 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | **/target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | 8 | # Other 9 | .DS_Store 10 | .env 11 | checksum.txt 12 | tags 13 | .env 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "docker" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /adapters/buckpal-persistence/src/account_entity.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Eq, PartialEq, Clone)] 2 | pub struct AccountEntity { 3 | pub id: i32, 4 | } 5 | 6 | impl AccountEntity { 7 | #[allow(dead_code)] 8 | pub fn new(id: i32) -> Self { 9 | Self { id } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /buckpal-application/src/application/port/outgoing/account_lock.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::account::AccountId; 2 | 3 | #[cfg_attr(test, mockall::automock)] 4 | pub trait AccountLock { 5 | fn lock_account(&self, account_id: &AccountId); 6 | fn release_account(&self, account_id: &AccountId); 7 | } 8 | -------------------------------------------------------------------------------- /buckpal-application/src/application/port/incoming/get_account_balance_query.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::account::AccountId; 2 | use anyhow::Result; 3 | use async_trait::async_trait; 4 | use rusty_money::Money; 5 | 6 | #[async_trait] 7 | pub trait GetAccountBalanceQuery { 8 | async fn get_account_balance(&self, account_id: &AccountId) -> Result; 9 | } 10 | -------------------------------------------------------------------------------- /migrations/20200715224357_create_activity.sql.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS activity ( 2 | id SERIAL PRIMARY KEY, 3 | timestamp TIMESTAMPTZ NOT NULL, 4 | owner_account_id INT NOT NULL, 5 | source_account_id INT NOT NULL, 6 | target_account_id INT NOT NULL, 7 | amount BIGINT NOT NULL 8 | ); 9 | 10 | -------------------------------------------------------------------------------- /buckpal-application/src/application/port/outgoing/update_account_state_port.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::account::Account; 2 | use crate::domain::activity::Activity; 3 | use anyhow::Result; 4 | use async_trait::async_trait; 5 | 6 | #[async_trait] 7 | pub trait UpdateAccountStatePort { 8 | async fn update_activities(&self, account: &Account) -> Result>; 9 | } 10 | -------------------------------------------------------------------------------- /buckpal-application/src/application/service/money_transfer_properties.rs: -------------------------------------------------------------------------------- 1 | use rusty_money::{money, Money}; 2 | 3 | #[derive(Debug, Eq, PartialEq, Clone, Default)] 4 | pub struct MoneyTransferProperties {} 5 | 6 | impl MoneyTransferProperties { 7 | pub fn new() -> Self { 8 | Self {} 9 | } 10 | 11 | pub fn maximum_transfer_threshold(&self) -> Money { 12 | money!(1_000_000, "AUD") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /buckpal-application/src/application/port/outgoing/load_account_port.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::account::{Account, AccountId}; 2 | use anyhow::Result; 3 | use async_trait::async_trait; 4 | use chrono::{DateTime, Utc}; 5 | 6 | #[async_trait] 7 | pub trait LoadAccountPort { 8 | async fn load_account( 9 | &self, 10 | account_id: &AccountId, 11 | baseline_date: &DateTime, 12 | ) -> Result; 13 | } 14 | -------------------------------------------------------------------------------- /buckpal-application/src/application/service/error.rs: -------------------------------------------------------------------------------- 1 | use rusty_money::Money; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum ServiceError { 6 | #[error("Maximum threshold for transferring money exceeded: tried to transfer {threshold:?} but threshold is {actual:?}!")] 7 | ThresholdExceededException { threshold: Money, actual: Money }, 8 | #[error("May withdraw failed with the following balance: `{0}`")] 9 | MayWithdrawFailed(i64), 10 | } 11 | -------------------------------------------------------------------------------- /buckpal-application/src/application/service/no_op_account_lock.rs: -------------------------------------------------------------------------------- 1 | use crate::application::port::outgoing::account_lock::AccountLock; 2 | use crate::domain::account::AccountId; 3 | 4 | #[derive(Debug, Clone, Default)] 5 | pub struct NoOpAccountLock {} 6 | 7 | impl AccountLock for NoOpAccountLock { 8 | fn lock_account(&self, _account_id: &AccountId) { 9 | // do nothing 10 | } 11 | fn release_account(&self, _account_id: &AccountId) { 12 | // do nothing 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /buckpal-application/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "buckpal-application" 3 | version = "0.1.0" 4 | authors = ["Anthony Mittaz "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | chrono = "0.4.19" 11 | rusty-money = "0.3.6" 12 | thiserror = "1.0.23" 13 | anyhow = "1.0.38" 14 | cfg-if = "1.0.0" 15 | async-trait = "0.1.42" 16 | rust_decimal = "1.10.1" 17 | 18 | [dev-dependencies] 19 | mockall = "0.9.0" 20 | mocktopus = "0.7.11" 21 | async-std = { version = "1.8.0", features = ["attributes"] } 22 | 23 | [lib] 24 | doctest = false 25 | -------------------------------------------------------------------------------- /buckpal-application/src/application/port/incoming/send_money_use_case.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::account::AccountId; 2 | use anyhow::Result; 3 | use async_trait::async_trait; 4 | use rusty_money::Money; 5 | 6 | pub struct SendMoneyCommand { 7 | pub source_account_id: AccountId, 8 | pub target_account_id: AccountId, 9 | pub money: Money, 10 | } 11 | 12 | impl SendMoneyCommand { 13 | pub fn new(source_account_id: AccountId, target_account_id: AccountId, money: Money) -> Self { 14 | Self { 15 | source_account_id, 16 | target_account_id, 17 | money, 18 | } 19 | } 20 | } 21 | 22 | #[async_trait] 23 | pub trait SendMoneyUseCase { 24 | async fn send_money(&self, command: &SendMoneyCommand) -> Result<()>; 25 | } 26 | -------------------------------------------------------------------------------- /adapters/buckpal-persistence/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "buckpal-persistence" 3 | version = "0.1.0" 4 | authors = ["Anthony Mittaz "] 5 | edition = "2018" 6 | exclude = ["test/*"] 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | sqlx = { git = "https://github.com/launchbadge/sqlx", features = ["postgres", "chrono", "offline", "bigdecimal"] } 12 | chrono = "0.4.19" 13 | anyhow = "1.0.38" 14 | thiserror = "1.0.23" 15 | rusty-money = "0.3.6" 16 | rust_decimal = "1.10.1" 17 | bigdecimal = "0.2.0" 18 | async-trait = "0.1.42" 19 | buckpal-application = { path = "../../buckpal-application" } 20 | 21 | [dev-dependencies] 22 | async-std = { version = "1.8.0", features = ["attributes"] } 23 | 24 | [lib] 25 | doctest = false 26 | -------------------------------------------------------------------------------- /adapters/buckpal-web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "buckpal-web" 3 | version = "0.1.0" 4 | authors = ["Anthony Mittaz "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | sqlx = { git = "https://github.com/launchbadge/sqlx", features = ["postgres", "chrono", "offline", "bigdecimal"] } 11 | dotenv = "0.15.0" 12 | env_logger = "0.8.2" 13 | log = "0.4.13" 14 | anyhow = "1.0.38" 15 | thiserror = "1.0.23" 16 | buckpal-application = { path = "../../buckpal-application" } 17 | buckpal-persistence = { path = "../buckpal-persistence" } 18 | tide = "0.13.0" 19 | rusty-money = "0.3.6" 20 | serde = { version = "1.0.120", features = ["derive"] } 21 | serde_json = "1.0.61" 22 | async-std = { version = "1.8.0", features = ["attributes"] } 23 | -------------------------------------------------------------------------------- /adapters/buckpal-persistence/src/activity_entity.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | 3 | #[derive(Debug, Eq, PartialEq, Clone)] 4 | pub struct ActivityEntity { 5 | pub id: Option, 6 | pub timestamp: DateTime, 7 | pub owner_account_id: i32, 8 | pub source_account_id: i32, 9 | pub target_account_id: i32, 10 | pub amount: i64, 11 | } 12 | 13 | impl ActivityEntity { 14 | pub fn new( 15 | id: Option, 16 | timestamp: DateTime, 17 | owner_account_id: i32, 18 | source_account_id: i32, 19 | target_account_id: i32, 20 | amount: i64, 21 | ) -> Self { 22 | Self { 23 | id, 24 | timestamp, 25 | owner_account_id, 26 | source_account_id, 27 | target_account_id, 28 | amount, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /adapters/buckpal-persistence/src/account_repository.rs: -------------------------------------------------------------------------------- 1 | use crate::account_entity::AccountEntity; 2 | use anyhow::Result; 3 | use sqlx::postgres::PgPool; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct AccountRepository { 7 | pool: PgPool, 8 | } 9 | 10 | impl AccountRepository { 11 | pub fn new(pool: PgPool) -> Self { 12 | Self { pool } 13 | } 14 | 15 | pub async fn find_by_id(&self, account_id: i32) -> Result { 16 | let entity = sqlx::query_as!( 17 | AccountEntity, 18 | r#" 19 | SELECT 20 | id 21 | FROM 22 | account 23 | WHERE 24 | id = $1 25 | "#, 26 | account_id 27 | ) 28 | .fetch_one(&self.pool) 29 | .await?; 30 | 31 | Ok(entity) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /buckpal-application/src/application/service/get_account_balance_service.rs: -------------------------------------------------------------------------------- 1 | use crate::application::port::incoming::get_account_balance_query::GetAccountBalanceQuery; 2 | use crate::application::port::outgoing::load_account_port::LoadAccountPort; 3 | use crate::domain::account::AccountId; 4 | use anyhow::Result; 5 | use async_trait::async_trait; 6 | use rusty_money::Money; 7 | 8 | pub struct GetAccountBalanceService { 9 | load_account_port: Box, 10 | } 11 | 12 | #[async_trait] 13 | impl GetAccountBalanceQuery for GetAccountBalanceService { 14 | async fn get_account_balance(&self, account_id: &AccountId) -> Result { 15 | use chrono::Utc; 16 | 17 | let account = self 18 | .load_account_port 19 | .load_account(account_id, &Utc::now()) 20 | .await?; 21 | 22 | Ok(account.calculate_balance()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /adapters/buckpal-web/src/utils.rs: -------------------------------------------------------------------------------- 1 | use tide::{Body, Error, Response, StatusCode}; 2 | 3 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 4 | struct SendMoneyResponse { 5 | message: String, 6 | } 7 | 8 | pub fn success_to_res(message: &str) -> tide::Result { 9 | let send_money_response = SendMoneyResponse { 10 | message: String::from(message), 11 | }; 12 | 13 | let mut res = Response::new(StatusCode::Ok); 14 | res.set_body(Body::from_json(&send_money_response)?); 15 | 16 | Ok(res) 17 | } 18 | 19 | #[allow(dead_code)] 20 | pub fn err_to_res(err: Error) -> tide::Result { 21 | let send_money_response = SendMoneyResponse { 22 | message: format!("Unable to process request: {}", err.to_string()), 23 | }; 24 | 25 | let mut res = Response::new(err.status()); 26 | res.set_body(Body::from_json(&send_money_response)?); 27 | 28 | Ok(res) 29 | } 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # -*- mode: dockerfile -*- 2 | # 3 | # An example Dockerfile showing how to build a Rust executable using this 4 | # image, and deploy it with a tiny Alpine Linux container. 5 | 6 | # You can override this `--build-arg BASE_IMAGE=...` to use different 7 | # version of Rust or OpenSSL. 8 | ARG BASE_IMAGE=ekidd/rust-musl-builder:latest 9 | 10 | # Our first FROM statement declares the build environment. 11 | FROM ${BASE_IMAGE} AS builder 12 | 13 | # Add our source code. 14 | ADD --chown=rust:rust . ./ 15 | 16 | # Build our application. 17 | RUN cargo build --release 18 | 19 | # Now, we need to build our _real_ Docker container, copying in `buckpal-web`. 20 | FROM alpine:latest 21 | 22 | RUN apk --no-cache add ca-certificates 23 | 24 | # Server binary 25 | COPY --from=builder \ 26 | /home/rust/src/target/x86_64-unknown-linux-musl/release/buckpal-web \ 27 | /usr/local/bin/ 28 | 29 | CMD /usr/local/bin/buckpal-web 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example Implementation of a Hexagonal Architecture in Rust 2 | 3 | This is the companion code to the eBook [Get Your Hands Dirty on Clean Architecture](https://leanpub.com/get-your-hands-dirty-on-clean-architecture). 4 | 5 | It implements a domain-centric "Hexagonal" approach of a common web application with Rust. 6 | 7 | ## Companion Articles 8 | 9 | * [Hexagonal Architecture with Java and Spring](https://reflectoring.io/spring-hexagonal/) 10 | * [Building a Multi-Module Spring Boot Application with Gradle](https://reflectoring.io/spring-boot-gradle-multi-module/) 11 | 12 | ## Prerequisites 13 | 14 | * Rust Nightly (needed for our test mocks on Struct functions) 15 | 16 | ## Rust nightly 17 | 18 | ```sh 19 | rustup toolchain install nightly 20 | 21 | cd buckpal; 22 | rustup override set nightly 23 | ``` 24 | 25 | ## Postgres 26 | 27 | ```sh 28 | # run docker locally (only have to do this once) 29 | brew install postgres 30 | brew services start postgres 31 | ``` 32 | ## Postgres Migrations 33 | 34 | ```sh 35 | cargo install sqlx-cli --git https://github.com/launchbadge/sqlx.git --no-default-features --features postgres 36 | 37 | # make sure database is created then 38 | sqlx migrate run 39 | 40 | # to create a new migration 41 | sqlx migrate add your_migration_name 42 | sqlx migrate run 43 | 44 | #also create test database 45 | createdb buckpal_test 46 | DATABASE_URL=postgres://localhost/buckpal_test sqlx migrate run 47 | ``` 48 | -------------------------------------------------------------------------------- /adapters/buckpal-persistence/src/account_mapper.rs: -------------------------------------------------------------------------------- 1 | use crate::account_entity::AccountEntity; 2 | use crate::activity_entity::ActivityEntity; 3 | use buckpal_application::domain::account::{Account, AccountId}; 4 | use buckpal_application::domain::activity::{Activity, ActivityId}; 5 | use buckpal_application::domain::activity_window::ActivityWindow; 6 | use rusty_money::{money, Money}; 7 | 8 | #[derive(Debug, Eq, PartialEq, Clone, Default)] 9 | pub struct AccountMapper {} 10 | 11 | // Note: we don't use & here in some places because we don't need it 12 | 13 | impl AccountMapper { 14 | pub fn map_to_domain_entity( 15 | &self, 16 | account: AccountEntity, 17 | activities: Vec, 18 | withdrawal_blance: i64, 19 | deposit_balance: i64, 20 | ) -> Account { 21 | let baseline_balance = money!(deposit_balance, "AUD") - money!(withdrawal_blance, "AUD"); 22 | 23 | Account::new_with_id( 24 | AccountId(account.id), 25 | baseline_balance, 26 | self.map_to_activity_window(activities), 27 | ) 28 | } 29 | 30 | pub fn map_to_activity(&self, activity: &ActivityEntity) -> Activity { 31 | Activity::new_with_id( 32 | activity.id.map(ActivityId), 33 | AccountId(activity.owner_account_id), 34 | AccountId(activity.source_account_id), 35 | AccountId(activity.target_account_id), 36 | activity.timestamp, 37 | money!(activity.amount, "AUD"), 38 | ) 39 | } 40 | 41 | pub fn map_to_activity_window(&self, activities: Vec) -> ActivityWindow { 42 | let mapped_activities: Vec = activities 43 | .iter() 44 | .map(|activity: &ActivityEntity| self.map_to_activity(activity)) 45 | .collect(); 46 | 47 | ActivityWindow::new(mapped_activities) 48 | } 49 | 50 | pub fn map_to_entity(&self, activity: Activity) -> ActivityEntity { 51 | use rust_decimal::prelude::*; 52 | 53 | ActivityEntity::new( 54 | activity.id.map(|id| id.0), 55 | activity.timestamp, 56 | activity.owner_account_id.0, 57 | activity.source_account_id.0, 58 | activity.target_account_id.0, 59 | // here we want to explode, no way to recover 60 | activity.money.amount().to_i64().unwrap(), 61 | ) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Test & checks 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | 8 | env: 9 | DATABASE_URL: postgres://buckpal:buckpal@localhost/buckpal_test 10 | 11 | jobs: 12 | ci: 13 | name: Test & checks 14 | runs-on: ubuntu-latest 15 | 16 | services: 17 | postgres: 18 | image: postgres 19 | env: 20 | POSTGRES_USER: buckpal 21 | POSTGRES_PASSWORD: buckpal 22 | options: >- 23 | --health-cmd pg_isready 24 | --health-interval 10s 25 | --health-timeout 5s 26 | --health-retries 5 27 | ports: 28 | - 5432:5432 29 | 30 | steps: 31 | - uses: actions/checkout@v2 32 | 33 | - id: rust-toolchain 34 | uses: actions-rs/toolchain@v1 35 | with: 36 | profile: minimal 37 | toolchain: nightly 38 | components: rustfmt, clippy 39 | override: true 40 | 41 | - name: Setup postgres and sqlx 42 | run: | 43 | sudo apt-get update 44 | sudo apt-get -y install libpq-dev 45 | cargo install sqlx-cli --git https://github.com/launchbadge/sqlx.git --no-default-features --features postgres 46 | 47 | - name: Create test db 48 | run: createdb -h localhost -U buckpal buckpal_test 49 | env: 50 | PGUSER: buckpal 51 | PGPASSWORD: buckpal 52 | 53 | - name: Migrate test db 54 | run: sqlx migrate run 55 | 56 | - name: Cache cargo registry 57 | uses: actions/cache@v1 58 | with: 59 | path: ~/.cargo/registry 60 | key: ${{ runner.os }}-cargo-registry-${{ steps.rust-toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }} 61 | restore-keys: | 62 | ${{ runner.os }}-cargo-registry-${{ steps.rust-toolchain.outputs.rustc_hash }}- 63 | ${{ runner.os }}-cargo-registry- 64 | 65 | - name: Cache cargo index 66 | uses: actions/cache@v1 67 | with: 68 | path: ~/.cargo/git 69 | key: ${{ runner.os }}-cargo-index-${{ steps.rust-toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }} 70 | restore-keys: | 71 | ${{ runner.os }}-cargo-index-${{ steps.rust-toolchain.outputs.rustc_hash }}- 72 | ${{ runner.os }}-cargo-index- 73 | 74 | - name: Cache cargo build 75 | uses: actions/cache@v1 76 | with: 77 | path: target 78 | key: ${{ runner.os }}-cargo-build-target-${{ steps.rust-toolchain.outputs.rustc_hash }}-${{ hashFiles('**/Cargo.lock') }} 79 | restore-keys: | 80 | ${{ runner.os }}-cargo-build-target-${{ steps.rust-toolchain.outputs.rustc_hash }}- 81 | ${{ runner.os }}-cargo-build-target- 82 | 83 | - name: Build 84 | id: build 85 | uses: actions-rs/cargo@v1 86 | with: 87 | command: build 88 | 89 | - name: Run tests 90 | uses: actions-rs/cargo@v1 91 | if: steps.build.outcome == 'success' 92 | with: 93 | command: test 94 | 95 | - name: Check rustfmt 96 | uses: actions-rs/cargo@v1 97 | if: steps.build.outcome == 'success' 98 | with: 99 | command: fmt 100 | args: --all -- --check 101 | -------------------------------------------------------------------------------- /buckpal-application/src/domain/activity.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::account::AccountId; 2 | use chrono::{DateTime, Utc}; 3 | use rusty_money::Money; 4 | 5 | #[derive(Debug, Eq, PartialEq, Clone)] 6 | pub struct ActivityId(pub i32); 7 | 8 | /// A money transfer activity between Accounts 9 | #[derive(Debug, Eq, PartialEq, Clone)] 10 | pub struct Activity { 11 | pub id: Option, 12 | /// The account that owns this activity. 13 | pub owner_account_id: AccountId, 14 | /// The debited account. 15 | pub source_account_id: AccountId, 16 | /// The credited account. 17 | pub target_account_id: AccountId, 18 | /// The timestamp of the activity. 19 | pub timestamp: DateTime, 20 | /// The money that was transferred between the accounts. 21 | pub money: Money, 22 | } 23 | 24 | impl Activity { 25 | pub fn new( 26 | owner_account_id: AccountId, 27 | source_account_id: AccountId, 28 | target_account_id: AccountId, 29 | timestamp: DateTime, 30 | money: Money, 31 | ) -> Self { 32 | Self { 33 | id: None, 34 | owner_account_id, 35 | source_account_id, 36 | target_account_id, 37 | timestamp, 38 | money, 39 | } 40 | } 41 | 42 | pub fn new_with_id( 43 | activity_id: Option, 44 | owner_account_id: AccountId, 45 | source_account_id: AccountId, 46 | target_account_id: AccountId, 47 | timestamp: DateTime, 48 | money: Money, 49 | ) -> Self { 50 | Self { 51 | id: activity_id, 52 | owner_account_id, 53 | source_account_id, 54 | target_account_id, 55 | timestamp, 56 | money, 57 | } 58 | } 59 | } 60 | 61 | pub mod activity_test_data { 62 | use super::{AccountId, Activity}; 63 | use chrono::{DateTime, Utc}; 64 | use rusty_money::{money, Money}; 65 | 66 | pub struct ActivityBuilder { 67 | activity: Activity, 68 | } 69 | 70 | impl ActivityBuilder { 71 | pub fn default_activity() -> Self { 72 | let activity = Activity::new( 73 | AccountId(42), 74 | AccountId(42), 75 | AccountId(41), 76 | Utc::now(), 77 | money!(999, "AUD"), 78 | ); 79 | 80 | Self { activity } 81 | } 82 | 83 | pub fn with_timestamp(&mut self, timestamp: &DateTime) -> &mut Self { 84 | let mut activity = self.activity.clone(); 85 | activity.timestamp = *timestamp; 86 | 87 | let mut new = self; 88 | new.activity = activity; 89 | new 90 | } 91 | 92 | pub fn with_source_account(&mut self, source_account_id: &AccountId) -> &mut Self { 93 | let mut activity = self.activity.clone(); 94 | activity.source_account_id = source_account_id.clone(); 95 | 96 | let mut new = self; 97 | new.activity = activity; 98 | new 99 | } 100 | 101 | pub fn with_target_account(&mut self, target_account_id: &AccountId) -> &mut Self { 102 | let mut activity = self.activity.clone(); 103 | activity.target_account_id = target_account_id.clone(); 104 | 105 | let mut new = self; 106 | new.activity = activity; 107 | new 108 | } 109 | 110 | pub fn with_money(&mut self, money: &Money) -> &mut Self { 111 | let mut activity = self.activity.clone(); 112 | activity.money = money.clone(); 113 | 114 | let mut new = self; 115 | new.activity = activity; 116 | new 117 | } 118 | 119 | pub fn build(&self) -> Activity { 120 | self.activity.clone() 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /adapters/buckpal-web/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | mod utils; 5 | 6 | use crate::utils::success_to_res; 7 | use anyhow::Result; 8 | use buckpal_application::application::port::incoming::send_money_use_case::{ 9 | SendMoneyCommand, SendMoneyUseCase, 10 | }; 11 | use buckpal_application::application::service::{ 12 | money_transfer_properties::MoneyTransferProperties, no_op_account_lock::NoOpAccountLock, 13 | send_money_service::SendMoneyService, 14 | }; 15 | use buckpal_application::domain::account::AccountId; 16 | use buckpal_persistence::account_persistence_adapter::AccountPersistenceAdapter; 17 | use rusty_money::{money, Money}; 18 | use sqlx::postgres::PgPoolOptions; 19 | use std::env; 20 | use std::sync::Arc; 21 | use tide::{security::CorsMiddleware, Error, ParamError, Request, Response, Server, StatusCode}; 22 | 23 | #[derive(Clone)] 24 | struct AppState { 25 | send_money_use_case: Arc, 26 | } 27 | 28 | impl AppState { 29 | fn new(send_money_use_case: Arc) -> Self { 30 | Self { 31 | send_money_use_case, 32 | } 33 | } 34 | } 35 | 36 | fn validate_accounts_send_params(req: &Request) -> tide::Result<(i32, i32, i64)> { 37 | let source_account_id: i32 = 38 | req.param("sourceAccountId") 39 | .map_err(|err: ParamError| { 40 | Error::from_str( 41 | StatusCode::UnprocessableEntity, 42 | format!("Invalid sourceAccountId: {}", err.to_string()), 43 | ) 44 | })?; 45 | 46 | let target_account_id: i32 = 47 | req.param("targetAccountId") 48 | .map_err(|err: ParamError| { 49 | Error::from_str( 50 | StatusCode::UnprocessableEntity, 51 | format!("Invalid targetAccountId: {}", err.to_string()), 52 | ) 53 | })?; 54 | 55 | let amount: i64 = req 56 | .param("amount") 57 | .map_err(|err: ParamError| { 58 | Error::from_str( 59 | StatusCode::UnprocessableEntity, 60 | format!("Invalid amount: {}", err.to_string()), 61 | ) 62 | })?; 63 | 64 | Ok((source_account_id, target_account_id, amount)) 65 | } 66 | 67 | async fn handle_accounts_send(req: Request) -> tide::Result { 68 | let (source_account_id, target_account_id, amount) = validate_accounts_send_params(&req)?; 69 | 70 | let command = SendMoneyCommand::new( 71 | AccountId(source_account_id), 72 | AccountId(target_account_id), 73 | money!(amount, "AUD"), 74 | ); 75 | 76 | let send_money_use_case = req.state().send_money_use_case.clone(); 77 | 78 | send_money_use_case 79 | .send_money(&command) 80 | .await 81 | .map_err(|err| Error::from_str(StatusCode::BadRequest, err.to_string()))?; 82 | 83 | success_to_res("Money Sent!") 84 | } 85 | 86 | #[async_std::main] 87 | async fn main() -> Result<()> { 88 | dotenv::dotenv()?; 89 | env_logger::init(); 90 | 91 | let database_url = env::var("DATABASE_URL")?; 92 | let port = env::var("PORT").unwrap_or_else(|_| String::from("6000")); 93 | let listen_addr = format!("0.0.0.0:{}", port); 94 | 95 | let pool = PgPoolOptions::new() 96 | .max_connections(5) 97 | .connect(&database_url) 98 | .await?; 99 | 100 | let account_persistence_adapter = AccountPersistenceAdapter::new(pool); 101 | let no_op_account_lock = NoOpAccountLock::default(); 102 | let money_transfer_properties = MoneyTransferProperties::new(); 103 | let send_money_use_case = SendMoneyService::new( 104 | Box::new(account_persistence_adapter.clone()), 105 | Box::new(no_op_account_lock), 106 | Box::new(account_persistence_adapter), 107 | money_transfer_properties, 108 | ); 109 | 110 | let app_state = AppState::new(Arc::new(send_money_use_case)); 111 | 112 | let mut app = Server::with_state(app_state); 113 | 114 | let cors = CorsMiddleware::new(); 115 | 116 | app.with(cors); 117 | 118 | app.at("/accounts/send/:sourceAccountId/:targetAccountId/:amount") 119 | .post(handle_accounts_send); 120 | 121 | info!("Starting at: {}", listen_addr); 122 | 123 | app.listen(listen_addr).await?; 124 | 125 | Ok(()) 126 | } 127 | -------------------------------------------------------------------------------- /buckpal-application/src/domain/activity_window.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::account::AccountId; 2 | use crate::domain::activity::Activity; 3 | use chrono::{DateTime, Utc}; 4 | use rusty_money::{money, Money}; 5 | 6 | /// A window of account activities. 7 | #[derive(Debug, Eq, PartialEq, Clone)] 8 | pub struct ActivityWindow { 9 | /// The list of account activities within this window. 10 | pub activities: Vec, 11 | } 12 | 13 | impl ActivityWindow { 14 | pub fn new(activities: Vec) -> Self { 15 | Self { activities } 16 | } 17 | 18 | /// The timestamp of the first activity within this window. 19 | pub fn get_start_timestamp(&self) -> Option> { 20 | self.activities 21 | .clone() 22 | .into_iter() 23 | .map(|activity| activity.timestamp) 24 | .min() 25 | } 26 | 27 | /// The timestamp of the last activity within this window. 28 | pub fn get_end_timestamp(&self) -> Option> { 29 | self.activities 30 | .clone() 31 | .into_iter() 32 | .map(|activity| activity.timestamp) 33 | .max() 34 | } 35 | 36 | /// Calculates the balance by summing up the values of all activities within this window. 37 | pub fn calculate_balance(&self, account_id: &AccountId) -> Money { 38 | let deposit_balance = self 39 | .activities 40 | .clone() 41 | .into_iter() 42 | .filter_map(|activity| { 43 | if activity.target_account_id == *account_id { 44 | Some(activity.money) 45 | } else { 46 | None 47 | } 48 | }) 49 | .fold(money!(0, "AUD"), |acc, x| acc + x); 50 | 51 | let withdrawal_balance = self 52 | .activities 53 | .clone() 54 | .into_iter() 55 | .filter_map(|activity| { 56 | if activity.source_account_id == *account_id { 57 | Some(activity.money) 58 | } else { 59 | None 60 | } 61 | }) 62 | .fold(money!(0, "AUD"), |acc, x| acc + x); 63 | 64 | deposit_balance - withdrawal_balance 65 | } 66 | 67 | pub fn add_activity(&mut self, activity: &Activity) { 68 | self.activities.push(activity.clone()) 69 | } 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use super::{AccountId, ActivityWindow}; 75 | use crate::domain::activity::activity_test_data::ActivityBuilder; 76 | use chrono::{DateTime, NaiveDate, Utc}; 77 | use rusty_money::{money, Money}; 78 | 79 | fn start_date() -> DateTime { 80 | DateTime::::from_utc(NaiveDate::from_ymd(2019, 8, 3).and_hms(0, 0, 0), Utc) 81 | } 82 | 83 | fn in_between_date() -> DateTime { 84 | DateTime::::from_utc(NaiveDate::from_ymd(2019, 8, 4).and_hms(0, 0, 0), Utc) 85 | } 86 | 87 | fn end_date() -> DateTime { 88 | DateTime::::from_utc(NaiveDate::from_ymd(2019, 8, 5).and_hms(0, 0, 0), Utc) 89 | } 90 | 91 | #[test] 92 | fn calculates_start_timestamp() { 93 | let activity_now = ActivityBuilder::default_activity() 94 | .with_timestamp(&start_date()) 95 | .build(); 96 | 97 | let activity_between = ActivityBuilder::default_activity() 98 | .with_timestamp(&in_between_date()) 99 | .build(); 100 | 101 | let activity_tomorrow = ActivityBuilder::default_activity() 102 | .with_timestamp(&end_date()) 103 | .build(); 104 | 105 | let window = ActivityWindow::new(vec![activity_now, activity_between, activity_tomorrow]); 106 | 107 | assert_eq!(window.get_start_timestamp().unwrap(), start_date()); 108 | } 109 | 110 | #[test] 111 | fn calculates_end_timestamp() { 112 | let activity_now = ActivityBuilder::default_activity() 113 | .with_timestamp(&start_date()) 114 | .build(); 115 | 116 | let activity_between = ActivityBuilder::default_activity() 117 | .with_timestamp(&in_between_date()) 118 | .build(); 119 | 120 | let activity_tomorrow = ActivityBuilder::default_activity() 121 | .with_timestamp(&end_date()) 122 | .build(); 123 | 124 | let window = ActivityWindow::new(vec![activity_now, activity_between, activity_tomorrow]); 125 | 126 | assert_eq!(window.get_end_timestamp().unwrap(), end_date()); 127 | } 128 | 129 | #[test] 130 | fn calculates_balance() { 131 | let account1 = AccountId(1); 132 | let account2 = AccountId(2); 133 | 134 | let window = ActivityWindow::new(vec![ 135 | ActivityBuilder::default_activity() 136 | .with_source_account(&account1) 137 | .with_target_account(&account2) 138 | .with_money(&money!(999, "AUD")) 139 | .build(), 140 | ActivityBuilder::default_activity() 141 | .with_source_account(&account1) 142 | .with_target_account(&account2) 143 | .with_money(&money!(1, "AUD")) 144 | .build(), 145 | ActivityBuilder::default_activity() 146 | .with_source_account(&account2) 147 | .with_target_account(&account1) 148 | .with_money(&money!(500, "AUD")) 149 | .build(), 150 | ]); 151 | 152 | debug_assert_eq!(window.calculate_balance(&account1), money!(-500, "AUD")); 153 | debug_assert_eq!(window.calculate_balance(&account2), money!(500, "AUD")); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /adapters/buckpal-persistence/src/activity_repository.rs: -------------------------------------------------------------------------------- 1 | use crate::activity_entity::ActivityEntity; 2 | use anyhow::{anyhow, Result}; 3 | use chrono::{DateTime, Utc}; 4 | use sqlx::postgres::PgPool; 5 | use thiserror::Error; 6 | 7 | #[derive(Error, Debug)] 8 | pub enum ActivityRepositoryError { 9 | #[error("Something went wrong when summing amount")] 10 | SumQueryException, 11 | #[error("Activity already has an id `{0}`, skipping insert")] 12 | AlreadyHasAnIdException(i32), 13 | } 14 | 15 | #[derive(Debug, Clone)] 16 | pub struct ActivityRepository { 17 | pool: PgPool, 18 | } 19 | 20 | impl ActivityRepository { 21 | pub fn new(pool: PgPool) -> Self { 22 | Self { pool } 23 | } 24 | 25 | pub async fn save(&self, activity_entity: &ActivityEntity) -> Result { 26 | match activity_entity.id { 27 | Some(activity_id) => Err(anyhow!(ActivityRepositoryError::AlreadyHasAnIdException( 28 | activity_id 29 | ))), 30 | None => { 31 | let entity = sqlx::query!( 32 | r#" 33 | INSERT INTO 34 | activity (timestamp, owner_account_id, source_account_id, target_account_id, amount) 35 | VALUES 36 | ($1, $2, $3, $4, $5) 37 | RETURNING 38 | id, timestamp, owner_account_id, source_account_id, target_account_id, amount 39 | "#, 40 | activity_entity.timestamp, 41 | activity_entity.owner_account_id, 42 | activity_entity.source_account_id, 43 | activity_entity.target_account_id, 44 | activity_entity.amount 45 | ) 46 | .fetch_one(&self.pool) 47 | .await?; 48 | 49 | let entity = ActivityEntity::new( 50 | Some(entity.id), 51 | entity.timestamp, 52 | entity.owner_account_id, 53 | entity.source_account_id, 54 | entity.target_account_id, 55 | entity.amount, 56 | ); 57 | 58 | Ok(entity) 59 | } 60 | } 61 | } 62 | 63 | pub async fn find_by_owner_since( 64 | &self, 65 | owner_account_id: i32, 66 | since: &DateTime, 67 | ) -> Result> { 68 | let entitites = sqlx::query!( 69 | r#" 70 | SELECT 71 | id, 72 | timestamp, 73 | owner_account_id, 74 | source_account_id, 75 | target_account_id, 76 | amount 77 | FROM 78 | activity 79 | WHERE 80 | owner_account_id = $1 81 | AND 82 | timestamp >= $2 83 | "#, 84 | owner_account_id, 85 | *since 86 | ) 87 | .fetch_all(&self.pool) 88 | .await?; 89 | 90 | let entitites = entitites 91 | .into_iter() 92 | .map(|entity| { 93 | ActivityEntity::new( 94 | Some(entity.id), 95 | entity.timestamp, 96 | entity.owner_account_id, 97 | entity.source_account_id, 98 | entity.target_account_id, 99 | entity.amount, 100 | ) 101 | }) 102 | .collect(); 103 | 104 | Ok(entitites) 105 | } 106 | 107 | pub async fn get_deposit_balance_until( 108 | &self, 109 | account_id: i32, 110 | until: &DateTime, 111 | ) -> Result { 112 | let sum = sqlx::query!( 113 | r#" 114 | SELECT 115 | SUM (amount) AS total 116 | FROM 117 | activity 118 | WHERE 119 | target_account_id = $1 120 | AND 121 | owner_account_id = $1 122 | AND 123 | timestamp < $2 124 | "#, 125 | account_id, 126 | *until, 127 | ) 128 | .fetch_one(&self.pool) 129 | .await?; 130 | 131 | use bigdecimal::*; 132 | 133 | match sum.total.and_then(|total| total.to_i64()) { 134 | Some(sum) => Ok(sum), 135 | None => Err(anyhow!(ActivityRepositoryError::SumQueryException)), 136 | } 137 | } 138 | 139 | pub async fn get_withdrawal_balance_until( 140 | &self, 141 | account_id: i32, 142 | until: &DateTime, 143 | ) -> Result { 144 | let sum = sqlx::query!( 145 | r#" 146 | SELECT 147 | SUM (amount) AS total 148 | FROM 149 | activity 150 | WHERE 151 | source_account_id = $1 152 | AND 153 | owner_account_id = $1 154 | AND 155 | timestamp < $2 156 | "#, 157 | account_id, 158 | *until, 159 | ) 160 | .fetch_one(&self.pool) 161 | .await?; 162 | 163 | use bigdecimal::*; 164 | 165 | match sum.total.and_then(|total| total.to_i64()) { 166 | Some(sum) => Ok(sum), 167 | None => Err(anyhow!(ActivityRepositoryError::SumQueryException)), 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /adapters/buckpal-persistence/sqlx-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": "PostgreSQL", 3 | "2d3bbb77efa214ccfec79394fa8fe6fdbb7a41797e6fd14a219e6a548f2dc564": { 4 | "query": "\n DELETE FROM activity WHERE id = $1\n ", 5 | "describe": { 6 | "columns": [], 7 | "parameters": { 8 | "Left": [ 9 | "Int4" 10 | ] 11 | }, 12 | "nullable": [] 13 | } 14 | }, 15 | "4967797caaa627abae987cec72b908841bf986207ddb3ae9bcd8b18b376ebd17": { 16 | "query": "\n SELECT \n id,\n timestamp,\n owner_account_id,\n source_account_id,\n target_account_id,\n amount\n FROM \n activity\n WHERE \n owner_account_id = $1\n AND\n timestamp >= $2\n ", 17 | "describe": { 18 | "columns": [ 19 | { 20 | "ordinal": 0, 21 | "name": "id", 22 | "type_info": "Int4" 23 | }, 24 | { 25 | "ordinal": 1, 26 | "name": "timestamp", 27 | "type_info": "Timestamptz" 28 | }, 29 | { 30 | "ordinal": 2, 31 | "name": "owner_account_id", 32 | "type_info": "Int4" 33 | }, 34 | { 35 | "ordinal": 3, 36 | "name": "source_account_id", 37 | "type_info": "Int4" 38 | }, 39 | { 40 | "ordinal": 4, 41 | "name": "target_account_id", 42 | "type_info": "Int4" 43 | }, 44 | { 45 | "ordinal": 5, 46 | "name": "amount", 47 | "type_info": "Int8" 48 | } 49 | ], 50 | "parameters": { 51 | "Left": [ 52 | "Int4", 53 | "Timestamptz" 54 | ] 55 | }, 56 | "nullable": [ 57 | false, 58 | false, 59 | false, 60 | false, 61 | false, 62 | false 63 | ] 64 | } 65 | }, 66 | "69bebe625b3f88a9ffc23d82dd20f9884bffb9f81b9ab5d97d0d74ed84c14fe5": { 67 | "query": "\n SELECT\n SUM (amount) AS total\n FROM\n activity\n WHERE\n target_account_id = $1\n AND \n owner_account_id = $1\n AND \n timestamp < $2\n ", 68 | "describe": { 69 | "columns": [ 70 | { 71 | "ordinal": 0, 72 | "name": "total", 73 | "type_info": "Numeric" 74 | } 75 | ], 76 | "parameters": { 77 | "Left": [ 78 | "Int4", 79 | "Timestamptz" 80 | ] 81 | }, 82 | "nullable": [ 83 | null 84 | ] 85 | } 86 | }, 87 | "9f3e43371fca6ebce6106f651b889c82dbf5e49d971050e14b71170b751402a4": { 88 | "query": "\n DELETE FROM account WHERE id = $1 \n ", 89 | "describe": { 90 | "columns": [], 91 | "parameters": { 92 | "Left": [ 93 | "Int4" 94 | ] 95 | }, 96 | "nullable": [] 97 | } 98 | }, 99 | "cae54719611a87631803d65128c7379fbd80843c08db932aced52957f31ea268": { 100 | "query": "\n INSERT INTO account DEFAULT VALUES RETURNING id \n ", 101 | "describe": { 102 | "columns": [ 103 | { 104 | "ordinal": 0, 105 | "name": "id", 106 | "type_info": "Int4" 107 | } 108 | ], 109 | "parameters": { 110 | "Left": [] 111 | }, 112 | "nullable": [ 113 | false 114 | ] 115 | } 116 | }, 117 | "dc77cfbbb1078774c26909355c8986b4baa3fa9f684e57202f921a254d200f33": { 118 | "query": "\n SELECT\n id \n FROM \n account\n WHERE \n id = $1\n ", 119 | "describe": { 120 | "columns": [ 121 | { 122 | "ordinal": 0, 123 | "name": "id", 124 | "type_info": "Int4" 125 | } 126 | ], 127 | "parameters": { 128 | "Left": [ 129 | "Int4" 130 | ] 131 | }, 132 | "nullable": [ 133 | false 134 | ] 135 | } 136 | }, 137 | "e6f6466188b9fefccf336142c387c04e90fa9701b3b075631005aec39619a0b6": { 138 | "query": "\n SELECT\n SUM (amount) AS total\n FROM\n activity\n WHERE\n source_account_id = $1\n AND \n owner_account_id = $1\n AND \n timestamp < $2\n ", 139 | "describe": { 140 | "columns": [ 141 | { 142 | "ordinal": 0, 143 | "name": "total", 144 | "type_info": "Numeric" 145 | } 146 | ], 147 | "parameters": { 148 | "Left": [ 149 | "Int4", 150 | "Timestamptz" 151 | ] 152 | }, 153 | "nullable": [ 154 | null 155 | ] 156 | } 157 | }, 158 | "e7fe4493b66f61ccb48943ed6569455e3c258c74b8f7a9fe16d96d4af502e07f": { 159 | "query": "\n INSERT INTO \n activity (timestamp, owner_account_id, source_account_id, target_account_id, amount)\n VALUES \n ($1, $2, $3, $4, $5)\n RETURNING \n id, timestamp, owner_account_id, source_account_id, target_account_id, amount \n ", 160 | "describe": { 161 | "columns": [ 162 | { 163 | "ordinal": 0, 164 | "name": "id", 165 | "type_info": "Int4" 166 | }, 167 | { 168 | "ordinal": 1, 169 | "name": "timestamp", 170 | "type_info": "Timestamptz" 171 | }, 172 | { 173 | "ordinal": 2, 174 | "name": "owner_account_id", 175 | "type_info": "Int4" 176 | }, 177 | { 178 | "ordinal": 3, 179 | "name": "source_account_id", 180 | "type_info": "Int4" 181 | }, 182 | { 183 | "ordinal": 4, 184 | "name": "target_account_id", 185 | "type_info": "Int4" 186 | }, 187 | { 188 | "ordinal": 5, 189 | "name": "amount", 190 | "type_info": "Int8" 191 | } 192 | ], 193 | "parameters": { 194 | "Left": [ 195 | "Timestamptz", 196 | "Int4", 197 | "Int4", 198 | "Int4", 199 | "Int8" 200 | ] 201 | }, 202 | "nullable": [ 203 | false, 204 | false, 205 | false, 206 | false, 207 | false, 208 | false 209 | ] 210 | } 211 | }, 212 | "fec96025fba091cb33f117ec514097bc2aba0f921d43c23ab95a9b043c56a47f": { 213 | "query": "\n SELECT \n id,\n timestamp,\n owner_account_id,\n source_account_id,\n target_account_id,\n amount\n FROM \n activity\n WHERE \n id = $1\n ", 214 | "describe": { 215 | "columns": [ 216 | { 217 | "ordinal": 0, 218 | "name": "id", 219 | "type_info": "Int4" 220 | }, 221 | { 222 | "ordinal": 1, 223 | "name": "timestamp", 224 | "type_info": "Timestamptz" 225 | }, 226 | { 227 | "ordinal": 2, 228 | "name": "owner_account_id", 229 | "type_info": "Int4" 230 | }, 231 | { 232 | "ordinal": 3, 233 | "name": "source_account_id", 234 | "type_info": "Int4" 235 | }, 236 | { 237 | "ordinal": 4, 238 | "name": "target_account_id", 239 | "type_info": "Int4" 240 | }, 241 | { 242 | "ordinal": 5, 243 | "name": "amount", 244 | "type_info": "Int8" 245 | } 246 | ], 247 | "parameters": { 248 | "Left": [ 249 | "Int4" 250 | ] 251 | }, 252 | "nullable": [ 253 | false, 254 | false, 255 | false, 256 | false, 257 | false, 258 | false 259 | ] 260 | } 261 | } 262 | } -------------------------------------------------------------------------------- /buckpal-application/src/domain/account.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::activity_window::ActivityWindow; 2 | use anyhow::{anyhow, Result}; 3 | use rusty_money::{money, Money}; 4 | use thiserror::Error; 5 | 6 | #[derive(Error, Debug)] 7 | pub enum AccountError { 8 | #[error("May withdraw failed with the following balance: `{0}`")] 9 | MayWithdrawFailed(i64), 10 | #[error("Account id is invalid, can't `{0}`")] 11 | InvalidAccountId(String), 12 | } 13 | 14 | #[derive(Debug, Eq, PartialEq, Clone)] 15 | pub struct AccountId(pub i32); 16 | 17 | #[derive(Debug, Eq, PartialEq, Clone)] 18 | pub struct Account { 19 | /// The unique ID of the account 20 | pub id: Option, 21 | /// The baseline balance of the account. This was the balance of the account before the first 22 | /// activity in the activityWindow. 23 | pub baseline_balance: Money, 24 | /// The window of latest activities on this account. 25 | pub activity_window: ActivityWindow, 26 | } 27 | 28 | #[cfg_attr(test, mocktopus::macros::mockable)] 29 | impl Account { 30 | /// Creates an Account entity without an ID. Use to create a new entity that is not yet 31 | /// persisted. 32 | pub fn new_without_id(baseline_balance: Money, activity_window: ActivityWindow) -> Self { 33 | Self { 34 | id: None, 35 | baseline_balance, 36 | activity_window, 37 | } 38 | } 39 | 40 | /// Creates an Account entity with an ID. Use to reconstitute a persisted entity. 41 | pub fn new_with_id( 42 | account_id: AccountId, 43 | baseline_balance: Money, 44 | activity_window: ActivityWindow, 45 | ) -> Self { 46 | Self { 47 | id: Some(account_id), 48 | baseline_balance, 49 | activity_window, 50 | } 51 | } 52 | 53 | /// Calculates the total balance of the account by adding the activity values to the baseline 54 | /// balance. 55 | pub fn calculate_balance(&self) -> Money { 56 | let window_balance = self.id.clone().map_or_else( 57 | || money!(0, "AUD"), 58 | |id| self.activity_window.calculate_balance(&id), 59 | ); 60 | self.baseline_balance.clone() + window_balance 61 | } 62 | 63 | /// Tries to withdraw a certain amount of money from this account. 64 | /// If successful, creates a new activity with a negative value. 65 | pub fn withdraw(&mut self, money: &Money, target_account_id: &AccountId) -> Result<()> { 66 | self.may_withdraw(&money)?; 67 | 68 | let id = match self.id.clone() { 69 | Some(id) => id, 70 | None => { 71 | return Err(anyhow!(AccountError::InvalidAccountId(String::from( 72 | "withdraw" 73 | )))) 74 | } 75 | }; 76 | 77 | use crate::domain::activity::Activity; 78 | use chrono::Utc; 79 | 80 | let withdrawal = Activity::new( 81 | id.clone(), 82 | id, 83 | target_account_id.clone(), 84 | Utc::now(), 85 | money.clone(), 86 | ); 87 | self.activity_window.add_activity(&withdrawal); 88 | Ok(()) 89 | } 90 | 91 | fn may_withdraw(&self, money: &Money) -> Result<()> { 92 | let balance = self.calculate_balance() - money.clone(); 93 | 94 | if balance.is_zero() || balance.is_positive() { 95 | Ok(()) 96 | } else { 97 | use rust_decimal::prelude::*; 98 | 99 | // here we want to explode, no way to recover 100 | Err(anyhow!(AccountError::MayWithdrawFailed( 101 | balance.amount().to_i64().unwrap() 102 | ))) 103 | } 104 | } 105 | 106 | /// Tries to deposit a certain amount of money to this account. 107 | /// If sucessful, creates a new activity with a positive value. 108 | /// return true if the deposit was successful, false if not. 109 | pub fn deposit(&mut self, money: &Money, source_account_id: &AccountId) -> Result<()> { 110 | let id = match self.id.clone() { 111 | Some(id) => id, 112 | None => { 113 | return Err(anyhow!(AccountError::InvalidAccountId(String::from( 114 | "withdraw" 115 | )))) 116 | } 117 | }; 118 | 119 | use crate::domain::activity::Activity; 120 | use chrono::Utc; 121 | 122 | let deposit = Activity::new( 123 | id.clone(), 124 | source_account_id.clone(), 125 | id, 126 | Utc::now(), 127 | money.clone(), 128 | ); 129 | self.activity_window.add_activity(&deposit); 130 | Ok(()) 131 | } 132 | } 133 | 134 | #[cfg(test)] 135 | mod tests { 136 | use super::account_test_data::AccountBuilder; 137 | use super::{AccountId, ActivityWindow}; 138 | use crate::domain::activity::activity_test_data::ActivityBuilder; 139 | use rusty_money::{money, Money}; 140 | 141 | #[test] 142 | fn calculates_balance() { 143 | let account_id = AccountId(1); 144 | let activity_window = ActivityWindow::new(vec![ 145 | ActivityBuilder::default_activity() 146 | .with_target_account(&account_id) 147 | .with_money(&money!(999, "AUD")) 148 | .build(), 149 | ActivityBuilder::default_activity() 150 | .with_target_account(&account_id) 151 | .with_money(&money!(1, "AUD")) 152 | .build(), 153 | ]); 154 | let account = AccountBuilder::default_account() 155 | .with_account_id(&account_id) 156 | .with_baseline_balance(&money!(555, "AUD")) 157 | .with_activity_window(&activity_window) 158 | .build(); 159 | 160 | let balance = account.calculate_balance(); 161 | 162 | assert_eq!(balance, money!(1555, "AUD")); 163 | } 164 | 165 | #[test] 166 | fn withdrawal_succeeds() { 167 | let account_id = AccountId(1); 168 | let activity_window = ActivityWindow::new(vec![ 169 | ActivityBuilder::default_activity() 170 | .with_target_account(&account_id) 171 | .with_money(&money!(999, "AUD")) 172 | .build(), 173 | ActivityBuilder::default_activity() 174 | .with_target_account(&account_id) 175 | .with_money(&money!(1, "AUD")) 176 | .build(), 177 | ]); 178 | let mut account = AccountBuilder::default_account() 179 | .with_account_id(&account_id.clone()) 180 | .with_baseline_balance(&money!(555, "AUD")) 181 | .with_activity_window(&activity_window) 182 | .build(); 183 | 184 | let success = account 185 | .withdraw(&money!(555, "AUD"), &AccountId(99)) 186 | .is_ok(); 187 | 188 | assert_eq!(success, true); 189 | assert_eq!(account.activity_window.activities.len(), 3); 190 | assert_eq!(account.calculate_balance(), money!(1000, "AUD")); 191 | } 192 | 193 | #[test] 194 | fn withdrawal_failure() { 195 | let account_id = AccountId(1); 196 | let activity_window = ActivityWindow::new(vec![ 197 | ActivityBuilder::default_activity() 198 | .with_target_account(&account_id) 199 | .with_money(&money!(999, "AUD")) 200 | .build(), 201 | ActivityBuilder::default_activity() 202 | .with_target_account(&account_id) 203 | .with_money(&money!(1, "AUD")) 204 | .build(), 205 | ]); 206 | let mut account = AccountBuilder::default_account() 207 | .with_account_id(&account_id) 208 | .with_baseline_balance(&money!(555, "AUD")) 209 | .with_activity_window(&activity_window) 210 | .build(); 211 | 212 | let success = account 213 | .withdraw(&money!(1556, "AUD"), &AccountId(99)) 214 | .is_ok(); 215 | 216 | assert_eq!(success, false); 217 | assert_eq!(account.activity_window.activities.len(), 2); 218 | assert_eq!(account.calculate_balance(), money!(1555, "AUD")); 219 | } 220 | 221 | #[test] 222 | fn deposit_succeeds() { 223 | let account_id = AccountId(1); 224 | let activity_window = ActivityWindow::new(vec![ 225 | ActivityBuilder::default_activity() 226 | .with_target_account(&account_id) 227 | .with_money(&money!(999, "AUD")) 228 | .build(), 229 | ActivityBuilder::default_activity() 230 | .with_target_account(&account_id) 231 | .with_money(&money!(1, "AUD")) 232 | .build(), 233 | ]); 234 | let mut account = AccountBuilder::default_account() 235 | .with_account_id(&account_id.clone()) 236 | .with_baseline_balance(&money!(555, "AUD")) 237 | .with_activity_window(&activity_window) 238 | .build(); 239 | 240 | let success = account.deposit(&money!(445, "AUD"), &AccountId(99)).is_ok(); 241 | 242 | assert_eq!(success, true); 243 | assert_eq!(account.activity_window.activities.len(), 3); 244 | assert_eq!(account.calculate_balance(), money!(2000, "AUD")); 245 | } 246 | } 247 | 248 | pub mod account_test_data { 249 | use super::{Account, AccountId}; 250 | use crate::domain::activity::activity_test_data::ActivityBuilder; 251 | use crate::domain::activity_window::ActivityWindow; 252 | use rusty_money::{money, Money}; 253 | 254 | pub struct AccountBuilder { 255 | account: Account, 256 | } 257 | 258 | impl AccountBuilder { 259 | pub fn default_account() -> Self { 260 | let activity_window = ActivityWindow::new(vec![ 261 | ActivityBuilder::default_activity().build(), 262 | ActivityBuilder::default_activity().build(), 263 | ]); 264 | let account = Account::new_with_id(AccountId(42), money!(999, "AUD"), activity_window); 265 | 266 | Self { account } 267 | } 268 | 269 | pub fn with_account_id(&mut self, account_id: &AccountId) -> &mut Self { 270 | let mut account = self.account.clone(); 271 | account.id = Some(account_id.clone()); 272 | 273 | let mut new = self; 274 | new.account = account; 275 | new 276 | } 277 | 278 | pub fn with_baseline_balance(&mut self, baseline_balance: &Money) -> &mut Self { 279 | let mut account = self.account.clone(); 280 | account.baseline_balance = baseline_balance.clone(); 281 | 282 | let mut new = self; 283 | new.account = account; 284 | new 285 | } 286 | 287 | pub fn with_activity_window(&mut self, activity_window: &ActivityWindow) -> &mut Self { 288 | let mut account = self.account.clone(); 289 | account.activity_window = activity_window.clone(); 290 | 291 | let mut new = self; 292 | new.account = account; 293 | new 294 | } 295 | 296 | pub fn build(&self) -> Account { 297 | self.account.clone() 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /adapters/buckpal-persistence/src/account_persistence_adapter.rs: -------------------------------------------------------------------------------- 1 | use crate::account_mapper::AccountMapper; 2 | use crate::account_repository::AccountRepository; 3 | use crate::activity_repository::ActivityRepository; 4 | use anyhow::Result; 5 | use async_trait::async_trait; 6 | use buckpal_application::application::port::outgoing::{ 7 | load_account_port::LoadAccountPort, update_account_state_port::UpdateAccountStatePort, 8 | }; 9 | use buckpal_application::domain::account::{Account, AccountId}; 10 | use buckpal_application::domain::activity::Activity; 11 | use chrono::{DateTime, Utc}; 12 | use sqlx::postgres::PgPool; 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct AccountPersistenceAdapter { 16 | account_repository: AccountRepository, 17 | activity_repository: ActivityRepository, 18 | account_mapper: AccountMapper, 19 | } 20 | 21 | impl AccountPersistenceAdapter { 22 | pub fn new(pool: PgPool) -> Self { 23 | Self { 24 | account_repository: AccountRepository::new(pool.clone()), 25 | activity_repository: ActivityRepository::new(pool), 26 | account_mapper: AccountMapper::default(), 27 | } 28 | } 29 | } 30 | 31 | #[async_trait] 32 | impl LoadAccountPort for AccountPersistenceAdapter { 33 | async fn load_account( 34 | &self, 35 | account_id: &AccountId, 36 | baseline_date: &DateTime, 37 | ) -> Result { 38 | let account_entity = self.account_repository.find_by_id(account_id.0).await?; 39 | 40 | let activities = self 41 | .activity_repository 42 | .find_by_owner_since(account_id.0, baseline_date) 43 | .await?; 44 | 45 | let withdrawal_balance = self 46 | .activity_repository 47 | .get_withdrawal_balance_until(account_id.0, baseline_date) 48 | .await 49 | .unwrap_or(0); 50 | 51 | let deposit_balance = self 52 | .activity_repository 53 | .get_deposit_balance_until(account_id.0, baseline_date) 54 | .await 55 | .unwrap_or(0); 56 | 57 | let account = self.account_mapper.map_to_domain_entity( 58 | account_entity, 59 | activities, 60 | withdrawal_balance, 61 | deposit_balance, 62 | ); 63 | 64 | Ok(account) 65 | } 66 | } 67 | 68 | #[async_trait] 69 | impl UpdateAccountStatePort for AccountPersistenceAdapter { 70 | async fn update_activities(&self, account: &Account) -> Result> { 71 | let mut activities: Vec = vec![]; 72 | for activity in account.clone().activity_window.activities { 73 | if activity.id.is_none() { 74 | let activity_entity = self 75 | .activity_repository 76 | .save(&self.account_mapper.map_to_entity(activity)) 77 | .await?; 78 | activities.push(self.account_mapper.map_to_activity(&activity_entity)); 79 | } 80 | } 81 | 82 | Ok(activities) 83 | } 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use super::AccountPersistenceAdapter; 89 | use crate::activity_entity::ActivityEntity; 90 | use crate::activity_repository::ActivityRepository; 91 | use anyhow::Result; 92 | use buckpal_application::application::port::outgoing::load_account_port::LoadAccountPort; 93 | use buckpal_application::domain::account::AccountId; 94 | use chrono::{DateTime, NaiveDate, Utc}; 95 | use rusty_money::{money, Money}; 96 | use sqlx::postgres::{PgPool, PgPoolOptions}; 97 | 98 | #[async_std::test] 99 | async fn load_account() -> Result<()> { 100 | let database_url = std::env::var("DATABASE_URL") 101 | .unwrap_or(String::from("postgres://localhost/buckpal_test")); 102 | 103 | let pool = PgPoolOptions::new() 104 | .max_connections(5) 105 | .connect(&database_url) 106 | .await 107 | .unwrap(); 108 | 109 | let date = 110 | DateTime::::from_utc(NaiveDate::from_ymd(2018, 8, 10).and_hms(0, 0, 0), Utc); 111 | 112 | // setup db 113 | let first_account_id = given_an_account(&pool).await.unwrap(); 114 | let second_account_id = given_an_account(&pool).await.unwrap(); 115 | 116 | let activity_ids = 117 | given_some_activites_for_account_ids(first_account_id, second_account_id, &pool) 118 | .await 119 | .unwrap(); 120 | // end setup db 121 | 122 | let account_id = AccountId(first_account_id); 123 | 124 | let adapter = AccountPersistenceAdapter::new(pool.clone()); 125 | 126 | let account = adapter.load_account(&account_id, &date).await.unwrap(); 127 | 128 | // cleanup db 129 | delete_account_with_id(first_account_id, &pool) 130 | .await 131 | .unwrap(); 132 | delete_account_with_id(second_account_id, &pool) 133 | .await 134 | .unwrap(); 135 | 136 | delete_activites_for_ids(activity_ids, &pool).await.unwrap(); 137 | // end cleanup db 138 | 139 | assert_eq!(account.id, Some(account_id)); 140 | assert_eq!(account.activity_window.activities.len(), 2); 141 | assert_eq!(account.calculate_balance(), money!(500, "AUD")); 142 | 143 | Ok(()) 144 | } 145 | 146 | #[async_std::test] 147 | async fn updates_activities() { 148 | use super::AccountPersistenceAdapter; 149 | use buckpal_application::application::port::outgoing::update_account_state_port::UpdateAccountStatePort; 150 | use buckpal_application::domain::account::account_test_data::AccountBuilder; 151 | use buckpal_application::domain::activity::activity_test_data::ActivityBuilder; 152 | use buckpal_application::domain::activity_window::ActivityWindow; 153 | 154 | let activity_window = ActivityWindow::new(vec![ActivityBuilder::default_activity() 155 | .with_money(&money!(1, "AUD")) 156 | .build()]); 157 | let account = AccountBuilder::default_account() 158 | .with_baseline_balance(&money!(555, "AUD")) 159 | .with_activity_window(&activity_window) 160 | .build(); 161 | 162 | let database_url = std::env::var("DATABASE_URL") 163 | .unwrap_or(String::from("postgres://localhost/buckpal_test")); 164 | 165 | let pool = PgPoolOptions::new() 166 | .max_connections(5) 167 | .connect(&database_url) 168 | .await 169 | .unwrap(); 170 | 171 | let adapter = AccountPersistenceAdapter::new(pool.clone()); 172 | let updated_activities = adapter.update_activities(&account).await.unwrap(); 173 | 174 | let activity_ids: Vec = updated_activities 175 | .iter() 176 | .map(|activity| { 177 | let cloned = activity.id.clone(); 178 | cloned.unwrap().0 179 | }) 180 | .collect(); 181 | 182 | let first_id = activity_ids.first().unwrap(); 183 | 184 | let saved_activity = find_activity(*first_id, &pool).await.unwrap(); 185 | 186 | delete_activites_for_ids(activity_ids, &pool).await.unwrap(); 187 | 188 | assert_eq!(updated_activities.len(), 1); 189 | assert_eq!(saved_activity.amount, 1); 190 | } 191 | 192 | async fn find_activity(activity_id: i32, pool: &PgPool) -> Result { 193 | let entity = sqlx::query!( 194 | r#" 195 | SELECT 196 | id, 197 | timestamp, 198 | owner_account_id, 199 | source_account_id, 200 | target_account_id, 201 | amount 202 | FROM 203 | activity 204 | WHERE 205 | id = $1 206 | "#, 207 | activity_id, 208 | ) 209 | .fetch_one(pool) 210 | .await?; 211 | 212 | let entity = ActivityEntity::new( 213 | Some(entity.id), 214 | entity.timestamp, 215 | entity.owner_account_id, 216 | entity.source_account_id, 217 | entity.target_account_id, 218 | entity.amount, 219 | ); 220 | 221 | Ok(entity) 222 | } 223 | 224 | async fn given_an_account(pool: &PgPool) -> Result { 225 | let entity = sqlx::query!( 226 | r#" 227 | INSERT INTO account DEFAULT VALUES RETURNING id 228 | "#, 229 | ) 230 | .fetch_one(pool) 231 | .await?; 232 | 233 | Ok(entity.id) 234 | } 235 | 236 | async fn delete_account_with_id(account_id: i32, pool: &PgPool) -> Result<()> { 237 | sqlx::query!( 238 | r#" 239 | DELETE FROM account WHERE id = $1 240 | "#, 241 | account_id, 242 | ) 243 | .execute(pool) 244 | .await?; 245 | 246 | Ok(()) 247 | } 248 | 249 | async fn given_some_activites_for_account_ids( 250 | first_account_id: i32, 251 | second_account_id: i32, 252 | pool: &PgPool, 253 | ) -> Result> { 254 | let activity_repostiory = ActivityRepository::new(pool.clone()); 255 | 256 | let first = activity_repostiory 257 | .save(&ActivityEntity::new( 258 | None, 259 | DateTime::::from_utc(NaiveDate::from_ymd(2018, 8, 8).and_hms(8, 0, 0), Utc), 260 | first_account_id, 261 | first_account_id, 262 | second_account_id, 263 | 500, 264 | )) 265 | .await?; 266 | 267 | let second = activity_repostiory 268 | .save(&ActivityEntity::new( 269 | None, 270 | DateTime::::from_utc(NaiveDate::from_ymd(2018, 8, 8).and_hms(8, 0, 0), Utc), 271 | second_account_id, 272 | first_account_id, 273 | second_account_id, 274 | 500, 275 | )) 276 | .await?; 277 | 278 | let third = activity_repostiory 279 | .save(&ActivityEntity::new( 280 | None, 281 | DateTime::::from_utc(NaiveDate::from_ymd(2018, 8, 9).and_hms(10, 0, 0), Utc), 282 | first_account_id, 283 | second_account_id, 284 | first_account_id, 285 | 1000, 286 | )) 287 | .await?; 288 | 289 | let fourth = activity_repostiory 290 | .save(&ActivityEntity::new( 291 | None, 292 | DateTime::::from_utc(NaiveDate::from_ymd(2018, 8, 9).and_hms(10, 0, 0), Utc), 293 | second_account_id, 294 | second_account_id, 295 | first_account_id, 296 | 1000, 297 | )) 298 | .await?; 299 | 300 | let fifth = activity_repostiory 301 | .save(&ActivityEntity::new( 302 | None, 303 | DateTime::::from_utc(NaiveDate::from_ymd(2019, 8, 9).and_hms(9, 0, 0), Utc), 304 | first_account_id, 305 | first_account_id, 306 | second_account_id, 307 | 1000, 308 | )) 309 | .await?; 310 | 311 | let sixth = activity_repostiory 312 | .save(&ActivityEntity::new( 313 | None, 314 | DateTime::::from_utc(NaiveDate::from_ymd(2019, 8, 9).and_hms(9, 0, 0), Utc), 315 | second_account_id, 316 | first_account_id, 317 | second_account_id, 318 | 1000, 319 | )) 320 | .await?; 321 | 322 | let seventh = activity_repostiory 323 | .save(&ActivityEntity::new( 324 | None, 325 | DateTime::::from_utc(NaiveDate::from_ymd(2019, 8, 9).and_hms(10, 0, 0), Utc), 326 | first_account_id, 327 | second_account_id, 328 | first_account_id, 329 | 1000, 330 | )) 331 | .await?; 332 | 333 | let eigth = activity_repostiory 334 | .save(&ActivityEntity::new( 335 | None, 336 | DateTime::::from_utc(NaiveDate::from_ymd(2019, 8, 9).and_hms(10, 0, 0), Utc), 337 | second_account_id, 338 | second_account_id, 339 | first_account_id, 340 | 1000, 341 | )) 342 | .await?; 343 | 344 | Ok(vec![ 345 | first.id.unwrap(), 346 | second.id.unwrap(), 347 | third.id.unwrap(), 348 | fourth.id.unwrap(), 349 | fifth.id.unwrap(), 350 | sixth.id.unwrap(), 351 | seventh.id.unwrap(), 352 | eigth.id.unwrap(), 353 | ]) 354 | } 355 | 356 | async fn delete_activites_for_ids(activity_ids: Vec, pool: &PgPool) -> Result<()> { 357 | for activity_id in activity_ids { 358 | sqlx::query!( 359 | r#" 360 | DELETE FROM activity WHERE id = $1 361 | "#, 362 | activity_id, 363 | ) 364 | .execute(pool) 365 | .await?; 366 | } 367 | 368 | Ok(()) 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /buckpal-application/src/application/service/send_money_service.rs: -------------------------------------------------------------------------------- 1 | use crate::application::port::incoming::send_money_use_case::{SendMoneyCommand, SendMoneyUseCase}; 2 | use crate::application::port::outgoing::{ 3 | account_lock::AccountLock, load_account_port::LoadAccountPort, 4 | update_account_state_port::UpdateAccountStatePort, 5 | }; 6 | use crate::application::service::error::ServiceError; 7 | use crate::application::service::money_transfer_properties::MoneyTransferProperties; 8 | use anyhow::{anyhow, Result}; 9 | use async_trait::async_trait; 10 | 11 | pub struct SendMoneyService { 12 | load_account_port: Box, 13 | account_lock: Box, 14 | update_account_state_port: Box, 15 | money_transfer_properties: MoneyTransferProperties, 16 | } 17 | 18 | impl SendMoneyService { 19 | pub fn new( 20 | load_account_port: Box, 21 | account_lock: Box, 22 | update_account_state_port: Box, 23 | money_transfer_properties: MoneyTransferProperties, 24 | ) -> Self { 25 | Self { 26 | load_account_port, 27 | account_lock, 28 | update_account_state_port, 29 | money_transfer_properties, 30 | } 31 | } 32 | } 33 | 34 | #[async_trait] 35 | impl SendMoneyUseCase for SendMoneyService { 36 | async fn send_money(&self, command: &SendMoneyCommand) -> Result<()> { 37 | use chrono::{Duration, Utc}; 38 | 39 | if let Err(err) = self.check_threshold(command) { 40 | return Err(err); 41 | } 42 | 43 | let baseline_date = Utc::now() - Duration::days(10); 44 | 45 | let mut source_account = self 46 | .load_account_port 47 | .load_account(&command.source_account_id, &baseline_date) 48 | .await?; 49 | 50 | let mut target_account = self 51 | .load_account_port 52 | .load_account(&command.target_account_id, &baseline_date) 53 | .await?; 54 | 55 | let source_account_id = source_account 56 | .clone() 57 | .id 58 | .expect("expected source account ID not to be empty"); 59 | let target_account_id = target_account 60 | .clone() 61 | .id 62 | .expect("expected target account ID not to be empty"); 63 | 64 | self.account_lock.lock_account(&source_account_id); 65 | if let Err(err) = source_account.withdraw(&command.money, &target_account_id) { 66 | self.account_lock.release_account(&source_account_id); 67 | return Err(err); 68 | } 69 | 70 | self.account_lock.lock_account(&target_account_id); 71 | if let Err(err) = target_account.deposit(&command.money, &source_account_id) { 72 | self.account_lock.release_account(&source_account_id); 73 | self.account_lock.release_account(&target_account_id); 74 | return Err(err); 75 | } 76 | 77 | self.update_account_state_port 78 | .update_activities(&source_account) 79 | .await?; 80 | self.update_account_state_port 81 | .update_activities(&target_account) 82 | .await?; 83 | 84 | self.account_lock.release_account(&source_account_id); 85 | self.account_lock.release_account(&target_account_id); 86 | 87 | Ok(()) 88 | } 89 | } 90 | 91 | impl SendMoneyService { 92 | fn check_threshold(&self, command: &SendMoneyCommand) -> Result<()> { 93 | if command.money > self.money_transfer_properties.maximum_transfer_threshold() { 94 | let error = ServiceError::ThresholdExceededException { 95 | threshold: self.money_transfer_properties.maximum_transfer_threshold(), 96 | actual: command.money.clone(), 97 | }; 98 | return Err(anyhow!(error)); 99 | } 100 | 101 | Ok(()) 102 | } 103 | } 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | use super::SendMoneyService; 108 | use crate::application::port::incoming::send_money_use_case::{ 109 | SendMoneyCommand, SendMoneyUseCase, 110 | }; 111 | use crate::application::port::outgoing::{ 112 | account_lock::MockAccountLock, load_account_port::LoadAccountPort, 113 | update_account_state_port::UpdateAccountStatePort, 114 | }; 115 | use crate::application::service::money_transfer_properties::MoneyTransferProperties; 116 | use crate::domain::account::account_test_data::AccountBuilder; 117 | use crate::domain::account::{Account, AccountId}; 118 | use crate::domain::activity::Activity; 119 | use anyhow::anyhow; 120 | use anyhow::Result; 121 | use async_trait::async_trait; 122 | use chrono::{DateTime, Utc}; 123 | use mockall::*; 124 | use mocktopus::mocking::*; 125 | use rusty_money::{money, Money}; 126 | use std::sync::Mutex; 127 | 128 | #[async_std::test] 129 | async fn given_withdrawal_fails_then_only_source_account_is_locked_and_released() { 130 | let mut load_account_port = MockLoadAccountPort::default(); 131 | let mut account_lock = MockAccountLock::new(); 132 | let update_account_state_port = MockUpdateAccountStatePort::default(); 133 | let money_transfer_properties = MoneyTransferProperties::default(); 134 | 135 | let source_account_id = AccountId(41); 136 | let source_account = given_an_account_with_id(&source_account_id, &mut load_account_port); 137 | 138 | let target_account_id = AccountId(42); 139 | let target_account = given_an_account_with_id(&target_account_id, &mut load_account_port); 140 | 141 | given_withdrawal_will_fail(&source_account); 142 | given_deposit_will_succeed(&target_account); 143 | 144 | account_lock 145 | .expect_lock_account() 146 | .with(predicate::eq(source_account_id.clone())) 147 | .times(1) 148 | .returning(|_| ()); 149 | 150 | account_lock 151 | .expect_release_account() 152 | .with(predicate::eq(source_account_id.clone())) 153 | .times(1) 154 | .returning(|_| ()); 155 | 156 | account_lock 157 | .expect_lock_account() 158 | .with(predicate::eq(target_account_id.clone())) 159 | .times(0); 160 | 161 | let command = 162 | SendMoneyCommand::new(source_account_id, target_account_id, money!(300, "AUD")); 163 | 164 | let send_money_service = SendMoneyService::new( 165 | Box::new(load_account_port), 166 | Box::new(account_lock), 167 | Box::new(update_account_state_port), 168 | money_transfer_properties, 169 | ); 170 | 171 | let success = send_money_service.send_money(&command).await.is_ok(); 172 | assert_eq!(success, false); 173 | } 174 | 175 | #[async_std::test] 176 | async fn transation_succeeds() { 177 | let mut load_account_port = MockLoadAccountPort::default(); 178 | let mut account_lock = MockAccountLock::new(); 179 | let mut update_account_state_port = MockUpdateAccountStatePort::default(); 180 | let money_transfer_properties = MoneyTransferProperties::default(); 181 | 182 | let source_account = given_source_account(&mut load_account_port); 183 | let source_account_id = source_account.clone().id.unwrap(); 184 | 185 | let target_account = given_target_account(&mut load_account_port); 186 | let target_account_id = source_account.clone().id.unwrap(); 187 | 188 | given_withdrawal_will_succeed(&source_account); 189 | given_deposit_will_succeed(&target_account); 190 | 191 | let money = money!(500, "AUD"); 192 | 193 | account_lock 194 | .expect_lock_account() 195 | .with(predicate::eq(source_account_id.clone())) 196 | .returning(|_| ()); 197 | 198 | account_lock 199 | .expect_release_account() 200 | .with(predicate::eq(source_account_id.clone())) 201 | .returning(|_| ()); 202 | 203 | account_lock 204 | .expect_lock_account() 205 | .with(predicate::eq(target_account_id.clone())) 206 | .returning(|_| ()); 207 | 208 | account_lock 209 | .expect_release_account() 210 | .with(predicate::eq(target_account_id.clone())) 211 | .returning(|_| ()); 212 | 213 | then_accounts_have_been_updated( 214 | vec![&source_account_id, &target_account_id], 215 | &mut update_account_state_port, 216 | ); 217 | 218 | let command = SendMoneyCommand::new(source_account_id, target_account_id, money); 219 | 220 | let send_money_service = SendMoneyService::new( 221 | Box::new(load_account_port), 222 | Box::new(account_lock), 223 | Box::new(update_account_state_port), 224 | money_transfer_properties, 225 | ); 226 | 227 | let success = send_money_service.send_money(&command).await.is_ok(); 228 | assert_eq!(success, true); 229 | } 230 | 231 | fn then_accounts_have_been_updated( 232 | account_ids: Vec<&AccountId>, 233 | update_account_state_port_mock: &mut MockUpdateAccountStatePort, 234 | ) { 235 | update_account_state_port_mock.expect_update_activities(account_ids); 236 | } 237 | 238 | fn given_target_account(load_account_port_mock: &mut MockLoadAccountPort) -> Account { 239 | given_an_account_with_id(&AccountId(42), load_account_port_mock) 240 | } 241 | 242 | fn given_source_account(load_account_port_mock: &mut MockLoadAccountPort) -> Account { 243 | given_an_account_with_id(&AccountId(41), load_account_port_mock) 244 | } 245 | 246 | fn given_an_account_with_id( 247 | id: &AccountId, 248 | load_account_port_mock: &mut MockLoadAccountPort, 249 | ) -> Account { 250 | let source_account = AccountBuilder::default_account() 251 | .with_account_id(&id) 252 | .build(); 253 | 254 | load_account_port_mock.expect_load_account(&source_account); 255 | 256 | source_account 257 | } 258 | 259 | fn given_withdrawal_will_succeed(account: &Account) { 260 | let cloned = account.clone(); 261 | Account::withdraw.mock_safe(move |curr, money, target| { 262 | if curr.id == cloned.id { 263 | MockResult::Return(Ok(())) 264 | } else { 265 | MockResult::Continue((curr, money, target)) 266 | } 267 | }) 268 | } 269 | 270 | fn given_withdrawal_will_fail(account: &Account) { 271 | let cloned = account.clone(); 272 | Account::withdraw.mock_safe(move |curr, money, target| { 273 | if curr.id == cloned.id { 274 | MockResult::Return(Err(anyhow!("Something bad happened"))) 275 | } else { 276 | MockResult::Continue((curr, money, target)) 277 | } 278 | }) 279 | } 280 | 281 | fn given_deposit_will_succeed(account: &Account) { 282 | let cloned = account.clone(); 283 | Account::deposit.mock_safe(move |curr, money, target| { 284 | if curr.id == cloned.id { 285 | MockResult::Return(Ok(())) 286 | } else { 287 | MockResult::Continue((curr, money, target)) 288 | } 289 | }) 290 | } 291 | 292 | #[derive(Debug, Default)] 293 | struct MockUpdateAccountStatePort { 294 | expected_updated_account_ids: Mutex>, 295 | } 296 | 297 | impl MockUpdateAccountStatePort { 298 | fn expect_update_activities(&self, account_ids: Vec<&AccountId>) { 299 | for account_id in account_ids { 300 | self.expected_updated_account_ids 301 | .lock() 302 | .unwrap() 303 | .push(account_id.clone()) 304 | } 305 | } 306 | } 307 | 308 | impl Drop for MockUpdateAccountStatePort { 309 | fn drop(&mut self) { 310 | assert!( 311 | self.expected_updated_account_ids.lock().unwrap().len() == 0, 312 | "update activities should have been called with the expected account ids" 313 | ) 314 | } 315 | } 316 | 317 | #[async_trait] 318 | impl UpdateAccountStatePort for MockUpdateAccountStatePort { 319 | async fn update_activities(&self, account: &Account) -> Result> { 320 | let index = self 321 | .expected_updated_account_ids 322 | .lock() 323 | .unwrap() 324 | .iter() 325 | .position(|item| *item == account.id.clone().unwrap()) 326 | .unwrap(); 327 | 328 | self.expected_updated_account_ids 329 | .lock() 330 | .unwrap() 331 | .remove(index); 332 | 333 | // return nothing here 334 | Ok(vec![]) 335 | } 336 | } 337 | 338 | #[derive(Debug, Default)] 339 | struct MockLoadAccountPort { 340 | available_accounts: Vec, 341 | } 342 | 343 | impl MockLoadAccountPort { 344 | fn expect_load_account(&mut self, account: &Account) { 345 | self.available_accounts.push(account.clone()); 346 | } 347 | } 348 | 349 | #[async_trait] 350 | impl LoadAccountPort for MockLoadAccountPort { 351 | async fn load_account( 352 | &self, 353 | account_id: &AccountId, 354 | _baseline_date: &DateTime, 355 | ) -> Result { 356 | let account = self 357 | .available_accounts 358 | .iter() 359 | .find(|account| match account.id.clone() { 360 | Some(id) => id == *account_id, 361 | None => false, 362 | }); 363 | 364 | account 365 | .map(|account| account.clone()) 366 | .ok_or(anyhow!("No matching account found from stub")) 367 | } 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "addr2line" 5 | version = "0.13.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" 8 | dependencies = [ 9 | "gimli", 10 | ] 11 | 12 | [[package]] 13 | name = "adler" 14 | version = "0.2.3" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" 17 | 18 | [[package]] 19 | name = "aead" 20 | version = "0.3.2" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" 23 | dependencies = [ 24 | "generic-array", 25 | ] 26 | 27 | [[package]] 28 | name = "aes" 29 | version = "0.4.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "f7001367fde4c768a19d1029f0a8be5abd9308e1119846d5bd9ad26297b8faf5" 32 | dependencies = [ 33 | "aes-soft", 34 | "aesni", 35 | "block-cipher", 36 | ] 37 | 38 | [[package]] 39 | name = "aes-gcm" 40 | version = "0.6.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "86f5007801316299f922a6198d1d09a0bae95786815d066d5880d13f7c45ead1" 43 | dependencies = [ 44 | "aead", 45 | "aes", 46 | "block-cipher", 47 | "ghash", 48 | "subtle", 49 | ] 50 | 51 | [[package]] 52 | name = "aes-soft" 53 | version = "0.4.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "4925647ee64e5056cf231608957ce7c81e12d6d6e316b9ce1404778cc1d35fa7" 56 | dependencies = [ 57 | "block-cipher", 58 | "byteorder", 59 | "opaque-debug 0.2.3", 60 | ] 61 | 62 | [[package]] 63 | name = "aesni" 64 | version = "0.7.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "d050d39b0b7688b3a3254394c3e30a9d66c41dcf9b05b0e2dbdc623f6505d264" 67 | dependencies = [ 68 | "block-cipher", 69 | "opaque-debug 0.2.3", 70 | ] 71 | 72 | [[package]] 73 | name = "ahash" 74 | version = "0.3.8" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" 77 | 78 | [[package]] 79 | name = "aho-corasick" 80 | version = "0.7.13" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" 83 | dependencies = [ 84 | "memchr", 85 | ] 86 | 87 | [[package]] 88 | name = "anyhow" 89 | version = "1.0.38" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" 92 | 93 | [[package]] 94 | name = "arrayref" 95 | version = "0.3.6" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 98 | 99 | [[package]] 100 | name = "arrayvec" 101 | version = "0.5.1" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 104 | 105 | [[package]] 106 | name = "async-attributes" 107 | version = "1.1.1" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423" 110 | dependencies = [ 111 | "quote", 112 | "syn", 113 | ] 114 | 115 | [[package]] 116 | name = "async-channel" 117 | version = "1.5.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "59740d83946db6a5af71ae25ddf9562c2b176b2ca42cf99a455f09f4a220d6b9" 120 | dependencies = [ 121 | "concurrent-queue", 122 | "event-listener", 123 | "futures-core", 124 | ] 125 | 126 | [[package]] 127 | name = "async-executor" 128 | version = "1.4.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146" 131 | dependencies = [ 132 | "async-task", 133 | "concurrent-queue", 134 | "fastrand", 135 | "futures-lite", 136 | "once_cell", 137 | "vec-arena", 138 | ] 139 | 140 | [[package]] 141 | name = "async-global-executor" 142 | version = "1.4.3" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "73079b49cd26b8fd5a15f68fc7707fc78698dc2a3d61430f2a7a9430230dfa04" 145 | dependencies = [ 146 | "async-executor", 147 | "async-io", 148 | "futures-lite", 149 | "num_cpus", 150 | "once_cell", 151 | ] 152 | 153 | [[package]] 154 | name = "async-h1" 155 | version = "2.1.2" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "7ca2b5cfe1804f48bb8dfb1b2391e6e9a3fbf89e07514dce3bddb03eb4d529db" 158 | dependencies = [ 159 | "async-std", 160 | "byte-pool", 161 | "futures-core", 162 | "http-types", 163 | "httparse", 164 | "lazy_static", 165 | "log", 166 | "pin-project-lite 0.1.7", 167 | ] 168 | 169 | [[package]] 170 | name = "async-io" 171 | version = "1.1.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "38628c78a34f111c5a6b98fc87dfc056cd1590b61afe748b145be4623c56d194" 174 | dependencies = [ 175 | "cfg-if 0.1.10", 176 | "concurrent-queue", 177 | "fastrand", 178 | "futures-lite", 179 | "libc", 180 | "log", 181 | "once_cell", 182 | "parking", 183 | "polling", 184 | "socket2", 185 | "vec-arena", 186 | "waker-fn", 187 | "wepoll-sys-stjepang", 188 | "winapi", 189 | ] 190 | 191 | [[package]] 192 | name = "async-mutex" 193 | version = "1.1.5" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "20e85981fc34e84cdff3fc2c9219189752633fdc538a06df8b5ac45b68a4f3a9" 196 | dependencies = [ 197 | "event-listener", 198 | ] 199 | 200 | [[package]] 201 | name = "async-native-tls" 202 | version = "0.3.3" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" 205 | dependencies = [ 206 | "async-std", 207 | "native-tls", 208 | "thiserror", 209 | "url", 210 | ] 211 | 212 | [[package]] 213 | name = "async-process" 214 | version = "1.0.1" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "4c8cea09c1fb10a317d1b5af8024eeba256d6554763e85ecd90ff8df31c7bbda" 217 | dependencies = [ 218 | "async-io", 219 | "blocking", 220 | "cfg-if 0.1.10", 221 | "event-listener", 222 | "futures-lite", 223 | "once_cell", 224 | "signal-hook", 225 | "winapi", 226 | ] 227 | 228 | [[package]] 229 | name = "async-session" 230 | version = "2.0.1" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "345022a2eed092cd105cc1b26fd61c341e100bd5fcbbd792df4baf31c2cc631f" 233 | dependencies = [ 234 | "anyhow", 235 | "async-std", 236 | "async-trait", 237 | "base64", 238 | "bincode", 239 | "blake3", 240 | "chrono", 241 | "hmac", 242 | "kv-log-macro", 243 | "rand", 244 | "serde", 245 | "serde_json", 246 | "sha2", 247 | ] 248 | 249 | [[package]] 250 | name = "async-sse" 251 | version = "4.0.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "885fb340b166aff3a3b16dd49ef169d8ee34428aa10c0432c61fff584aa222b9" 254 | dependencies = [ 255 | "async-channel", 256 | "async-std", 257 | "http-types", 258 | "log", 259 | "memchr", 260 | "pin-project-lite 0.1.7", 261 | ] 262 | 263 | [[package]] 264 | name = "async-std" 265 | version = "1.8.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "8f9f84f1280a2b436a2c77c2582602732b6c2f4321d5494d6e799e6c367859a8" 268 | dependencies = [ 269 | "async-attributes", 270 | "async-channel", 271 | "async-global-executor", 272 | "async-io", 273 | "async-mutex", 274 | "async-process", 275 | "blocking", 276 | "crossbeam-utils 0.8.1", 277 | "futures-channel", 278 | "futures-core", 279 | "futures-io", 280 | "futures-lite", 281 | "gloo-timers", 282 | "kv-log-macro", 283 | "log", 284 | "memchr", 285 | "num_cpus", 286 | "once_cell", 287 | "pin-project-lite 0.2.0", 288 | "pin-utils", 289 | "slab", 290 | "wasm-bindgen-futures", 291 | ] 292 | 293 | [[package]] 294 | name = "async-task" 295 | version = "4.0.3" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" 298 | 299 | [[package]] 300 | name = "async-trait" 301 | version = "0.1.42" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" 304 | dependencies = [ 305 | "proc-macro2", 306 | "quote", 307 | "syn", 308 | ] 309 | 310 | [[package]] 311 | name = "atoi" 312 | version = "0.3.2" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "e0afb7287b68575f5ca0e5c7e40191cbd4be59d325781f46faa603e176eaef47" 315 | dependencies = [ 316 | "num-traits", 317 | ] 318 | 319 | [[package]] 320 | name = "atomic-waker" 321 | version = "1.0.0" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" 324 | 325 | [[package]] 326 | name = "atty" 327 | version = "0.2.14" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 330 | dependencies = [ 331 | "hermit-abi", 332 | "libc", 333 | "winapi", 334 | ] 335 | 336 | [[package]] 337 | name = "autocfg" 338 | version = "1.0.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 341 | 342 | [[package]] 343 | name = "backtrace" 344 | version = "0.3.50" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" 347 | dependencies = [ 348 | "addr2line", 349 | "cfg-if 0.1.10", 350 | "libc", 351 | "miniz_oxide", 352 | "object", 353 | "rustc-demangle", 354 | ] 355 | 356 | [[package]] 357 | name = "base-x" 358 | version = "0.2.6" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1" 361 | 362 | [[package]] 363 | name = "base64" 364 | version = "0.12.3" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 367 | 368 | [[package]] 369 | name = "bigdecimal" 370 | version = "0.1.2" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "1374191e2dd25f9ae02e3aa95041ed5d747fc77b3c102b49fe2dd9a8117a6244" 373 | dependencies = [ 374 | "num-bigint 0.2.6", 375 | "num-integer", 376 | "num-traits", 377 | ] 378 | 379 | [[package]] 380 | name = "bigdecimal" 381 | version = "0.2.0" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "cc403c26e6b03005522e6e8053384c4e881dfe5b2bf041c0c2c49be33d64a539" 384 | dependencies = [ 385 | "num-bigint 0.3.0", 386 | "num-integer", 387 | "num-traits", 388 | ] 389 | 390 | [[package]] 391 | name = "bincode" 392 | version = "1.3.1" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" 395 | dependencies = [ 396 | "byteorder", 397 | "serde", 398 | ] 399 | 400 | [[package]] 401 | name = "bitflags" 402 | version = "1.2.1" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 405 | 406 | [[package]] 407 | name = "blake3" 408 | version = "0.3.6" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "ce4f9586c9a3151c4b49b19e82ba163dd073614dd057e53c969e1a4db5b52720" 411 | dependencies = [ 412 | "arrayref", 413 | "arrayvec", 414 | "cc", 415 | "cfg-if 0.1.10", 416 | "constant_time_eq", 417 | "crypto-mac", 418 | "digest", 419 | ] 420 | 421 | [[package]] 422 | name = "block-buffer" 423 | version = "0.9.0" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 426 | dependencies = [ 427 | "generic-array", 428 | ] 429 | 430 | [[package]] 431 | name = "block-cipher" 432 | version = "0.7.1" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "fa136449e765dc7faa244561ccae839c394048667929af599b5d931ebe7b7f10" 435 | dependencies = [ 436 | "generic-array", 437 | ] 438 | 439 | [[package]] 440 | name = "blocking" 441 | version = "1.0.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "2640778f8053e72c11f621b0a5175a0560a269282aa98ed85107773ab8e2a556" 444 | dependencies = [ 445 | "async-channel", 446 | "atomic-waker", 447 | "fastrand", 448 | "futures-lite", 449 | "once_cell", 450 | "waker-fn", 451 | ] 452 | 453 | [[package]] 454 | name = "buckpal-application" 455 | version = "0.1.0" 456 | dependencies = [ 457 | "anyhow", 458 | "async-std", 459 | "async-trait", 460 | "cfg-if 1.0.0", 461 | "chrono", 462 | "mockall", 463 | "mocktopus", 464 | "rust_decimal", 465 | "rusty-money", 466 | "thiserror", 467 | ] 468 | 469 | [[package]] 470 | name = "buckpal-persistence" 471 | version = "0.1.0" 472 | dependencies = [ 473 | "anyhow", 474 | "async-std", 475 | "async-trait", 476 | "bigdecimal 0.2.0", 477 | "buckpal-application", 478 | "chrono", 479 | "rust_decimal", 480 | "rusty-money", 481 | "sqlx", 482 | "thiserror", 483 | ] 484 | 485 | [[package]] 486 | name = "buckpal-web" 487 | version = "0.1.0" 488 | dependencies = [ 489 | "anyhow", 490 | "async-std", 491 | "buckpal-application", 492 | "buckpal-persistence", 493 | "dotenv", 494 | "env_logger", 495 | "log", 496 | "rusty-money", 497 | "serde", 498 | "serde_json", 499 | "sqlx", 500 | "thiserror", 501 | "tide", 502 | ] 503 | 504 | [[package]] 505 | name = "build_const" 506 | version = "0.2.1" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" 509 | 510 | [[package]] 511 | name = "bumpalo" 512 | version = "3.4.0" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" 515 | 516 | [[package]] 517 | name = "byte-pool" 518 | version = "0.2.2" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "1e38e98299d518ec351ca016363e0cbfc77059dcd08dfa9700d15e405536097a" 521 | dependencies = [ 522 | "crossbeam-queue", 523 | "stable_deref_trait", 524 | ] 525 | 526 | [[package]] 527 | name = "byteorder" 528 | version = "1.3.4" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 531 | 532 | [[package]] 533 | name = "bytes" 534 | version = "0.5.6" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" 537 | 538 | [[package]] 539 | name = "cache-padded" 540 | version = "1.1.1" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" 543 | 544 | [[package]] 545 | name = "cc" 546 | version = "1.0.58" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" 549 | 550 | [[package]] 551 | name = "cfg-if" 552 | version = "0.1.10" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 555 | 556 | [[package]] 557 | name = "cfg-if" 558 | version = "1.0.0" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 561 | 562 | [[package]] 563 | name = "chrono" 564 | version = "0.4.19" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 567 | dependencies = [ 568 | "libc", 569 | "num-integer", 570 | "num-traits", 571 | "serde", 572 | "time 0.1.43", 573 | "winapi", 574 | ] 575 | 576 | [[package]] 577 | name = "cloudabi" 578 | version = "0.1.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" 581 | dependencies = [ 582 | "bitflags", 583 | ] 584 | 585 | [[package]] 586 | name = "concurrent-queue" 587 | version = "1.2.2" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" 590 | dependencies = [ 591 | "cache-padded", 592 | ] 593 | 594 | [[package]] 595 | name = "constant_time_eq" 596 | version = "0.1.5" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 599 | 600 | [[package]] 601 | name = "cookie" 602 | version = "0.14.2" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "1373a16a4937bc34efec7b391f9c1500c30b8478a701a4f44c9165cc0475a6e0" 605 | dependencies = [ 606 | "aes-gcm", 607 | "base64", 608 | "hkdf", 609 | "hmac", 610 | "percent-encoding", 611 | "rand", 612 | "sha2", 613 | "time 0.2.16", 614 | "version_check", 615 | ] 616 | 617 | [[package]] 618 | name = "core-foundation" 619 | version = "0.7.0" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" 622 | dependencies = [ 623 | "core-foundation-sys", 624 | "libc", 625 | ] 626 | 627 | [[package]] 628 | name = "core-foundation-sys" 629 | version = "0.7.0" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" 632 | 633 | [[package]] 634 | name = "cpuid-bool" 635 | version = "0.1.2" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" 638 | 639 | [[package]] 640 | name = "crc" 641 | version = "1.8.1" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" 644 | dependencies = [ 645 | "build_const", 646 | ] 647 | 648 | [[package]] 649 | name = "crossbeam-channel" 650 | version = "0.4.3" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "09ee0cc8804d5393478d743b035099520087a5186f3b93fa58cec08fa62407b6" 653 | dependencies = [ 654 | "cfg-if 0.1.10", 655 | "crossbeam-utils 0.7.2", 656 | ] 657 | 658 | [[package]] 659 | name = "crossbeam-queue" 660 | version = "0.2.3" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" 663 | dependencies = [ 664 | "cfg-if 0.1.10", 665 | "crossbeam-utils 0.7.2", 666 | "maybe-uninit", 667 | ] 668 | 669 | [[package]] 670 | name = "crossbeam-utils" 671 | version = "0.7.2" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 674 | dependencies = [ 675 | "autocfg", 676 | "cfg-if 0.1.10", 677 | "lazy_static", 678 | ] 679 | 680 | [[package]] 681 | name = "crossbeam-utils" 682 | version = "0.8.1" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" 685 | dependencies = [ 686 | "autocfg", 687 | "cfg-if 1.0.0", 688 | "lazy_static", 689 | ] 690 | 691 | [[package]] 692 | name = "crypto-mac" 693 | version = "0.8.0" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" 696 | dependencies = [ 697 | "generic-array", 698 | "subtle", 699 | ] 700 | 701 | [[package]] 702 | name = "data-encoding" 703 | version = "2.3.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "d4d0e2d24e5ee3b23a01de38eefdcd978907890701f08ffffd4cb457ca4ee8d6" 706 | 707 | [[package]] 708 | name = "difference" 709 | version = "2.0.0" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" 712 | 713 | [[package]] 714 | name = "digest" 715 | version = "0.9.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 718 | dependencies = [ 719 | "generic-array", 720 | ] 721 | 722 | [[package]] 723 | name = "discard" 724 | version = "1.0.4" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" 727 | 728 | [[package]] 729 | name = "dotenv" 730 | version = "0.15.0" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 733 | 734 | [[package]] 735 | name = "downcast" 736 | version = "0.10.0" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d" 739 | 740 | [[package]] 741 | name = "dtoa" 742 | version = "0.4.6" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" 745 | 746 | [[package]] 747 | name = "either" 748 | version = "1.5.3" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 751 | dependencies = [ 752 | "serde", 753 | ] 754 | 755 | [[package]] 756 | name = "env_logger" 757 | version = "0.8.2" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e" 760 | dependencies = [ 761 | "atty", 762 | "humantime", 763 | "log", 764 | "regex", 765 | "termcolor", 766 | ] 767 | 768 | [[package]] 769 | name = "error-chain" 770 | version = "0.12.4" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 773 | dependencies = [ 774 | "backtrace", 775 | "version_check", 776 | ] 777 | 778 | [[package]] 779 | name = "event-listener" 780 | version = "2.5.1" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" 783 | 784 | [[package]] 785 | name = "fastrand" 786 | version = "1.3.4" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "4bd3bdaaf0a72155260a1c098989b60db1cbb22d6a628e64f16237aa4da93cc7" 789 | 790 | [[package]] 791 | name = "femme" 792 | version = "2.1.0" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "7b6b21baebbed15551f2170010ca4101b9ed3fdc05822791c8bd4631840eab81" 795 | dependencies = [ 796 | "cfg-if 0.1.10", 797 | "js-sys", 798 | "log", 799 | "serde", 800 | "serde_derive", 801 | "wasm-bindgen", 802 | "web-sys", 803 | ] 804 | 805 | [[package]] 806 | name = "float-cmp" 807 | version = "0.8.0" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" 810 | dependencies = [ 811 | "num-traits", 812 | ] 813 | 814 | [[package]] 815 | name = "foreign-types" 816 | version = "0.3.2" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 819 | dependencies = [ 820 | "foreign-types-shared", 821 | ] 822 | 823 | [[package]] 824 | name = "foreign-types-shared" 825 | version = "0.1.1" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 828 | 829 | [[package]] 830 | name = "fragile" 831 | version = "1.0.0" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2" 834 | 835 | [[package]] 836 | name = "futures" 837 | version = "0.3.5" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" 840 | dependencies = [ 841 | "futures-channel", 842 | "futures-core", 843 | "futures-executor", 844 | "futures-io", 845 | "futures-sink", 846 | "futures-task", 847 | "futures-util", 848 | ] 849 | 850 | [[package]] 851 | name = "futures-channel" 852 | version = "0.3.5" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" 855 | dependencies = [ 856 | "futures-core", 857 | "futures-sink", 858 | ] 859 | 860 | [[package]] 861 | name = "futures-core" 862 | version = "0.3.5" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" 865 | 866 | [[package]] 867 | name = "futures-executor" 868 | version = "0.3.5" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" 871 | dependencies = [ 872 | "futures-core", 873 | "futures-task", 874 | "futures-util", 875 | ] 876 | 877 | [[package]] 878 | name = "futures-io" 879 | version = "0.3.5" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" 882 | 883 | [[package]] 884 | name = "futures-lite" 885 | version = "1.11.2" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "5e6c079abfac3ab269e2927ec048dabc89d009ebfdda6b8ee86624f30c689658" 888 | dependencies = [ 889 | "fastrand", 890 | "futures-core", 891 | "futures-io", 892 | "memchr", 893 | "parking", 894 | "pin-project-lite 0.1.7", 895 | "waker-fn", 896 | ] 897 | 898 | [[package]] 899 | name = "futures-macro" 900 | version = "0.3.5" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" 903 | dependencies = [ 904 | "proc-macro-hack", 905 | "proc-macro2", 906 | "quote", 907 | "syn", 908 | ] 909 | 910 | [[package]] 911 | name = "futures-sink" 912 | version = "0.3.5" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" 915 | 916 | [[package]] 917 | name = "futures-task" 918 | version = "0.3.5" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" 921 | dependencies = [ 922 | "once_cell", 923 | ] 924 | 925 | [[package]] 926 | name = "futures-util" 927 | version = "0.3.5" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" 930 | dependencies = [ 931 | "futures-channel", 932 | "futures-core", 933 | "futures-io", 934 | "futures-macro", 935 | "futures-sink", 936 | "futures-task", 937 | "memchr", 938 | "pin-project", 939 | "pin-utils", 940 | "proc-macro-hack", 941 | "proc-macro-nested", 942 | "slab", 943 | ] 944 | 945 | [[package]] 946 | name = "generic-array" 947 | version = "0.14.4" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 950 | dependencies = [ 951 | "typenum", 952 | "version_check", 953 | ] 954 | 955 | [[package]] 956 | name = "getrandom" 957 | version = "0.1.14" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 960 | dependencies = [ 961 | "cfg-if 0.1.10", 962 | "libc", 963 | "wasi", 964 | ] 965 | 966 | [[package]] 967 | name = "ghash" 968 | version = "0.3.0" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "d6e27f0689a6e15944bdce7e45425efb87eaa8ab0c6e87f11d0987a9133e2531" 971 | dependencies = [ 972 | "polyval", 973 | ] 974 | 975 | [[package]] 976 | name = "gimli" 977 | version = "0.22.0" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" 980 | 981 | [[package]] 982 | name = "gloo-timers" 983 | version = "0.2.1" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" 986 | dependencies = [ 987 | "futures-channel", 988 | "futures-core", 989 | "js-sys", 990 | "wasm-bindgen", 991 | "web-sys", 992 | ] 993 | 994 | [[package]] 995 | name = "hashbrown" 996 | version = "0.8.2" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25" 999 | dependencies = [ 1000 | "ahash", 1001 | "autocfg", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "heck" 1006 | version = "0.3.1" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 1009 | dependencies = [ 1010 | "unicode-segmentation", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "hermit-abi" 1015 | version = "0.1.15" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" 1018 | dependencies = [ 1019 | "libc", 1020 | ] 1021 | 1022 | [[package]] 1023 | name = "hex" 1024 | version = "0.4.2" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" 1027 | 1028 | [[package]] 1029 | name = "hkdf" 1030 | version = "0.9.0" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "fe1149865383e4526a43aee8495f9a325f0b806c63ce6427d06336a590abbbc9" 1033 | dependencies = [ 1034 | "digest", 1035 | "hmac", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "hmac" 1040 | version = "0.8.1" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" 1043 | dependencies = [ 1044 | "crypto-mac", 1045 | "digest", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "http-types" 1050 | version = "2.4.0" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "bb4daf8dc001485f4a32a7a17c54c67fa8a10340188f30ba87ac0fe1a9451e97" 1053 | dependencies = [ 1054 | "anyhow", 1055 | "async-std", 1056 | "cookie", 1057 | "infer", 1058 | "pin-project-lite 0.1.7", 1059 | "rand", 1060 | "serde", 1061 | "serde_json", 1062 | "serde_qs", 1063 | "serde_urlencoded", 1064 | "url", 1065 | ] 1066 | 1067 | [[package]] 1068 | name = "httparse" 1069 | version = "1.3.4" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" 1072 | 1073 | [[package]] 1074 | name = "humantime" 1075 | version = "2.0.1" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a" 1078 | 1079 | [[package]] 1080 | name = "idna" 1081 | version = "0.2.0" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" 1084 | dependencies = [ 1085 | "matches", 1086 | "unicode-bidi", 1087 | "unicode-normalization", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "indexmap" 1092 | version = "1.5.1" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9" 1095 | dependencies = [ 1096 | "autocfg", 1097 | "hashbrown", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "infer" 1102 | version = "0.1.7" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "6854dd77ddc4f9ba1a448f487e27843583d407648150426a30c2ea3a2c39490a" 1105 | 1106 | [[package]] 1107 | name = "instant" 1108 | version = "0.1.6" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "5b141fdc7836c525d4d594027d318c84161ca17aaf8113ab1f81ab93ae897485" 1111 | 1112 | [[package]] 1113 | name = "itoa" 1114 | version = "0.4.6" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 1117 | 1118 | [[package]] 1119 | name = "js-sys" 1120 | version = "0.3.44" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "85a7e2c92a4804dd459b86c339278d0fe87cf93757fae222c3fa3ae75458bc73" 1123 | dependencies = [ 1124 | "wasm-bindgen", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "kv-log-macro" 1129 | version = "1.0.7" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" 1132 | dependencies = [ 1133 | "log", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "lazy_static" 1138 | version = "1.4.0" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 1141 | 1142 | [[package]] 1143 | name = "libc" 1144 | version = "0.2.77" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" 1147 | 1148 | [[package]] 1149 | name = "linked-hash-map" 1150 | version = "0.5.3" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" 1153 | 1154 | [[package]] 1155 | name = "lock_api" 1156 | version = "0.4.1" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c" 1159 | dependencies = [ 1160 | "scopeguard", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "log" 1165 | version = "0.4.13" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2" 1168 | dependencies = [ 1169 | "cfg-if 0.1.10", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "lru-cache" 1174 | version = "0.1.2" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" 1177 | dependencies = [ 1178 | "linked-hash-map", 1179 | ] 1180 | 1181 | [[package]] 1182 | name = "maplit" 1183 | version = "1.0.2" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 1186 | 1187 | [[package]] 1188 | name = "matches" 1189 | version = "0.1.8" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 1192 | 1193 | [[package]] 1194 | name = "maybe-uninit" 1195 | version = "2.0.0" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 1198 | 1199 | [[package]] 1200 | name = "md-5" 1201 | version = "0.9.1" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" 1204 | dependencies = [ 1205 | "block-buffer", 1206 | "digest", 1207 | "opaque-debug 0.3.0", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "memchr" 1212 | version = "2.3.3" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 1215 | 1216 | [[package]] 1217 | name = "miniz_oxide" 1218 | version = "0.4.0" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" 1221 | dependencies = [ 1222 | "adler", 1223 | ] 1224 | 1225 | [[package]] 1226 | name = "mockall" 1227 | version = "0.9.0" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "619634fd9149c4a06e66d8fd9256e85326d8eeee75abee4565ff76c92e4edfe0" 1230 | dependencies = [ 1231 | "cfg-if 1.0.0", 1232 | "downcast", 1233 | "fragile", 1234 | "lazy_static", 1235 | "mockall_derive", 1236 | "predicates", 1237 | "predicates-tree", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "mockall_derive" 1242 | version = "0.9.0" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "83714c95dbf4c24202f0f1b208f0f248e6bd65abfa8989303611a71c0f781548" 1245 | dependencies = [ 1246 | "cfg-if 1.0.0", 1247 | "proc-macro2", 1248 | "quote", 1249 | "syn", 1250 | ] 1251 | 1252 | [[package]] 1253 | name = "mocktopus" 1254 | version = "0.7.11" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "d1e54a5bbecd61a064cb9c6ef396f8c896aee14e5baba8d1d555f35167dfd7c3" 1257 | dependencies = [ 1258 | "mocktopus_macros", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "mocktopus_macros" 1263 | version = "0.7.11" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "3048ef3680533a27f9f8e7d6a0bce44dc61e4895ea0f42709337fa1c8616fefe" 1266 | dependencies = [ 1267 | "proc-macro2", 1268 | "quote", 1269 | "syn", 1270 | ] 1271 | 1272 | [[package]] 1273 | name = "native-tls" 1274 | version = "0.2.4" 1275 | source = "registry+https://github.com/rust-lang/crates.io-index" 1276 | checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" 1277 | dependencies = [ 1278 | "lazy_static", 1279 | "libc", 1280 | "log", 1281 | "openssl", 1282 | "openssl-probe", 1283 | "openssl-sys", 1284 | "schannel", 1285 | "security-framework", 1286 | "security-framework-sys", 1287 | "tempfile", 1288 | ] 1289 | 1290 | [[package]] 1291 | name = "normalize-line-endings" 1292 | version = "0.3.0" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 1295 | 1296 | [[package]] 1297 | name = "num-bigint" 1298 | version = "0.2.6" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" 1301 | dependencies = [ 1302 | "autocfg", 1303 | "num-integer", 1304 | "num-traits", 1305 | ] 1306 | 1307 | [[package]] 1308 | name = "num-bigint" 1309 | version = "0.3.0" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "b7f3fc75e3697059fb1bc465e3d8cca6cf92f56854f201158b3f9c77d5a3cfa0" 1312 | dependencies = [ 1313 | "autocfg", 1314 | "num-integer", 1315 | "num-traits", 1316 | ] 1317 | 1318 | [[package]] 1319 | name = "num-integer" 1320 | version = "0.1.43" 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" 1322 | checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" 1323 | dependencies = [ 1324 | "autocfg", 1325 | "num-traits", 1326 | ] 1327 | 1328 | [[package]] 1329 | name = "num-traits" 1330 | version = "0.2.12" 1331 | source = "registry+https://github.com/rust-lang/crates.io-index" 1332 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 1333 | dependencies = [ 1334 | "autocfg", 1335 | ] 1336 | 1337 | [[package]] 1338 | name = "num_cpus" 1339 | version = "1.13.0" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 1342 | dependencies = [ 1343 | "hermit-abi", 1344 | "libc", 1345 | ] 1346 | 1347 | [[package]] 1348 | name = "object" 1349 | version = "0.20.0" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" 1352 | 1353 | [[package]] 1354 | name = "once_cell" 1355 | version = "1.4.1" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" 1358 | 1359 | [[package]] 1360 | name = "opaque-debug" 1361 | version = "0.2.3" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 1364 | 1365 | [[package]] 1366 | name = "opaque-debug" 1367 | version = "0.3.0" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 1370 | 1371 | [[package]] 1372 | name = "openssl" 1373 | version = "0.10.30" 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" 1375 | checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" 1376 | dependencies = [ 1377 | "bitflags", 1378 | "cfg-if 0.1.10", 1379 | "foreign-types", 1380 | "lazy_static", 1381 | "libc", 1382 | "openssl-sys", 1383 | ] 1384 | 1385 | [[package]] 1386 | name = "openssl-probe" 1387 | version = "0.1.2" 1388 | source = "registry+https://github.com/rust-lang/crates.io-index" 1389 | checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 1390 | 1391 | [[package]] 1392 | name = "openssl-sys" 1393 | version = "0.9.58" 1394 | source = "registry+https://github.com/rust-lang/crates.io-index" 1395 | checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" 1396 | dependencies = [ 1397 | "autocfg", 1398 | "cc", 1399 | "libc", 1400 | "pkg-config", 1401 | "vcpkg", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "parking" 1406 | version = "2.0.0" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" 1409 | 1410 | [[package]] 1411 | name = "parking_lot" 1412 | version = "0.11.0" 1413 | source = "registry+https://github.com/rust-lang/crates.io-index" 1414 | checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" 1415 | dependencies = [ 1416 | "instant", 1417 | "lock_api", 1418 | "parking_lot_core", 1419 | ] 1420 | 1421 | [[package]] 1422 | name = "parking_lot_core" 1423 | version = "0.8.0" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" 1426 | dependencies = [ 1427 | "cfg-if 0.1.10", 1428 | "cloudabi", 1429 | "instant", 1430 | "libc", 1431 | "redox_syscall", 1432 | "smallvec", 1433 | "winapi", 1434 | ] 1435 | 1436 | [[package]] 1437 | name = "percent-encoding" 1438 | version = "2.1.0" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1441 | 1442 | [[package]] 1443 | name = "pin-project" 1444 | version = "0.4.23" 1445 | source = "registry+https://github.com/rust-lang/crates.io-index" 1446 | checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" 1447 | dependencies = [ 1448 | "pin-project-internal", 1449 | ] 1450 | 1451 | [[package]] 1452 | name = "pin-project-internal" 1453 | version = "0.4.23" 1454 | source = "registry+https://github.com/rust-lang/crates.io-index" 1455 | checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" 1456 | dependencies = [ 1457 | "proc-macro2", 1458 | "quote", 1459 | "syn", 1460 | ] 1461 | 1462 | [[package]] 1463 | name = "pin-project-lite" 1464 | version = "0.1.7" 1465 | source = "registry+https://github.com/rust-lang/crates.io-index" 1466 | checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" 1467 | 1468 | [[package]] 1469 | name = "pin-project-lite" 1470 | version = "0.2.0" 1471 | source = "registry+https://github.com/rust-lang/crates.io-index" 1472 | checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" 1473 | 1474 | [[package]] 1475 | name = "pin-utils" 1476 | version = "0.1.0" 1477 | source = "registry+https://github.com/rust-lang/crates.io-index" 1478 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1479 | 1480 | [[package]] 1481 | name = "pkg-config" 1482 | version = "0.3.18" 1483 | source = "registry+https://github.com/rust-lang/crates.io-index" 1484 | checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" 1485 | 1486 | [[package]] 1487 | name = "polling" 1488 | version = "1.0.3" 1489 | source = "registry+https://github.com/rust-lang/crates.io-index" 1490 | checksum = "0307b8c7f438902536321f63c28cab0362f6ee89f1c7da47e3642ff956641c8b" 1491 | dependencies = [ 1492 | "cfg-if 0.1.10", 1493 | "libc", 1494 | "log", 1495 | "wepoll-sys-stjepang", 1496 | "winapi", 1497 | ] 1498 | 1499 | [[package]] 1500 | name = "polyval" 1501 | version = "0.4.0" 1502 | source = "registry+https://github.com/rust-lang/crates.io-index" 1503 | checksum = "d9a50142b55ab3ed0e9f68dfb3709f1d90d29da24e91033f28b96330643107dc" 1504 | dependencies = [ 1505 | "cfg-if 0.1.10", 1506 | "universal-hash", 1507 | ] 1508 | 1509 | [[package]] 1510 | name = "ppv-lite86" 1511 | version = "0.2.8" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" 1514 | 1515 | [[package]] 1516 | name = "predicates" 1517 | version = "1.0.5" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "96bfead12e90dccead362d62bb2c90a5f6fc4584963645bc7f71a735e0b0735a" 1520 | dependencies = [ 1521 | "difference", 1522 | "float-cmp", 1523 | "normalize-line-endings", 1524 | "predicates-core", 1525 | "regex", 1526 | ] 1527 | 1528 | [[package]] 1529 | name = "predicates-core" 1530 | version = "1.0.0" 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" 1532 | checksum = "06075c3a3e92559ff8929e7a280684489ea27fe44805174c3ebd9328dcb37178" 1533 | 1534 | [[package]] 1535 | name = "predicates-tree" 1536 | version = "1.0.0" 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" 1538 | checksum = "8e63c4859013b38a76eca2414c64911fba30def9e3202ac461a2d22831220124" 1539 | dependencies = [ 1540 | "predicates-core", 1541 | "treeline", 1542 | ] 1543 | 1544 | [[package]] 1545 | name = "proc-macro-hack" 1546 | version = "0.5.18" 1547 | source = "registry+https://github.com/rust-lang/crates.io-index" 1548 | checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" 1549 | 1550 | [[package]] 1551 | name = "proc-macro-nested" 1552 | version = "0.1.6" 1553 | source = "registry+https://github.com/rust-lang/crates.io-index" 1554 | checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" 1555 | 1556 | [[package]] 1557 | name = "proc-macro2" 1558 | version = "1.0.24" 1559 | source = "registry+https://github.com/rust-lang/crates.io-index" 1560 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 1561 | dependencies = [ 1562 | "unicode-xid", 1563 | ] 1564 | 1565 | [[package]] 1566 | name = "quote" 1567 | version = "1.0.7" 1568 | source = "registry+https://github.com/rust-lang/crates.io-index" 1569 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 1570 | dependencies = [ 1571 | "proc-macro2", 1572 | ] 1573 | 1574 | [[package]] 1575 | name = "rand" 1576 | version = "0.7.3" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 1579 | dependencies = [ 1580 | "getrandom", 1581 | "libc", 1582 | "rand_chacha", 1583 | "rand_core", 1584 | "rand_hc", 1585 | ] 1586 | 1587 | [[package]] 1588 | name = "rand_chacha" 1589 | version = "0.2.2" 1590 | source = "registry+https://github.com/rust-lang/crates.io-index" 1591 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 1592 | dependencies = [ 1593 | "ppv-lite86", 1594 | "rand_core", 1595 | ] 1596 | 1597 | [[package]] 1598 | name = "rand_core" 1599 | version = "0.5.1" 1600 | source = "registry+https://github.com/rust-lang/crates.io-index" 1601 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 1602 | dependencies = [ 1603 | "getrandom", 1604 | ] 1605 | 1606 | [[package]] 1607 | name = "rand_hc" 1608 | version = "0.2.0" 1609 | source = "registry+https://github.com/rust-lang/crates.io-index" 1610 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 1611 | dependencies = [ 1612 | "rand_core", 1613 | ] 1614 | 1615 | [[package]] 1616 | name = "redox_syscall" 1617 | version = "0.1.57" 1618 | source = "registry+https://github.com/rust-lang/crates.io-index" 1619 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 1620 | 1621 | [[package]] 1622 | name = "regex" 1623 | version = "1.3.9" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 1626 | dependencies = [ 1627 | "aho-corasick", 1628 | "memchr", 1629 | "regex-syntax", 1630 | "thread_local", 1631 | ] 1632 | 1633 | [[package]] 1634 | name = "regex-syntax" 1635 | version = "0.6.18" 1636 | source = "registry+https://github.com/rust-lang/crates.io-index" 1637 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 1638 | 1639 | [[package]] 1640 | name = "remove_dir_all" 1641 | version = "0.5.3" 1642 | source = "registry+https://github.com/rust-lang/crates.io-index" 1643 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1644 | dependencies = [ 1645 | "winapi", 1646 | ] 1647 | 1648 | [[package]] 1649 | name = "route-recognizer" 1650 | version = "0.2.0" 1651 | source = "registry+https://github.com/rust-lang/crates.io-index" 1652 | checksum = "56770675ebc04927ded3e60633437841581c285dc6236109ea25fbf3beb7b59e" 1653 | 1654 | [[package]] 1655 | name = "rust_decimal" 1656 | version = "1.10.1" 1657 | source = "registry+https://github.com/rust-lang/crates.io-index" 1658 | checksum = "04d1fde955d206c00af1eb529d8ebfca3b505921820a0436566dbcc6c4d4ffb3" 1659 | dependencies = [ 1660 | "arrayvec", 1661 | "num-traits", 1662 | "serde", 1663 | ] 1664 | 1665 | [[package]] 1666 | name = "rust_decimal_macros" 1667 | version = "1.8.1" 1668 | source = "registry+https://github.com/rust-lang/crates.io-index" 1669 | checksum = "a5edf059b3f9aaceab5402cea5565ccc567aff559a519a9f180374528ae3a2db" 1670 | dependencies = [ 1671 | "quote", 1672 | "rust_decimal", 1673 | ] 1674 | 1675 | [[package]] 1676 | name = "rustc-demangle" 1677 | version = "0.1.16" 1678 | source = "registry+https://github.com/rust-lang/crates.io-index" 1679 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 1680 | 1681 | [[package]] 1682 | name = "rustc_version" 1683 | version = "0.2.3" 1684 | source = "registry+https://github.com/rust-lang/crates.io-index" 1685 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 1686 | dependencies = [ 1687 | "semver", 1688 | ] 1689 | 1690 | [[package]] 1691 | name = "rusty-money" 1692 | version = "0.3.6" 1693 | source = "registry+https://github.com/rust-lang/crates.io-index" 1694 | checksum = "94d1465869b5a28ff9e1f700555053419efea6d064c6db01a7ed86a3616a04d2" 1695 | dependencies = [ 1696 | "lazy_static", 1697 | "rust_decimal", 1698 | "rust_decimal_macros", 1699 | ] 1700 | 1701 | [[package]] 1702 | name = "ryu" 1703 | version = "1.0.5" 1704 | source = "registry+https://github.com/rust-lang/crates.io-index" 1705 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 1706 | 1707 | [[package]] 1708 | name = "schannel" 1709 | version = "0.1.19" 1710 | source = "registry+https://github.com/rust-lang/crates.io-index" 1711 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 1712 | dependencies = [ 1713 | "lazy_static", 1714 | "winapi", 1715 | ] 1716 | 1717 | [[package]] 1718 | name = "scopeguard" 1719 | version = "1.1.0" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1722 | 1723 | [[package]] 1724 | name = "security-framework" 1725 | version = "0.4.4" 1726 | source = "registry+https://github.com/rust-lang/crates.io-index" 1727 | checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" 1728 | dependencies = [ 1729 | "bitflags", 1730 | "core-foundation", 1731 | "core-foundation-sys", 1732 | "libc", 1733 | "security-framework-sys", 1734 | ] 1735 | 1736 | [[package]] 1737 | name = "security-framework-sys" 1738 | version = "0.4.3" 1739 | source = "registry+https://github.com/rust-lang/crates.io-index" 1740 | checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" 1741 | dependencies = [ 1742 | "core-foundation-sys", 1743 | "libc", 1744 | ] 1745 | 1746 | [[package]] 1747 | name = "semver" 1748 | version = "0.9.0" 1749 | source = "registry+https://github.com/rust-lang/crates.io-index" 1750 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 1751 | dependencies = [ 1752 | "semver-parser", 1753 | ] 1754 | 1755 | [[package]] 1756 | name = "semver-parser" 1757 | version = "0.7.0" 1758 | source = "registry+https://github.com/rust-lang/crates.io-index" 1759 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 1760 | 1761 | [[package]] 1762 | name = "serde" 1763 | version = "1.0.120" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "166b2349061381baf54a58e4b13c89369feb0ef2eaa57198899e2312aac30aab" 1766 | dependencies = [ 1767 | "serde_derive", 1768 | ] 1769 | 1770 | [[package]] 1771 | name = "serde_derive" 1772 | version = "1.0.120" 1773 | source = "registry+https://github.com/rust-lang/crates.io-index" 1774 | checksum = "0ca2a8cb5805ce9e3b95435e3765b7b553cecc762d938d409434338386cb5775" 1775 | dependencies = [ 1776 | "proc-macro2", 1777 | "quote", 1778 | "syn", 1779 | ] 1780 | 1781 | [[package]] 1782 | name = "serde_json" 1783 | version = "1.0.61" 1784 | source = "registry+https://github.com/rust-lang/crates.io-index" 1785 | checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" 1786 | dependencies = [ 1787 | "indexmap", 1788 | "itoa", 1789 | "ryu", 1790 | "serde", 1791 | ] 1792 | 1793 | [[package]] 1794 | name = "serde_qs" 1795 | version = "0.6.1" 1796 | source = "registry+https://github.com/rust-lang/crates.io-index" 1797 | checksum = "c6f3acf84e23ab27c01cb5917551765c01c50b2000089db8fa47fe018a3260cf" 1798 | dependencies = [ 1799 | "data-encoding", 1800 | "error-chain", 1801 | "percent-encoding", 1802 | "serde", 1803 | ] 1804 | 1805 | [[package]] 1806 | name = "serde_urlencoded" 1807 | version = "0.6.1" 1808 | source = "registry+https://github.com/rust-lang/crates.io-index" 1809 | checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" 1810 | dependencies = [ 1811 | "dtoa", 1812 | "itoa", 1813 | "serde", 1814 | "url", 1815 | ] 1816 | 1817 | [[package]] 1818 | name = "sha-1" 1819 | version = "0.9.1" 1820 | source = "registry+https://github.com/rust-lang/crates.io-index" 1821 | checksum = "170a36ea86c864a3f16dd2687712dd6646f7019f301e57537c7f4dc9f5916770" 1822 | dependencies = [ 1823 | "block-buffer", 1824 | "cfg-if 0.1.10", 1825 | "cpuid-bool", 1826 | "digest", 1827 | "opaque-debug 0.3.0", 1828 | ] 1829 | 1830 | [[package]] 1831 | name = "sha1" 1832 | version = "0.6.0" 1833 | source = "registry+https://github.com/rust-lang/crates.io-index" 1834 | checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" 1835 | 1836 | [[package]] 1837 | name = "sha2" 1838 | version = "0.9.1" 1839 | source = "registry+https://github.com/rust-lang/crates.io-index" 1840 | checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" 1841 | dependencies = [ 1842 | "block-buffer", 1843 | "cfg-if 0.1.10", 1844 | "cpuid-bool", 1845 | "digest", 1846 | "opaque-debug 0.3.0", 1847 | ] 1848 | 1849 | [[package]] 1850 | name = "signal-hook" 1851 | version = "0.1.16" 1852 | source = "registry+https://github.com/rust-lang/crates.io-index" 1853 | checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed" 1854 | dependencies = [ 1855 | "libc", 1856 | "signal-hook-registry", 1857 | ] 1858 | 1859 | [[package]] 1860 | name = "signal-hook-registry" 1861 | version = "1.2.2" 1862 | source = "registry+https://github.com/rust-lang/crates.io-index" 1863 | checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab" 1864 | dependencies = [ 1865 | "libc", 1866 | ] 1867 | 1868 | [[package]] 1869 | name = "slab" 1870 | version = "0.4.2" 1871 | source = "registry+https://github.com/rust-lang/crates.io-index" 1872 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1873 | 1874 | [[package]] 1875 | name = "smallvec" 1876 | version = "1.4.1" 1877 | source = "registry+https://github.com/rust-lang/crates.io-index" 1878 | checksum = "3757cb9d89161a2f24e1cf78efa0c1fcff485d18e3f55e0aa3480824ddaa0f3f" 1879 | 1880 | [[package]] 1881 | name = "socket2" 1882 | version = "0.3.12" 1883 | source = "registry+https://github.com/rust-lang/crates.io-index" 1884 | checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" 1885 | dependencies = [ 1886 | "cfg-if 0.1.10", 1887 | "libc", 1888 | "redox_syscall", 1889 | "winapi", 1890 | ] 1891 | 1892 | [[package]] 1893 | name = "sqlformat" 1894 | version = "0.1.0" 1895 | source = "registry+https://github.com/rust-lang/crates.io-index" 1896 | checksum = "5ce64a4576e1720a2e511bf3ccdb8c0f6cfed0fc265bcbaa0bd369485e02c631" 1897 | dependencies = [ 1898 | "lazy_static", 1899 | "maplit", 1900 | "regex", 1901 | ] 1902 | 1903 | [[package]] 1904 | name = "sqlx" 1905 | version = "0.4.0-beta.1" 1906 | source = "git+https://github.com/launchbadge/sqlx#2e1658e08b053f66102b5beabc7fdd4ac28e3a48" 1907 | dependencies = [ 1908 | "sqlx-core", 1909 | "sqlx-macros", 1910 | ] 1911 | 1912 | [[package]] 1913 | name = "sqlx-core" 1914 | version = "0.4.0-beta.1" 1915 | source = "git+https://github.com/launchbadge/sqlx#2e1658e08b053f66102b5beabc7fdd4ac28e3a48" 1916 | dependencies = [ 1917 | "atoi", 1918 | "base64", 1919 | "bigdecimal 0.1.2", 1920 | "bitflags", 1921 | "byteorder", 1922 | "bytes", 1923 | "chrono", 1924 | "crc", 1925 | "crossbeam-channel", 1926 | "crossbeam-queue", 1927 | "crossbeam-utils 0.7.2", 1928 | "either", 1929 | "futures-channel", 1930 | "futures-core", 1931 | "futures-util", 1932 | "hashbrown", 1933 | "hex", 1934 | "hmac", 1935 | "itoa", 1936 | "libc", 1937 | "log", 1938 | "lru-cache", 1939 | "md-5", 1940 | "memchr", 1941 | "num-bigint 0.2.6", 1942 | "once_cell", 1943 | "parking_lot", 1944 | "percent-encoding", 1945 | "rand", 1946 | "serde", 1947 | "sha-1", 1948 | "sha2", 1949 | "smallvec", 1950 | "sqlformat", 1951 | "sqlx-rt", 1952 | "stringprep", 1953 | "thiserror", 1954 | "url", 1955 | "whoami", 1956 | ] 1957 | 1958 | [[package]] 1959 | name = "sqlx-macros" 1960 | version = "0.4.0-beta.1" 1961 | source = "git+https://github.com/launchbadge/sqlx#2e1658e08b053f66102b5beabc7fdd4ac28e3a48" 1962 | dependencies = [ 1963 | "dotenv", 1964 | "either", 1965 | "futures", 1966 | "heck", 1967 | "hex", 1968 | "proc-macro2", 1969 | "quote", 1970 | "serde", 1971 | "serde_json", 1972 | "sha2", 1973 | "sqlx-core", 1974 | "sqlx-rt", 1975 | "syn", 1976 | "url", 1977 | ] 1978 | 1979 | [[package]] 1980 | name = "sqlx-rt" 1981 | version = "0.1.1" 1982 | source = "git+https://github.com/launchbadge/sqlx#2e1658e08b053f66102b5beabc7fdd4ac28e3a48" 1983 | dependencies = [ 1984 | "async-native-tls", 1985 | "async-std", 1986 | "native-tls", 1987 | ] 1988 | 1989 | [[package]] 1990 | name = "stable_deref_trait" 1991 | version = "1.2.0" 1992 | source = "registry+https://github.com/rust-lang/crates.io-index" 1993 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1994 | 1995 | [[package]] 1996 | name = "standback" 1997 | version = "0.2.9" 1998 | source = "registry+https://github.com/rust-lang/crates.io-index" 1999 | checksum = "b0437cfb83762844799a60e1e3b489d5ceb6a650fbacb86437badc1b6d87b246" 2000 | dependencies = [ 2001 | "version_check", 2002 | ] 2003 | 2004 | [[package]] 2005 | name = "stdweb" 2006 | version = "0.4.20" 2007 | source = "registry+https://github.com/rust-lang/crates.io-index" 2008 | checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" 2009 | dependencies = [ 2010 | "discard", 2011 | "rustc_version", 2012 | "stdweb-derive", 2013 | "stdweb-internal-macros", 2014 | "stdweb-internal-runtime", 2015 | "wasm-bindgen", 2016 | ] 2017 | 2018 | [[package]] 2019 | name = "stdweb-derive" 2020 | version = "0.5.3" 2021 | source = "registry+https://github.com/rust-lang/crates.io-index" 2022 | checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" 2023 | dependencies = [ 2024 | "proc-macro2", 2025 | "quote", 2026 | "serde", 2027 | "serde_derive", 2028 | "syn", 2029 | ] 2030 | 2031 | [[package]] 2032 | name = "stdweb-internal-macros" 2033 | version = "0.2.9" 2034 | source = "registry+https://github.com/rust-lang/crates.io-index" 2035 | checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" 2036 | dependencies = [ 2037 | "base-x", 2038 | "proc-macro2", 2039 | "quote", 2040 | "serde", 2041 | "serde_derive", 2042 | "serde_json", 2043 | "sha1", 2044 | "syn", 2045 | ] 2046 | 2047 | [[package]] 2048 | name = "stdweb-internal-runtime" 2049 | version = "0.1.5" 2050 | source = "registry+https://github.com/rust-lang/crates.io-index" 2051 | checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" 2052 | 2053 | [[package]] 2054 | name = "stringprep" 2055 | version = "0.1.2" 2056 | source = "registry+https://github.com/rust-lang/crates.io-index" 2057 | checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" 2058 | dependencies = [ 2059 | "unicode-bidi", 2060 | "unicode-normalization", 2061 | ] 2062 | 2063 | [[package]] 2064 | name = "subtle" 2065 | version = "2.2.3" 2066 | source = "registry+https://github.com/rust-lang/crates.io-index" 2067 | checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1" 2068 | 2069 | [[package]] 2070 | name = "syn" 2071 | version = "1.0.58" 2072 | source = "registry+https://github.com/rust-lang/crates.io-index" 2073 | checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" 2074 | dependencies = [ 2075 | "proc-macro2", 2076 | "quote", 2077 | "unicode-xid", 2078 | ] 2079 | 2080 | [[package]] 2081 | name = "tempfile" 2082 | version = "3.1.0" 2083 | source = "registry+https://github.com/rust-lang/crates.io-index" 2084 | checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 2085 | dependencies = [ 2086 | "cfg-if 0.1.10", 2087 | "libc", 2088 | "rand", 2089 | "redox_syscall", 2090 | "remove_dir_all", 2091 | "winapi", 2092 | ] 2093 | 2094 | [[package]] 2095 | name = "termcolor" 2096 | version = "1.1.0" 2097 | source = "registry+https://github.com/rust-lang/crates.io-index" 2098 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 2099 | dependencies = [ 2100 | "winapi-util", 2101 | ] 2102 | 2103 | [[package]] 2104 | name = "thiserror" 2105 | version = "1.0.23" 2106 | source = "registry+https://github.com/rust-lang/crates.io-index" 2107 | checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" 2108 | dependencies = [ 2109 | "thiserror-impl", 2110 | ] 2111 | 2112 | [[package]] 2113 | name = "thiserror-impl" 2114 | version = "1.0.23" 2115 | source = "registry+https://github.com/rust-lang/crates.io-index" 2116 | checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" 2117 | dependencies = [ 2118 | "proc-macro2", 2119 | "quote", 2120 | "syn", 2121 | ] 2122 | 2123 | [[package]] 2124 | name = "thread_local" 2125 | version = "1.0.1" 2126 | source = "registry+https://github.com/rust-lang/crates.io-index" 2127 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 2128 | dependencies = [ 2129 | "lazy_static", 2130 | ] 2131 | 2132 | [[package]] 2133 | name = "tide" 2134 | version = "0.13.0" 2135 | source = "registry+https://github.com/rust-lang/crates.io-index" 2136 | checksum = "b7c4c4e35f5a89ed08cbb59b6e4e1d648e563d6f8daa804002f3557d11d3c8f9" 2137 | dependencies = [ 2138 | "async-h1", 2139 | "async-session", 2140 | "async-sse", 2141 | "async-std", 2142 | "async-trait", 2143 | "femme", 2144 | "futures-util", 2145 | "http-types", 2146 | "kv-log-macro", 2147 | "pin-project-lite 0.1.7", 2148 | "route-recognizer", 2149 | "serde", 2150 | "serde_json", 2151 | ] 2152 | 2153 | [[package]] 2154 | name = "time" 2155 | version = "0.1.43" 2156 | source = "registry+https://github.com/rust-lang/crates.io-index" 2157 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 2158 | dependencies = [ 2159 | "libc", 2160 | "winapi", 2161 | ] 2162 | 2163 | [[package]] 2164 | name = "time" 2165 | version = "0.2.16" 2166 | source = "registry+https://github.com/rust-lang/crates.io-index" 2167 | checksum = "3a51cadc5b1eec673a685ff7c33192ff7b7603d0b75446fb354939ee615acb15" 2168 | dependencies = [ 2169 | "cfg-if 0.1.10", 2170 | "libc", 2171 | "standback", 2172 | "stdweb", 2173 | "time-macros", 2174 | "version_check", 2175 | "winapi", 2176 | ] 2177 | 2178 | [[package]] 2179 | name = "time-macros" 2180 | version = "0.1.0" 2181 | source = "registry+https://github.com/rust-lang/crates.io-index" 2182 | checksum = "9ae9b6e9f095bc105e183e3cd493d72579be3181ad4004fceb01adbe9eecab2d" 2183 | dependencies = [ 2184 | "proc-macro-hack", 2185 | "time-macros-impl", 2186 | ] 2187 | 2188 | [[package]] 2189 | name = "time-macros-impl" 2190 | version = "0.1.1" 2191 | source = "registry+https://github.com/rust-lang/crates.io-index" 2192 | checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" 2193 | dependencies = [ 2194 | "proc-macro-hack", 2195 | "proc-macro2", 2196 | "quote", 2197 | "standback", 2198 | "syn", 2199 | ] 2200 | 2201 | [[package]] 2202 | name = "tinyvec" 2203 | version = "0.3.3" 2204 | source = "registry+https://github.com/rust-lang/crates.io-index" 2205 | checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" 2206 | 2207 | [[package]] 2208 | name = "treeline" 2209 | version = "0.1.0" 2210 | source = "registry+https://github.com/rust-lang/crates.io-index" 2211 | checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" 2212 | 2213 | [[package]] 2214 | name = "typenum" 2215 | version = "1.12.0" 2216 | source = "registry+https://github.com/rust-lang/crates.io-index" 2217 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 2218 | 2219 | [[package]] 2220 | name = "unicode-bidi" 2221 | version = "0.3.4" 2222 | source = "registry+https://github.com/rust-lang/crates.io-index" 2223 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 2224 | dependencies = [ 2225 | "matches", 2226 | ] 2227 | 2228 | [[package]] 2229 | name = "unicode-normalization" 2230 | version = "0.1.13" 2231 | source = "registry+https://github.com/rust-lang/crates.io-index" 2232 | checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" 2233 | dependencies = [ 2234 | "tinyvec", 2235 | ] 2236 | 2237 | [[package]] 2238 | name = "unicode-segmentation" 2239 | version = "1.6.0" 2240 | source = "registry+https://github.com/rust-lang/crates.io-index" 2241 | checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 2242 | 2243 | [[package]] 2244 | name = "unicode-xid" 2245 | version = "0.2.1" 2246 | source = "registry+https://github.com/rust-lang/crates.io-index" 2247 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 2248 | 2249 | [[package]] 2250 | name = "universal-hash" 2251 | version = "0.4.0" 2252 | source = "registry+https://github.com/rust-lang/crates.io-index" 2253 | checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" 2254 | dependencies = [ 2255 | "generic-array", 2256 | "subtle", 2257 | ] 2258 | 2259 | [[package]] 2260 | name = "url" 2261 | version = "2.1.1" 2262 | source = "registry+https://github.com/rust-lang/crates.io-index" 2263 | checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" 2264 | dependencies = [ 2265 | "idna", 2266 | "matches", 2267 | "percent-encoding", 2268 | "serde", 2269 | ] 2270 | 2271 | [[package]] 2272 | name = "vcpkg" 2273 | version = "0.2.10" 2274 | source = "registry+https://github.com/rust-lang/crates.io-index" 2275 | checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" 2276 | 2277 | [[package]] 2278 | name = "vec-arena" 2279 | version = "1.0.0" 2280 | source = "registry+https://github.com/rust-lang/crates.io-index" 2281 | checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" 2282 | 2283 | [[package]] 2284 | name = "version_check" 2285 | version = "0.9.2" 2286 | source = "registry+https://github.com/rust-lang/crates.io-index" 2287 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 2288 | 2289 | [[package]] 2290 | name = "waker-fn" 2291 | version = "1.0.0" 2292 | source = "registry+https://github.com/rust-lang/crates.io-index" 2293 | checksum = "9571542c2ce85ce642e6b58b3364da2fb53526360dfb7c211add4f5c23105ff7" 2294 | 2295 | [[package]] 2296 | name = "wasi" 2297 | version = "0.9.0+wasi-snapshot-preview1" 2298 | source = "registry+https://github.com/rust-lang/crates.io-index" 2299 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 2300 | 2301 | [[package]] 2302 | name = "wasm-bindgen" 2303 | version = "0.2.67" 2304 | source = "registry+https://github.com/rust-lang/crates.io-index" 2305 | checksum = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c" 2306 | dependencies = [ 2307 | "cfg-if 0.1.10", 2308 | "serde", 2309 | "serde_json", 2310 | "wasm-bindgen-macro", 2311 | ] 2312 | 2313 | [[package]] 2314 | name = "wasm-bindgen-backend" 2315 | version = "0.2.67" 2316 | source = "registry+https://github.com/rust-lang/crates.io-index" 2317 | checksum = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0" 2318 | dependencies = [ 2319 | "bumpalo", 2320 | "lazy_static", 2321 | "log", 2322 | "proc-macro2", 2323 | "quote", 2324 | "syn", 2325 | "wasm-bindgen-shared", 2326 | ] 2327 | 2328 | [[package]] 2329 | name = "wasm-bindgen-futures" 2330 | version = "0.4.17" 2331 | source = "registry+https://github.com/rust-lang/crates.io-index" 2332 | checksum = "95f8d235a77f880bcef268d379810ea6c0af2eacfa90b1ad5af731776e0c4699" 2333 | dependencies = [ 2334 | "cfg-if 0.1.10", 2335 | "js-sys", 2336 | "wasm-bindgen", 2337 | "web-sys", 2338 | ] 2339 | 2340 | [[package]] 2341 | name = "wasm-bindgen-macro" 2342 | version = "0.2.67" 2343 | source = "registry+https://github.com/rust-lang/crates.io-index" 2344 | checksum = "97c57cefa5fa80e2ba15641578b44d36e7a64279bc5ed43c6dbaf329457a2ed2" 2345 | dependencies = [ 2346 | "quote", 2347 | "wasm-bindgen-macro-support", 2348 | ] 2349 | 2350 | [[package]] 2351 | name = "wasm-bindgen-macro-support" 2352 | version = "0.2.67" 2353 | source = "registry+https://github.com/rust-lang/crates.io-index" 2354 | checksum = "841a6d1c35c6f596ccea1f82504a192a60378f64b3bb0261904ad8f2f5657556" 2355 | dependencies = [ 2356 | "proc-macro2", 2357 | "quote", 2358 | "syn", 2359 | "wasm-bindgen-backend", 2360 | "wasm-bindgen-shared", 2361 | ] 2362 | 2363 | [[package]] 2364 | name = "wasm-bindgen-shared" 2365 | version = "0.2.67" 2366 | source = "registry+https://github.com/rust-lang/crates.io-index" 2367 | checksum = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092" 2368 | 2369 | [[package]] 2370 | name = "web-sys" 2371 | version = "0.3.44" 2372 | source = "registry+https://github.com/rust-lang/crates.io-index" 2373 | checksum = "dda38f4e5ca63eda02c059d243aa25b5f35ab98451e518c51612cd0f1bd19a47" 2374 | dependencies = [ 2375 | "js-sys", 2376 | "wasm-bindgen", 2377 | ] 2378 | 2379 | [[package]] 2380 | name = "wepoll-sys-stjepang" 2381 | version = "1.0.6" 2382 | source = "registry+https://github.com/rust-lang/crates.io-index" 2383 | checksum = "6fd319e971980166b53e17b1026812ad66c6b54063be879eb182342b55284694" 2384 | dependencies = [ 2385 | "cc", 2386 | ] 2387 | 2388 | [[package]] 2389 | name = "whoami" 2390 | version = "0.9.0" 2391 | source = "registry+https://github.com/rust-lang/crates.io-index" 2392 | checksum = "7884773ab69074615cb8f8425d0e53f11710786158704fca70f53e71b0e05504" 2393 | 2394 | [[package]] 2395 | name = "winapi" 2396 | version = "0.3.9" 2397 | source = "registry+https://github.com/rust-lang/crates.io-index" 2398 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2399 | dependencies = [ 2400 | "winapi-i686-pc-windows-gnu", 2401 | "winapi-x86_64-pc-windows-gnu", 2402 | ] 2403 | 2404 | [[package]] 2405 | name = "winapi-i686-pc-windows-gnu" 2406 | version = "0.4.0" 2407 | source = "registry+https://github.com/rust-lang/crates.io-index" 2408 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2409 | 2410 | [[package]] 2411 | name = "winapi-util" 2412 | version = "0.1.5" 2413 | source = "registry+https://github.com/rust-lang/crates.io-index" 2414 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 2415 | dependencies = [ 2416 | "winapi", 2417 | ] 2418 | 2419 | [[package]] 2420 | name = "winapi-x86_64-pc-windows-gnu" 2421 | version = "0.4.0" 2422 | source = "registry+https://github.com/rust-lang/crates.io-index" 2423 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2424 | --------------------------------------------------------------------------------