diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f07d323 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +default_install_hook_types: [commit-msg, pre-commit] +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-yaml + stages: [commit] + - id: check-json + stages: [commit] + - id: end-of-file-fixer + stages: [commit] + - repo: https://github.com/commitizen-tools/commitizen + rev: 3.0.1 + hooks: + - id: commitizen + stages: + - commit-msg + - repo: https://github.com/doublify/pre-commit-rust + rev: v1.0 + hooks: + - id: fmt + - id: cargo-check + - id: clippy + - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook + rev: v9.5.0 + hooks: + - id: commitlint + stages: [commit-msg] + additional_dependencies: ["@commitlint/config-conventional"] diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..28fe5c5 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1 @@ +module.exports = {extends: ['@commitlint/config-conventional']} diff --git a/src/log.rs b/src/log.rs index eef3b00..fce8fed 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,8 +1,8 @@ use std::time::SystemTime; -use crate::{Error, Result}; use crate::ctx::Ctx; use crate::error::ClientError; +use crate::{Error, Result}; use axum::http::{Method, Uri}; use serde::Serialize; use serde_json::{json, Value}; @@ -16,11 +16,16 @@ pub async fn log_request( ctx: Option, service_error: Option<&Error>, client_error: Option, - ) -> Result<()> { - let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis(); +) -> Result<()> { + let timestamp = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis(); let error_type = service_error.map(|se| se.as_ref().to_string()); - let error_data = serde_json::to_value(service_error).ok().and_then(|mut v| v.get_mut("data").map(|v| v.take())); + let error_data = serde_json::to_value(service_error) + .ok() + .and_then(|mut v| v.get_mut("data").map(|v| v.take())); // Create the RequestLogLine. let log_line = RequestLogLine { diff --git a/src/main.rs b/src/main.rs index 5f1c582..94ad556 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,8 @@ use crate::model::ModelController; -pub use self::error::{Error, Result}; use self::ctx::Ctx; +pub use self::error::{Error, Result}; use self::log::log_request; use std::net::SocketAddr; @@ -12,19 +12,19 @@ use axum::extract::{Path, Query}; use axum::http::{Method, Uri}; use axum::response::{Html, IntoResponse, Response}; use axum::routing::{get, get_service}; -use axum::{middleware, Json, Router}; use axum::Server; +use axum::{middleware, Json, Router}; use serde::Deserialize; use serde_json::json; -use tower_http::services::ServeDir; use tower_cookies::CookieManagerLayer; +use tower_http::services::ServeDir; use uuid::Uuid; mod ctx; mod error; mod log; -mod web; mod model; +mod web; #[derive(Debug, Deserialize)] struct HelloParams { @@ -32,17 +32,21 @@ struct HelloParams { } #[tokio::main] -async fn main() -> Result<()>{ +async fn main() -> Result<()> { let mc = ModelController::new().await?; - let routes_apis = web::routes_properties::routes(mc.clone()).route_layer(middleware::from_fn(web::mw_auth::mw_require_auth)); + let routes_apis = web::routes_properties::routes(mc.clone()) + .route_layer(middleware::from_fn(web::mw_auth::mw_require_auth)); let routes_all = Router::new() .merge(routes_hello()) .merge(web::routes_login::routes()) .nest("/api", routes_apis) .layer(middleware::map_response(main_response_mapper)) - .layer(middleware::from_fn_with_state(mc.clone(), web::mw_auth::mw_ctx_resolver)) + .layer(middleware::from_fn_with_state( + mc.clone(), + web::mw_auth::mw_ctx_resolver, + )) .layer(CookieManagerLayer::new()) // must be above? the auth routes .fallback_service(routes_static()); @@ -56,11 +60,15 @@ async fn main() -> Result<()>{ Ok(()) } - /// Map the response to add headers, etc. /// /// * `res`: the response to map -async fn main_response_mapper(ctx: Option, uri: Uri, req_method: Method, res: Response) -> Response { +async fn main_response_mapper( + ctx: Option, + uri: Uri, + req_method: Method, + res: Response, +) -> Response { println!("->> {:<12} - main_response_mapper", "RES_MAPPER"); let uuid = Uuid::new_v4(); @@ -99,7 +107,8 @@ fn routes_static() -> Router { } fn routes_hello() -> Router { - Router::new().route("/hello", get(handler_hello)) + Router::new() + .route("/hello", get(handler_hello)) .route("/hello2/:name", get(handler_hello2)) } diff --git a/src/model.rs b/src/model.rs index c46fae9..732043b 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,7 +1,7 @@ //! Simplistic model layer //! (with mock-store layer) -use crate::{Error, Result, ctx::Ctx}; +use crate::{ctx::Ctx, Error, Result}; use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; @@ -32,7 +32,9 @@ pub struct ModelController { // Constructor impl ModelController { pub async fn new() -> Result { - Ok(Self { property_store: Arc::default() }) + Ok(Self { + property_store: Arc::default(), + }) } } @@ -41,7 +43,12 @@ impl ModelController { pub async fn create_property(&self, ctx: Ctx, property: PropertyForCreate) -> Result { let mut store = self.property_store.lock().unwrap(); let id = store.len() as u64; - let property = Property { id, creator_id: ctx.user_id(), address: property.address, contact: property.contact }; + let property = Property { + id, + creator_id: ctx.user_id(), + address: property.address, + contact: property.contact, + }; store.push(Some(property.clone())); Ok(property) } diff --git a/src/web/mw_auth.rs b/src/web/mw_auth.rs index 2eecc0e..9942434 100644 --- a/src/web/mw_auth.rs +++ b/src/web/mw_auth.rs @@ -1,12 +1,12 @@ use async_trait::async_trait; -use axum::RequestPartsExt; use axum::extract::FromRequestParts; -use axum::http::Request; use axum::http::request::Parts; +use axum::http::Request; use axum::middleware::Next; use axum::response::Response; +use axum::RequestPartsExt; use lazy_regex::regex_captures; -use tower_cookies::{Cookies, Cookie}; +use tower_cookies::{Cookie, Cookies}; use crate::ctx::Ctx; use crate::web::AUTH_TOKEN; @@ -16,21 +16,22 @@ pub async fn mw_ctx_resolver( cookies: Cookies, mut req: Request, next: Next, - ) -> Result { +) -> Result { println!("->> {:<12} - mw_ctx_resolver", "MIDDLEWARE"); let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.value().to_string()); // Compute Result. - let result_ctx = match auth_token. - ok_or(Error::AuthFailNoAuthTokenCookie) - .and_then(parse_token) { - Ok((user_id, _exp, _sign)) => { - // TODO: Token components validations. - Ok(Ctx::new(user_id)) - }, - Err(e) => Err(e), - }; + let result_ctx = match auth_token + .ok_or(Error::AuthFailNoAuthTokenCookie) + .and_then(parse_token) + { + Ok((user_id, _exp, _sign)) => { + // TODO: Token components validations. + Ok(Ctx::new(user_id)) + } + Err(e) => Err(e), + }; // Remove the cookie if something went wrong other than NoAuthTokenCookie. if result_ctx.is_err() && !matches!(result_ctx, Err(Error::AuthFailNoAuthTokenCookie)) { @@ -47,7 +48,7 @@ pub async fn mw_ctx_resolver( pub async fn mw_require_auth( ctx: Result, req: Request, - next: Next + next: Next, ) -> Result { println!("->> {:<12} - mw_require_auth - {ctx:?}", "MIDDLEWARE"); @@ -60,17 +61,17 @@ pub async fn mw_require_auth( #[async_trait] impl FromRequestParts for Ctx { - type Rejection = Error; + type Rejection = Error; - async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { - println!("->> {:<12} - Ctx", "EXTRACTOR"); + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { + println!("->> {:<12} - Ctx", "EXTRACTOR"); - parts - .extensions - .get::>() - .ok_or(Error::AuthFailCtxNotInRequestExt)? - .clone() - } + parts + .extensions + .get::>() + .ok_or(Error::AuthFailCtxNotInRequestExt)? + .clone() + } } // endregion: --- Ctx Extractor @@ -80,7 +81,8 @@ impl FromRequestParts for Ctx { fn parse_token(token: String) -> Result<(u64, String, String)> { let (_whole, user_id, exp, sign) = regex_captures!( r#"^user-(\d+)\.(.+)\.(.+)"#, // a literal regex - &token) + &token + ) .ok_or(Error::AuthFailTokenWrongFormat)?; let user_id: u64 = user_id @@ -88,5 +90,4 @@ fn parse_token(token: String) -> Result<(u64, String, String)> { .map_err(|_| Error::AuthFailTokenWrongFormat)?; Ok((user_id, exp.to_string(), sign.to_string())) - } diff --git a/src/web/routes_login.rs b/src/web/routes_login.rs index 5de8e4e..d7b42a5 100644 --- a/src/web/routes_login.rs +++ b/src/web/routes_login.rs @@ -1,6 +1,6 @@ -use crate::{Error, Result, web}; -use axum::{Json, Router}; +use crate::{web, Error, Result}; use axum::routing::post; +use axum::{Json, Router}; use serde::Deserialize; use serde_json::{json, Value}; use tower_cookies::{Cookie, Cookies}; @@ -9,7 +9,7 @@ pub fn routes() -> Router { Router::new().route("/api/login", post(api_login)) } -async fn api_login(cookies: Cookies, payload: Json) -> Result>{ +async fn api_login(cookies: Cookies, payload: Json) -> Result> { println!("->> {:<12} - api_login", "HANDLER"); if payload.username != "demo1" || payload.password != "demo1" { @@ -18,7 +18,7 @@ async fn api_login(cookies: Cookies, payload: Json) -> Result Router { // region: --- REST Handlers -async fn create_property(State(mc): State, ctx: Ctx, Json(property_fc): Json) -> Result> { +async fn create_property( + State(mc): State, + ctx: Ctx, + Json(property_fc): Json, +) -> Result> { println!("->> {:<12} - create_property", "HANDLER"); let property = mc.create_property(ctx, property_fc).await?; Ok(Json(property)) } -async fn list_properties(State(mc): State, ctx: Ctx) -> Result>> { +async fn list_properties( + State(mc): State, + ctx: Ctx, +) -> Result>> { println!("->> {:<12} - list_properties", "HANDLER"); let properties = mc.list_properties(ctx).await?; Ok(Json(properties)) } -async fn delete_property(State(mc): State, ctx: Ctx, Path(id): Path) -> Result> { +async fn delete_property( + State(mc): State, + ctx: Ctx, + Path(id): Path, +) -> Result> { println!("->> {:<12} - delete_property", "HANDLER"); let property = mc.delete_property(ctx, id).await?; Ok(Json(property)) diff --git a/tests/quick_dev.rs b/tests/quick_dev.rs index e6f11e3..30017d2 100644 --- a/tests/quick_dev.rs +++ b/tests/quick_dev.rs @@ -11,7 +11,6 @@ async fn test_quick_dev() -> Result<()> { let hc = httpc_test::new_client("http://localhost:3000")?; hc.do_get("/hello2/mike").await?.print().await?; - hc.do_get("/src/main.rs").await?.print().await?; hc.do_get("/src/blub.rs").await?.print().await?; @@ -19,22 +18,22 @@ async fn test_quick_dev() -> Result<()> { let req_login = hc.do_post( "/api/login", json!( - { - "username": "demo1", - "password": "demowrong" - } - ) - ); + { + "username": "demo1", + "password": "demowrong" + } + ), + ); req_login.await?.print().await?; let req_login = hc.do_post( "/api/login", json!( - { - "username": "demo1", - "password": "demo1" - } - ) - ); + { + "username": "demo1", + "password": "demo1" + } + ), + ); req_login.await?.print().await?; hc.do_get("/hello2/mike").await?.print().await?; @@ -42,22 +41,22 @@ async fn test_quick_dev() -> Result<()> { let req_create_property = hc.do_post( "/api/properties", json!( - { - "address": "Lolilat Street 1", - "contact": "01234 567890" - } - ) - ); + { + "address": "Lolilat Street 1", + "contact": "01234 567890" + } + ), + ); req_create_property.await?.print().await?; let req_create_property = hc.do_post( "/api/properties", json!( - { - "address": "Lolilat Street 2", - "contact": "01243 217890" - } - ) - ); + { + "address": "Lolilat Street 2", + "contact": "01243 217890" + } + ), + ); req_create_property.await?.print().await?; let req_get_properties = hc.do_get("/api/properties").await?; req_get_properties.print().await?;