Implement the http variant of the bank server.
During that process, many shortcomings with the socket server and the bank lib were fixed. I am aware a massive commit like this is not ideal.
This commit is contained in:
parent
c69654a924
commit
dac95b7dae
@ -3,6 +3,7 @@
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/bank/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/http-server/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/socket-server/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
|
1074
Cargo.lock
generated
1074
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -2,5 +2,6 @@
|
||||
|
||||
members = [
|
||||
"bank",
|
||||
"socket-server"
|
||||
"http-server",
|
||||
"socket-server",
|
||||
]
|
78
bank/Cargo.lock
generated
78
bank/Cargo.lock
generated
@ -8,7 +8,7 @@ version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -23,20 +23,20 @@ version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bank-server"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
"anyhow",
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -51,11 +51,11 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -64,9 +64,9 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -75,7 +75,7 @@ version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -84,7 +84,7 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
||||
dependencies = [
|
||||
"quick-error",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -99,7 +99,7 @@ version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -114,8 +114,8 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"log",
|
||||
"env_logger",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -124,7 +124,7 @@ version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -139,7 +139,7 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -148,9 +148,9 @@ version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -165,9 +165,9 @@ version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -176,7 +176,7 @@ version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -185,7 +185,7 @@ version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -194,9 +194,9 @@ version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -211,7 +211,7 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -226,8 +226,8 @@ version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -242,7 +242,7 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1,19 +1,20 @@
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::account::AccountError::{Inactive, InvalidAmount, Overdraw};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AccountError {
|
||||
#[error("can not overdraw account")]
|
||||
Overdraw(),
|
||||
Overdraw,
|
||||
#[error("account is inactive")]
|
||||
Inactive(),
|
||||
Inactive,
|
||||
#[error("amount must be > 0")]
|
||||
InvalidAmount(),
|
||||
InvalidAmount,
|
||||
#[error("account does not exist")]
|
||||
NotFound,
|
||||
#[error("account still has a balance")]
|
||||
AccountNotZero,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -52,33 +53,35 @@ impl Hash for Account {
|
||||
impl Account {
|
||||
#[cfg(test)]
|
||||
pub fn new() -> Self {
|
||||
Self { ..Default::default() }
|
||||
Self {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deposit(&mut self, amount: f64) -> Result<()> {
|
||||
pub fn deposit(&mut self, amount: f64) -> Result<f64, AccountError> {
|
||||
self.check_account(amount)?;
|
||||
self.balance += amount;
|
||||
Ok(())
|
||||
Ok(self.balance)
|
||||
}
|
||||
|
||||
pub fn withdraw(&mut self, amount: f64) -> Result<()> {
|
||||
pub fn withdraw(&mut self, amount: f64) -> Result<f64, AccountError> {
|
||||
self.check_account(amount)?;
|
||||
|
||||
if self.balance - amount < 0 as f64 {
|
||||
bail!(Overdraw());
|
||||
return Err(AccountError::Overdraw);
|
||||
}
|
||||
self.balance -= amount;
|
||||
|
||||
Ok(())
|
||||
Ok(self.balance)
|
||||
}
|
||||
|
||||
fn check_account(&self, amount: f64) -> Result<()> {
|
||||
fn check_account(&self, amount: f64) -> Result<(), AccountError> {
|
||||
if !self.is_active {
|
||||
bail!(Inactive());
|
||||
return Err(AccountError::Inactive);
|
||||
}
|
||||
|
||||
if amount < 0 as f64 {
|
||||
bail!(InvalidAmount());
|
||||
return Err(AccountError::InvalidAmount);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1,38 +1,61 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::RwLock;
|
||||
use std::sync::{RwLock, RwLockReadGuard};
|
||||
|
||||
#[cfg(test)]
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::account::Account;
|
||||
use crate::account::{Account, AccountError};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Bank {
|
||||
pub accounts: HashMap<String, RwLock<Account>>,
|
||||
}
|
||||
|
||||
impl Bank {
|
||||
pub fn new() -> Self {
|
||||
Self { accounts: HashMap::new() }
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn account_action<F: Fn(&mut Account) -> Result<f64, AccountError>>(
|
||||
bank: RwLockReadGuard<'_, Bank>,
|
||||
nr: &str,
|
||||
action: F,
|
||||
) -> Result<f64, AccountError> {
|
||||
match bank.accounts.get(nr) {
|
||||
None => Err(AccountError::NotFound),
|
||||
Some(account) => {
|
||||
let mut account = account.write().unwrap();
|
||||
action(&mut account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn account_numbers(&self) -> HashSet<String> {
|
||||
self.accounts.iter()
|
||||
self.accounts
|
||||
.iter()
|
||||
.filter(|(_, acc)| acc.read().unwrap().is_active)
|
||||
.map(|(_, acc)| acc.read().unwrap().number.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn create_account(&mut self, owner: String) -> String {
|
||||
let acc = Account { owner, ..Default::default() };
|
||||
let number = acc.number.clone();
|
||||
let acc = Account {
|
||||
owner,
|
||||
..Default::default()
|
||||
};
|
||||
let nr = acc.number.clone();
|
||||
|
||||
self.accounts.insert(acc.number.clone(), RwLock::new(acc));
|
||||
|
||||
number
|
||||
nr
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn transfer(&self, from: &mut Account, to: &mut Account, amount: f64) -> Result<()> {
|
||||
pub fn transfer(
|
||||
&self,
|
||||
from: &mut Account,
|
||||
to: &mut Account,
|
||||
amount: f64,
|
||||
) -> Result<(), AccountError> {
|
||||
from.withdraw(amount)?;
|
||||
to.deposit(amount)?;
|
||||
|
||||
@ -83,4 +106,4 @@ mod tests {
|
||||
|
||||
assert_eq!(1, bank.account_numbers().len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
pub mod account;
|
||||
pub mod bank;
|
||||
pub mod bank;
|
||||
|
16
http-server/Cargo.toml
Normal file
16
http-server/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "http-server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bank = { path = "../bank" }
|
||||
actix-web = "4.0.1"
|
||||
anyhow = "1.0.55"
|
||||
thiserror = "1.0.30"
|
||||
log = "0.4.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0.79"
|
29
http-server/src/handlers/close_account.rs
Normal file
29
http-server/src/handlers/close_account.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use actix_web::{post, web, HttpResponse, Responder, Result};
|
||||
|
||||
use bank::account::AccountError;
|
||||
use bank::bank::Bank;
|
||||
|
||||
use crate::handlers::error::HttpAccountError;
|
||||
use crate::AppState;
|
||||
|
||||
#[post("/{nr}/close")]
|
||||
pub async fn route(info: web::Path<String>, data: web::Data<AppState>) -> Result<impl Responder> {
|
||||
let nr = info.into_inner();
|
||||
|
||||
info!("closing account {}...", nr);
|
||||
|
||||
let bank = data.bank.read().unwrap();
|
||||
match Bank::account_action(bank, &nr, |account| {
|
||||
// TODO: make the error handling part of the passivate method
|
||||
if account.balance > 0_f64 {
|
||||
Err(AccountError::AccountNotZero)
|
||||
} else if account.passivate() {
|
||||
Ok(0_f64)
|
||||
} else {
|
||||
Err(AccountError::Inactive)
|
||||
}
|
||||
}) {
|
||||
Err(e) => Err(HttpAccountError(e).into()),
|
||||
Ok(_) => Ok(HttpResponse::Ok().finish()),
|
||||
}
|
||||
}
|
29
http-server/src/handlers/create_account.rs
Normal file
29
http-server/src/handlers/create_account.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use actix_web::{post, web, Responder, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::AppState;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct OwnerData {
|
||||
pub(crate) owner: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AccountCreated {
|
||||
nr: String,
|
||||
}
|
||||
|
||||
#[post("")]
|
||||
pub async fn route(
|
||||
form: web::Form<OwnerData>,
|
||||
data: web::Data<AppState>,
|
||||
) -> Result<impl Responder> {
|
||||
let owner = &form.owner;
|
||||
info!("creating new account with owner {}...", owner);
|
||||
|
||||
let mut bank = data.bank.write().unwrap();
|
||||
let nr = bank.create_account(owner.clone());
|
||||
info!("created account {}", nr);
|
||||
|
||||
Ok(web::Json(AccountCreated { nr }))
|
||||
}
|
31
http-server/src/handlers/deposit.rs
Normal file
31
http-server/src/handlers/deposit.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use actix_web::{post, web, HttpResponse, Responder, Result};
|
||||
use serde::Serialize;
|
||||
|
||||
use bank::bank::Bank;
|
||||
|
||||
use crate::handlers::error::HttpAccountError;
|
||||
use crate::handlers::AmountData;
|
||||
use crate::AppState;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AccountCreated {
|
||||
nr: String,
|
||||
}
|
||||
|
||||
#[post("/{nr}/deposit")]
|
||||
pub async fn route(
|
||||
info: web::Path<String>,
|
||||
form: web::Form<AmountData>,
|
||||
data: web::Data<AppState>,
|
||||
) -> Result<impl Responder> {
|
||||
let nr = info.into_inner();
|
||||
let amount = form.amount;
|
||||
|
||||
info!("depositing {} into account {}...", amount, nr);
|
||||
|
||||
let bank = data.bank.read().unwrap();
|
||||
match Bank::account_action(bank, &nr, |account| account.deposit(amount)) {
|
||||
Err(e) => Err(HttpAccountError(e).into()),
|
||||
Ok(_) => Ok(HttpResponse::Ok().finish()),
|
||||
}
|
||||
}
|
53
http-server/src/handlers/error.rs
Normal file
53
http-server/src/handlers/error.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{HttpResponse, ResponseError};
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
||||
use bank::account::AccountError;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ErrorResponse {
|
||||
code: u16,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub struct HttpAccountError(pub AccountError);
|
||||
|
||||
impl Display for HttpAccountError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.deref())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for HttpAccountError {
|
||||
type Target = AccountError;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for HttpAccountError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self.deref() {
|
||||
AccountError::NotFound => StatusCode::NOT_FOUND,
|
||||
AccountError::InvalidAmount => StatusCode::BAD_REQUEST,
|
||||
AccountError::Inactive => StatusCode::BAD_REQUEST,
|
||||
AccountError::Overdraw => StatusCode::BAD_REQUEST,
|
||||
AccountError::AccountNotZero => StatusCode::EXPECTATION_FAILED,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let status_code = self.status_code();
|
||||
let error_response = ErrorResponse {
|
||||
code: status_code.as_u16(),
|
||||
message: self.to_string(),
|
||||
};
|
||||
HttpResponse::build(status_code).json(error_response)
|
||||
}
|
||||
}
|
44
http-server/src/handlers/get_account.rs
Normal file
44
http-server/src/handlers/get_account.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use actix_web::{get, web, Responder, Result};
|
||||
use serde::Serialize;
|
||||
|
||||
use bank::account::{Account, AccountError};
|
||||
|
||||
use crate::handlers::error::HttpAccountError;
|
||||
use crate::AppState;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct JsonAccount {
|
||||
pub number: String,
|
||||
pub owner: String,
|
||||
pub balance: f64,
|
||||
pub is_active: bool,
|
||||
}
|
||||
|
||||
impl From<&Account> for JsonAccount {
|
||||
fn from(a: &Account) -> Self {
|
||||
JsonAccount {
|
||||
number: a.number.clone(),
|
||||
owner: a.owner.clone(),
|
||||
balance: a.balance,
|
||||
is_active: a.is_active,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/{nr}")]
|
||||
pub async fn route(info: web::Path<String>, data: web::Data<AppState>) -> Result<impl Responder> {
|
||||
let nr = info.into_inner();
|
||||
info!("getting account {}...", nr);
|
||||
|
||||
let bank = data.bank.read().unwrap();
|
||||
match bank.accounts.get(&nr) {
|
||||
None => Err(HttpAccountError(AccountError::NotFound).into()),
|
||||
Some(account) => {
|
||||
let account = account.read().unwrap();
|
||||
let account: JsonAccount = account.deref().into();
|
||||
Ok(web::Json(account))
|
||||
}
|
||||
}
|
||||
}
|
11
http-server/src/handlers/get_account_nrs.rs
Normal file
11
http-server/src/handlers/get_account_nrs.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use actix_web::{get, web, Responder, Result};
|
||||
|
||||
use crate::AppState;
|
||||
|
||||
#[get("")]
|
||||
pub async fn route(data: web::Data<AppState>) -> Result<impl Responder> {
|
||||
info!("getting account numbers...");
|
||||
|
||||
let bank = data.bank.read().unwrap();
|
||||
Ok(web::Json(bank.account_numbers()))
|
||||
}
|
16
http-server/src/handlers/mod.rs
Normal file
16
http-server/src/handlers/mod.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod close_account;
|
||||
pub mod create_account;
|
||||
pub mod deposit;
|
||||
pub mod error;
|
||||
pub mod get_account;
|
||||
pub mod get_account_nrs;
|
||||
pub mod pong;
|
||||
pub mod transfer;
|
||||
pub mod withdraw;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AmountData {
|
||||
pub(crate) amount: f64,
|
||||
}
|
6
http-server/src/handlers/pong.rs
Normal file
6
http-server/src/handlers/pong.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use actix_web::{get, HttpResponse};
|
||||
|
||||
#[get("/ping")]
|
||||
pub async fn route() -> HttpResponse {
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
44
http-server/src/handlers/transfer.rs
Normal file
44
http-server/src/handlers/transfer.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use actix_web::{post, web, HttpResponse, Responder, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use bank::account::AccountError;
|
||||
|
||||
use crate::handlers::error::HttpAccountError;
|
||||
use crate::AppState;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct TransferData {
|
||||
pub(crate) from: String,
|
||||
pub(crate) to: String,
|
||||
pub(crate) amount: f64,
|
||||
}
|
||||
|
||||
#[post("/transfer")]
|
||||
pub async fn route(
|
||||
form: web::Form<TransferData>,
|
||||
data: web::Data<AppState>,
|
||||
) -> Result<impl Responder> {
|
||||
let from = form.from.clone();
|
||||
let to = form.to.clone();
|
||||
let amount = form.amount;
|
||||
|
||||
info!("transfering {} from {} to {}...", from, to, amount);
|
||||
|
||||
let bank = data.bank.read().unwrap();
|
||||
let from_acc = bank
|
||||
.accounts
|
||||
.get(&from)
|
||||
.ok_or(HttpAccountError(AccountError::NotFound))?;
|
||||
let mut from_acc = from_acc.write().unwrap();
|
||||
|
||||
let to_acc = bank
|
||||
.accounts
|
||||
.get(&to)
|
||||
.ok_or(HttpAccountError(AccountError::NotFound))?;
|
||||
let mut to_acc = to_acc.write().unwrap();
|
||||
|
||||
match bank.transfer(&mut from_acc, &mut to_acc, amount) {
|
||||
Err(e) => Err(HttpAccountError(e).into()),
|
||||
Ok(_) => Ok(HttpResponse::Ok().finish()),
|
||||
}
|
||||
}
|
31
http-server/src/handlers/withdraw.rs
Normal file
31
http-server/src/handlers/withdraw.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use actix_web::{post, web, HttpResponse, Responder, Result};
|
||||
use serde::Serialize;
|
||||
|
||||
use bank::bank::Bank;
|
||||
|
||||
use crate::handlers::error::HttpAccountError;
|
||||
use crate::handlers::AmountData;
|
||||
use crate::AppState;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AccountCreated {
|
||||
nr: String,
|
||||
}
|
||||
|
||||
#[post("/{nr}/withdraw")]
|
||||
pub async fn route(
|
||||
info: web::Path<String>,
|
||||
form: web::Form<AmountData>,
|
||||
data: web::Data<AppState>,
|
||||
) -> Result<impl Responder> {
|
||||
let nr = info.into_inner();
|
||||
let amount = form.amount;
|
||||
|
||||
info!("withdrawing {} from account {}...", amount, nr);
|
||||
|
||||
let bank = data.bank.read().unwrap();
|
||||
match Bank::account_action(bank, &nr, |account| account.withdraw(amount)) {
|
||||
Err(e) => Err(HttpAccountError(e).into()),
|
||||
Ok(_) => Ok(HttpResponse::Ok().finish()),
|
||||
}
|
||||
}
|
204
http-server/src/main.rs
Normal file
204
http-server/src/main.rs
Normal file
@ -0,0 +1,204 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::sync::RwLock;
|
||||
|
||||
use actix_web::middleware::TrailingSlash;
|
||||
use actix_web::web::{Data, ServiceConfig};
|
||||
use actix_web::{middleware, web, App, HttpServer};
|
||||
|
||||
use bank::bank::Bank;
|
||||
|
||||
use crate::handlers::close_account;
|
||||
use crate::handlers::create_account;
|
||||
use crate::handlers::deposit;
|
||||
use crate::handlers::get_account;
|
||||
use crate::handlers::get_account_nrs;
|
||||
use crate::handlers::pong;
|
||||
use crate::handlers::transfer;
|
||||
use crate::handlers::withdraw;
|
||||
|
||||
pub struct AppState {
|
||||
bank: RwLock<Bank>,
|
||||
}
|
||||
|
||||
mod handlers;
|
||||
|
||||
fn config_app(app_data: Data<AppState>) -> Box<dyn Fn(&mut ServiceConfig)> {
|
||||
Box::new(move |cfg: &mut ServiceConfig| {
|
||||
cfg.app_data(app_data.clone()).service(pong::route).service(
|
||||
web::scope("/accounts")
|
||||
.service(create_account::route)
|
||||
.service(get_account::route)
|
||||
.service(deposit::route)
|
||||
.service(close_account::route)
|
||||
.service(get_account_nrs::route)
|
||||
.service(withdraw::route)
|
||||
.service(transfer::route),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let app_data = web::Data::new(AppState {
|
||||
bank: RwLock::new(Bank::new()),
|
||||
});
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(middleware::NormalizePath::new(TrailingSlash::Trim))
|
||||
.configure(config_app(app_data.clone()))
|
||||
})
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::body::to_bytes;
|
||||
use actix_web::{test, App};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::create_account::OwnerData;
|
||||
use crate::handlers::AmountData;
|
||||
use crate::transfer::TransferData;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[actix_web::test]
|
||||
async fn test_routes() {
|
||||
// TODO: split up into separate tests
|
||||
|
||||
let app_data = web::Data::new(AppState {
|
||||
bank: RwLock::new(Bank::new()),
|
||||
});
|
||||
|
||||
let app = test::init_service(
|
||||
App::new()
|
||||
.wrap(middleware::NormalizePath::new(TrailingSlash::Trim))
|
||||
.configure(config_app(app_data)),
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = OwnerData {
|
||||
owner: "aaa".to_string(),
|
||||
};
|
||||
let req = test::TestRequest::post()
|
||||
.uri("/accounts")
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
||||
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
||||
let nr = response_json["nr"].as_str().unwrap();
|
||||
|
||||
assert!(!nr.is_empty());
|
||||
|
||||
let payload = AmountData { amount: 10_f64 };
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/accounts/{}/withdraw", nr))
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(!resp.status().is_success());
|
||||
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/accounts/{}/deposit", nr))
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/accounts/{}", nr))
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
||||
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
||||
let balance = response_json["balance"].as_f64().unwrap();
|
||||
assert_eq!(10_f64, balance);
|
||||
|
||||
let payload = OwnerData {
|
||||
owner: "bbb".to_string(),
|
||||
};
|
||||
let req = test::TestRequest::post()
|
||||
.uri("/accounts")
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
||||
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
||||
let nr2 = response_json["nr"].as_str().unwrap();
|
||||
|
||||
let payload = TransferData {
|
||||
from: nr.to_string(),
|
||||
to: nr2.to_string(),
|
||||
amount: 5_f64,
|
||||
};
|
||||
let req = test::TestRequest::post()
|
||||
.uri("/accounts/transfer")
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let req = test::TestRequest::get()
|
||||
.uri(&format!("/accounts/{}", nr2))
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
||||
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
||||
let balance = response_json["balance"].as_f64().unwrap();
|
||||
assert_eq!(5_f64, balance);
|
||||
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/accounts/{}/close", nr))
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(!resp.status().is_success());
|
||||
|
||||
let payload = AmountData { amount: 5_f64 };
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/accounts/{}/withdraw", nr))
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let req = test::TestRequest::get().uri("/accounts").to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
||||
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
||||
let nrs = response_json.as_array().unwrap();
|
||||
assert_eq!(2, nrs.len());
|
||||
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/accounts/{}/close", nr))
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let req = test::TestRequest::get().uri("/accounts").to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
||||
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
||||
let nrs = response_json.as_array().unwrap();
|
||||
assert_eq!(1, nrs.len());
|
||||
}
|
||||
}
|
@ -7,4 +7,5 @@ edition = "2021"
|
||||
bank = { path = "../bank" }
|
||||
anyhow = "1.0.55"
|
||||
log = "0.4.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
pretty_env_logger = "0.4.0"
|
||||
thiserror = "1.0.30"
|
@ -12,7 +12,12 @@ use crate::protocol;
|
||||
pub struct CloseAccount;
|
||||
|
||||
impl Command for CloseAccount {
|
||||
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
|
||||
fn execute(
|
||||
&self,
|
||||
bank: Arc<RwLock<Bank>>,
|
||||
data: &[u8],
|
||||
mut stream: &TcpStream,
|
||||
) -> Result<usize> {
|
||||
debug!("account nr bytes {:?}", data);
|
||||
|
||||
let nr = String::from_utf8_lossy(data).to_string();
|
||||
@ -25,9 +30,9 @@ impl Command for CloseAccount {
|
||||
let mut acc = acc.write().unwrap();
|
||||
stream.write(&protocol::account_passivate(acc.passivate()))?
|
||||
}
|
||||
None => stream.write(&protocol::account_passivate(false))?
|
||||
None => stream.write(&protocol::account_passivate(false))?,
|
||||
};
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,12 @@ use crate::protocol;
|
||||
pub struct CreateAccount;
|
||||
|
||||
impl Command for CreateAccount {
|
||||
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
|
||||
fn execute(
|
||||
&self,
|
||||
bank: Arc<RwLock<Bank>>,
|
||||
data: &[u8],
|
||||
mut stream: &TcpStream,
|
||||
) -> Result<usize> {
|
||||
debug!("owner nr bytes {:?}", data);
|
||||
|
||||
let owner = String::from_utf8_lossy(data);
|
||||
@ -25,4 +30,4 @@ impl Command for CreateAccount {
|
||||
let written = stream.write(&protocol::account_nr(&nr))?;
|
||||
Ok(written)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,12 @@ use crate::protocol;
|
||||
pub struct Deposit;
|
||||
|
||||
impl Command for Deposit {
|
||||
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
|
||||
fn execute(
|
||||
&self,
|
||||
bank: Arc<RwLock<Bank>>,
|
||||
data: &[u8],
|
||||
mut stream: &TcpStream,
|
||||
) -> Result<usize> {
|
||||
let value_bytes: [u8; 8] = <[u8; 8]>::try_from(data[..8].to_vec().as_slice())?;
|
||||
debug!("value bytes {:?}", value_bytes);
|
||||
|
||||
@ -30,9 +35,9 @@ impl Command for Deposit {
|
||||
let mut acc = acc.write().unwrap();
|
||||
stream.write(&protocol::deposit(acc.deposit(amount)))?
|
||||
}
|
||||
None => stream.write(&protocol::error(2))?
|
||||
None => stream.write(&protocol::error(2))?,
|
||||
};
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
35
socket-server/src/commands/error.rs
Normal file
35
socket-server/src/commands/error.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use bank::account::AccountError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub struct SocketAccountError(pub AccountError);
|
||||
|
||||
impl Display for SocketAccountError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.deref())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SocketAccountError {
|
||||
type Target = AccountError;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SocketAccountError> for u8 {
|
||||
fn from(e: SocketAccountError) -> Self {
|
||||
match e.deref() {
|
||||
AccountError::Overdraw => 11,
|
||||
AccountError::Inactive => 12,
|
||||
AccountError::InvalidAmount => 13,
|
||||
AccountError::NotFound => 14,
|
||||
AccountError::AccountNotZero => 15,
|
||||
}
|
||||
}
|
||||
}
|
@ -12,9 +12,14 @@ use crate::protocol;
|
||||
pub struct Fail;
|
||||
|
||||
impl Command for Fail {
|
||||
fn execute(&self, _: Arc<RwLock<Bank>>, error_code: &[u8], mut stream: &TcpStream) -> Result<usize> {
|
||||
fn execute(
|
||||
&self,
|
||||
_: Arc<RwLock<Bank>>,
|
||||
error_code: &[u8],
|
||||
mut stream: &TcpStream,
|
||||
) -> Result<usize> {
|
||||
error!("sending error code {}", error_code[0]);
|
||||
let written = stream.write(&protocol::error(error_code[0]))?;
|
||||
Ok(written)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,12 @@ use crate::protocol;
|
||||
pub struct GetAccount;
|
||||
|
||||
impl Command for GetAccount {
|
||||
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
|
||||
fn execute(
|
||||
&self,
|
||||
bank: Arc<RwLock<Bank>>,
|
||||
data: &[u8],
|
||||
mut stream: &TcpStream,
|
||||
) -> Result<usize> {
|
||||
debug!("account nr bytes {:?}", data);
|
||||
let nr = String::from_utf8_lossy(data).to_string();
|
||||
|
||||
@ -25,9 +30,9 @@ impl Command for GetAccount {
|
||||
let acc = acc.read().unwrap();
|
||||
stream.write(&protocol::account(&acc))?
|
||||
}
|
||||
None => stream.write(&protocol::error(2))?
|
||||
None => stream.write(&protocol::error(2))?,
|
||||
};
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,10 @@ impl Command for GetAccountNrs {
|
||||
info!("getting account numbers...");
|
||||
|
||||
let bank = bank.read().unwrap();
|
||||
let nrs: Vec<String> = bank.account_numbers()
|
||||
.into_iter()
|
||||
.collect();
|
||||
let nrs: Vec<String> = bank.account_numbers().into_iter().collect();
|
||||
|
||||
let written = stream.write(&protocol::account_nrs(&nrs))?;
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ use std::sync::{Arc, RwLock};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use bank::account::AccountError;
|
||||
use bank::bank::Bank;
|
||||
|
||||
use crate::commands::close_account::CloseAccount;
|
||||
@ -16,13 +15,14 @@ use crate::commands::get_account_nrs::GetAccountNrs;
|
||||
use crate::commands::pong::Pong;
|
||||
use crate::commands::withdraw::Withdraw;
|
||||
|
||||
mod pong;
|
||||
mod fail;
|
||||
mod close_account;
|
||||
mod create_account;
|
||||
mod deposit;
|
||||
pub mod error;
|
||||
mod fail;
|
||||
mod get_account;
|
||||
mod get_account_nrs;
|
||||
mod pong;
|
||||
mod withdraw;
|
||||
|
||||
pub trait Command: Sync + Send {
|
||||
@ -33,26 +33,6 @@ pub struct Commands {
|
||||
cmds: HashMap<u8, Box<dyn Command>>,
|
||||
}
|
||||
|
||||
struct WebAccountError(AccountError);
|
||||
|
||||
impl From<&WebAccountError> for u8 {
|
||||
fn from(error: &WebAccountError) -> Self {
|
||||
match error.0 {
|
||||
AccountError::Overdraw() => 11,
|
||||
AccountError::Inactive() => 12,
|
||||
AccountError::InvalidAmount() => 13,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error_to_code(error: &AccountError) -> u8 {
|
||||
match error {
|
||||
AccountError::Overdraw() => 11,
|
||||
AccountError::Inactive() => 12,
|
||||
AccountError::InvalidAmount() => 13,
|
||||
}
|
||||
}
|
||||
|
||||
impl Commands {
|
||||
pub fn new() -> Self {
|
||||
let mut cmds = HashMap::new();
|
||||
@ -64,15 +44,19 @@ impl Commands {
|
||||
cmds.insert(6_u8, Box::new(Deposit) as Box<dyn Command>);
|
||||
cmds.insert(7_u8, Box::new(Withdraw) as Box<dyn Command>);
|
||||
|
||||
Self {
|
||||
cmds
|
||||
}
|
||||
Self { cmds }
|
||||
}
|
||||
|
||||
pub fn run(&self, bank: Arc<RwLock<Bank>>, cmd: u8, data: &[u8], stream: &TcpStream) -> Result<usize> {
|
||||
pub fn run(
|
||||
&self,
|
||||
bank: Arc<RwLock<Bank>>,
|
||||
cmd: u8,
|
||||
data: &[u8],
|
||||
stream: &TcpStream,
|
||||
) -> Result<usize> {
|
||||
match self.cmds.get(&cmd) {
|
||||
None => Fail.execute(bank, &[1_u8], stream),
|
||||
Some(cmd) => cmd.execute(bank, data, stream)
|
||||
Some(cmd) => cmd.execute(bank, data, stream),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,4 +17,4 @@ impl Command for Pong {
|
||||
let written = stream.write(&[protocol::PONG])?;
|
||||
Ok(written)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,12 @@ use crate::protocol;
|
||||
pub struct Withdraw;
|
||||
|
||||
impl Command for Withdraw {
|
||||
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
|
||||
fn execute(
|
||||
&self,
|
||||
bank: Arc<RwLock<Bank>>,
|
||||
data: &[u8],
|
||||
mut stream: &TcpStream,
|
||||
) -> Result<usize> {
|
||||
let value_bytes: [u8; 8] = <[u8; 8]>::try_from(data[..8].to_vec().as_slice())?;
|
||||
debug!("value bytes {:?}", value_bytes);
|
||||
|
||||
@ -24,15 +29,14 @@ impl Command for Withdraw {
|
||||
info!("withdrawing {} from {}...", amount, nr);
|
||||
|
||||
let bank = bank.read().unwrap();
|
||||
|
||||
let written = match bank.accounts.get(&nr) {
|
||||
Some(acc) => {
|
||||
let mut acc = acc.write().unwrap();
|
||||
stream.write(&protocol::withdraw(acc.withdraw(amount)))?
|
||||
}
|
||||
None => stream.write(&protocol::error(2))?
|
||||
None => stream.write(&protocol::error(2))?,
|
||||
};
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
mod commands;
|
||||
mod protocol;
|
||||
mod server;
|
||||
mod threadpool;
|
||||
mod commands;
|
||||
|
||||
fn main() {
|
||||
pretty_env_logger::init();
|
||||
|
@ -2,18 +2,11 @@ use anyhow::Result;
|
||||
|
||||
use bank::account::{Account, AccountError};
|
||||
|
||||
use crate::commands::error_to_code;
|
||||
use crate::commands::error::SocketAccountError;
|
||||
|
||||
pub const START: [u8; 2] = [0xde, 0xad];
|
||||
pub const PONG: u8 = 0b0010_0000;
|
||||
|
||||
fn to_error_code(error: anyhow::Error, default: u8) -> u8 {
|
||||
match error.root_cause().downcast_ref::<AccountError>() {
|
||||
Some(e) => error_to_code(e),
|
||||
None => default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn account_nr(nr: &str) -> Vec<u8> {
|
||||
let mut response = vec![PONG];
|
||||
response.append(&mut nr.as_bytes().to_vec());
|
||||
@ -26,17 +19,17 @@ pub fn account_passivate(was_passivated: bool) -> Vec<u8> {
|
||||
vec![PONG | is_active_byte]
|
||||
}
|
||||
|
||||
pub fn deposit(result: Result<()>) -> Vec<u8> {
|
||||
pub fn deposit(result: Result<f64, AccountError>) -> Vec<u8> {
|
||||
match result {
|
||||
Err(e) => error(to_error_code(e, 10)).to_vec(),
|
||||
Ok(_) => vec![PONG]
|
||||
Err(e) => error(SocketAccountError(e).into()).to_vec(),
|
||||
Ok(_) => vec![PONG],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn withdraw(result: Result<()>) -> Vec<u8> {
|
||||
pub fn withdraw(result: Result<f64, AccountError>) -> Vec<u8> {
|
||||
match result {
|
||||
Err(e) => error(to_error_code(e, 10)).to_vec(),
|
||||
Ok(_) => vec![PONG]
|
||||
Err(e) => error(SocketAccountError(e).into()).to_vec(),
|
||||
Ok(_) => vec![PONG],
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,4 +78,4 @@ pub fn account(account: &Account) -> Vec<u8> {
|
||||
|
||||
pub fn error(code: u8) -> [u8; 1] {
|
||||
[0b0100_0000 | code]
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,11 @@ pub fn run(host: &str, threads: usize) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_connection(bank: Arc<RwLock<Bank>>, cmds: &Commands, mut stream: TcpStream) -> Result<()> {
|
||||
fn handle_connection(
|
||||
bank: Arc<RwLock<Bank>>,
|
||||
cmds: &Commands,
|
||||
mut stream: TcpStream,
|
||||
) -> Result<()> {
|
||||
const BUF_SIZE: usize = 64;
|
||||
|
||||
let mut data: Vec<u8> = vec![];
|
||||
@ -58,4 +62,3 @@ fn handle_connection(bank: Arc<RwLock<Bank>>, cmds: &Commands, mut stream: TcpSt
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
|
||||
@ -33,8 +33,8 @@ impl ThreadPool {
|
||||
}
|
||||
|
||||
pub fn execute<F>(&self, f: F)
|
||||
where
|
||||
F: FnOnce() + Send + 'static,
|
||||
where
|
||||
F: FnOnce() + Send + 'static,
|
||||
{
|
||||
let job = Box::new(f);
|
||||
|
||||
@ -91,4 +91,4 @@ impl Worker {
|
||||
thread: Some(thread),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user