add http-client
This commit is contained in:
parent
ac2904588c
commit
8bcd555d71
36 changed files with 1978 additions and 49 deletions
|
@ -5,6 +5,8 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
bank = { path = "../bank" }
|
||||
http-lib = { path = "../http-lib" }
|
||||
actix-cors = "0.6.1"
|
||||
actix-web = "4.0.1"
|
||||
thiserror = "1.0.30"
|
||||
log = "0.4.14"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use actix_web::{post, web, HttpResponse, Responder, Result};
|
||||
use actix_web::{delete, web, HttpResponse, Responder, Result};
|
||||
|
||||
use bank::account::AccountError;
|
||||
use bank::bank::Bank;
|
||||
|
@ -6,7 +6,7 @@ use bank::bank::Bank;
|
|||
use crate::handlers::error::HttpAccountError;
|
||||
use crate::AppState;
|
||||
|
||||
#[post("/{nr}/close")]
|
||||
#[delete("/{nr}")]
|
||||
pub async fn route(info: web::Path<String>, data: web::Data<AppState>) -> Result<impl Responder> {
|
||||
let nr = info.into_inner();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use actix_web::{post, web, Responder, Result};
|
||||
use actix_web::{post, web, HttpResponse, Responder, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::AppState;
|
||||
|
@ -25,5 +25,5 @@ pub async fn route(
|
|||
let nr = bank.create_account(owner.clone());
|
||||
info!("created account {}", nr);
|
||||
|
||||
Ok(web::Json(AccountCreated { nr }))
|
||||
Ok(HttpResponse::Created().json(AccountCreated { nr }))
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use actix_web::{post, web, HttpResponse, Responder, Result};
|
||||
use actix_web::{patch, web, HttpResponse, Responder, Result};
|
||||
use serde::Serialize;
|
||||
|
||||
use bank::bank::Bank;
|
||||
|
@ -12,7 +12,7 @@ struct AccountCreated {
|
|||
nr: String,
|
||||
}
|
||||
|
||||
#[post("/{nr}/deposit")]
|
||||
#[patch("/{nr}/deposit")]
|
||||
pub async fn route(
|
||||
info: web::Path<String>,
|
||||
form: web::Form<AmountData>,
|
||||
|
|
|
@ -36,9 +36,9 @@ impl ResponseError for HttpAccountError {
|
|||
match self.deref() {
|
||||
AccountError::NotFound => StatusCode::NOT_FOUND,
|
||||
AccountError::InvalidAmount => StatusCode::BAD_REQUEST,
|
||||
AccountError::Inactive => StatusCode::BAD_REQUEST,
|
||||
AccountError::Inactive => StatusCode::GONE,
|
||||
AccountError::Overdraw => StatusCode::BAD_REQUEST,
|
||||
AccountError::AccountNotZero => StatusCode::EXPECTATION_FAILED,
|
||||
AccountError::AccountNotZero => StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,32 +1,13 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use actix_web::{get, web, Responder, Result};
|
||||
use serde::Serialize;
|
||||
|
||||
use bank::account::{Account, AccountError};
|
||||
use bank::account::AccountError;
|
||||
use http_lib::json_account::JsonAccount;
|
||||
|
||||
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();
|
||||
|
|
31
http-server/src/handlers/is_active.rs
Normal file
31
http-server/src/handlers/is_active.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use actix_web::{head, web, HttpResponse, Responder, Result};
|
||||
use bank::account::AccountError;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::handlers::error::HttpAccountError;
|
||||
use crate::AppState;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AccountCreated {
|
||||
nr: String,
|
||||
}
|
||||
|
||||
#[head("/{nr}")]
|
||||
pub async fn route(info: web::Path<String>, data: web::Data<AppState>) -> Result<impl Responder> {
|
||||
let nr = info.into_inner();
|
||||
|
||||
info!("checking 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();
|
||||
if account.is_active {
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
} else {
|
||||
Err(HttpAccountError(AccountError::Inactive).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,8 +6,10 @@ pub mod deposit;
|
|||
pub mod error;
|
||||
pub mod get_account;
|
||||
pub mod get_account_nrs;
|
||||
pub mod is_active;
|
||||
pub mod pong;
|
||||
pub mod transfer;
|
||||
pub mod update_account;
|
||||
pub mod withdraw;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use actix_web::{post, web, HttpResponse, Responder, Result};
|
||||
use actix_web::{patch, web, HttpResponse, Responder, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use bank::account::AccountError;
|
||||
|
@ -13,7 +13,7 @@ pub struct TransferData {
|
|||
pub(crate) amount: f64,
|
||||
}
|
||||
|
||||
#[post("/transfer")]
|
||||
#[patch("/transfer")]
|
||||
pub async fn route(
|
||||
form: web::Form<TransferData>,
|
||||
data: web::Data<AppState>,
|
||||
|
|
39
http-server/src/handlers/update_account.rs
Normal file
39
http-server/src/handlers/update_account.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use actix_web::{put, web, HttpResponse, Responder, Result};
|
||||
use bank::account::AccountError;
|
||||
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,
|
||||
}
|
||||
|
||||
#[put("/{nr}")]
|
||||
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;
|
||||
|
||||
if amount <= 0_f64 {
|
||||
return Err(HttpAccountError(AccountError::InvalidAmount).into());
|
||||
}
|
||||
|
||||
info!("updating account {}...", nr);
|
||||
|
||||
let bank = data.bank.read().unwrap();
|
||||
match Bank::account_action(bank, &nr, |account| {
|
||||
account.balance = amount;
|
||||
Ok(amount)
|
||||
}) {
|
||||
Err(e) => Err(HttpAccountError(e).into()),
|
||||
Ok(_) => Ok(HttpResponse::Ok().finish()),
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use actix_web::{post, web, HttpResponse, Responder, Result};
|
||||
use actix_web::{patch, web, HttpResponse, Responder, Result};
|
||||
use serde::Serialize;
|
||||
|
||||
use bank::bank::Bank;
|
||||
|
@ -12,7 +12,7 @@ struct AccountCreated {
|
|||
nr: String,
|
||||
}
|
||||
|
||||
#[post("/{nr}/withdraw")]
|
||||
#[patch("/{nr}/withdraw")]
|
||||
pub async fn route(
|
||||
info: web::Path<String>,
|
||||
form: web::Form<AmountData>,
|
||||
|
|
|
@ -3,6 +3,7 @@ extern crate log;
|
|||
|
||||
use std::sync::RwLock;
|
||||
|
||||
use actix_cors::Cors;
|
||||
use actix_web::middleware::TrailingSlash;
|
||||
use actix_web::web::{Data, ServiceConfig};
|
||||
use actix_web::{middleware, web, App, HttpServer};
|
||||
|
@ -14,8 +15,10 @@ use crate::handlers::create_account;
|
|||
use crate::handlers::deposit;
|
||||
use crate::handlers::get_account;
|
||||
use crate::handlers::get_account_nrs;
|
||||
use crate::handlers::is_active;
|
||||
use crate::handlers::pong;
|
||||
use crate::handlers::transfer;
|
||||
use crate::handlers::update_account;
|
||||
use crate::handlers::withdraw;
|
||||
|
||||
pub struct AppState {
|
||||
|
@ -34,7 +37,9 @@ fn config_app(app_data: Data<AppState>) -> Box<dyn Fn(&mut ServiceConfig)> {
|
|||
.service(close_account::route)
|
||||
.service(get_account_nrs::route)
|
||||
.service(withdraw::route)
|
||||
.service(transfer::route),
|
||||
.service(transfer::route)
|
||||
.service(is_active::route)
|
||||
.service(update_account::route),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
@ -48,11 +53,14 @@ async fn main() -> std::io::Result<()> {
|
|||
});
|
||||
|
||||
HttpServer::new(move || {
|
||||
let cors = Cors::permissive();
|
||||
|
||||
App::new()
|
||||
.wrap(cors)
|
||||
.wrap(middleware::NormalizePath::new(TrailingSlash::Trim))
|
||||
.configure(config_app(app_data.clone()))
|
||||
})
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.bind(("127.0.0.1", 8000))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
@ -60,6 +68,8 @@ async fn main() -> std::io::Result<()> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::body::to_bytes;
|
||||
use actix_web::dev::Service;
|
||||
use actix_web::http::{Method as HttpMethod, StatusCode};
|
||||
use actix_web::{test, App};
|
||||
use serde_json::Value;
|
||||
|
||||
|
@ -92,7 +102,7 @@ mod tests {
|
|||
.set_form(&payload)
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
|
||||
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
||||
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
||||
|
@ -100,15 +110,29 @@ mod tests {
|
|||
|
||||
assert!(!nr.is_empty());
|
||||
|
||||
let payload = AmountData { amount: 10_f64 };
|
||||
let req = test::TestRequest::post()
|
||||
let req = test::TestRequest::default()
|
||||
.method(HttpMethod::HEAD)
|
||||
.uri(&format!("/accounts/{}", nr))
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let req = test::TestRequest::default()
|
||||
.method(HttpMethod::HEAD)
|
||||
.uri("/accounts/muh")
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let payload = AmountData { amount: 5_f64 };
|
||||
let req = test::TestRequest::patch()
|
||||
.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()
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/accounts/{}/deposit", nr))
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
|
@ -121,6 +145,40 @@ mod tests {
|
|||
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 payload = AmountData { amount: 10_f64 };
|
||||
let req = test::TestRequest::put()
|
||||
.uri(&format!("/accounts/{}", nr))
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let req = test::TestRequest::put()
|
||||
.uri("/accounts/muh")
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_client_error());
|
||||
|
||||
let payload = AmountData { amount: -10_f64 };
|
||||
let req = test::TestRequest::put()
|
||||
.uri(&format!("/accounts/{}", nr))
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_client_error());
|
||||
|
||||
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();
|
||||
|
@ -134,7 +192,7 @@ mod tests {
|
|||
.set_form(&payload)
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
|
||||
let body_bytes = to_bytes(resp.into_body()).await.unwrap();
|
||||
let response_json: Value = serde_json::from_slice(&body_bytes).unwrap();
|
||||
|
@ -145,7 +203,7 @@ mod tests {
|
|||
to: nr2.to_string(),
|
||||
amount: 5_f64,
|
||||
};
|
||||
let req = test::TestRequest::post()
|
||||
let req = test::TestRequest::patch()
|
||||
.uri("/accounts/transfer")
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
|
@ -163,14 +221,14 @@ mod tests {
|
|||
let balance = response_json["balance"].as_f64().unwrap();
|
||||
assert_eq!(5_f64, balance);
|
||||
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/accounts/{}/close", nr))
|
||||
let req = test::TestRequest::delete()
|
||||
.uri(&format!("/accounts/{}", 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()
|
||||
let req = test::TestRequest::patch()
|
||||
.uri(&format!("/accounts/{}/withdraw", nr))
|
||||
.set_form(&payload)
|
||||
.to_request();
|
||||
|
@ -186,12 +244,19 @@ mod tests {
|
|||
let nrs = response_json.as_array().unwrap();
|
||||
assert_eq!(2, nrs.len());
|
||||
|
||||
let req = test::TestRequest::post()
|
||||
.uri(&format!("/accounts/{}/close", nr))
|
||||
let req = test::TestRequest::delete()
|
||||
.uri(&format!("/accounts/{}", nr))
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
||||
let req = test::TestRequest::default()
|
||||
.method(HttpMethod::HEAD)
|
||||
.uri(&format!("/accounts/{}", nr))
|
||||
.to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::GONE);
|
||||
|
||||
let req = test::TestRequest::get().uri("/accounts").to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
assert!(resp.status().is_success());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue