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
34 changed files with 1797 additions and 140 deletions
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()),
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue