chore: add pre-commit and format files

This commit is contained in:
Sandro Eiler 2023-10-21 13:47:06 +02:00
parent ed4322b8e9
commit c558a6623c
9 changed files with 136 additions and 74 deletions

29
.pre-commit-config.yaml Normal file
View file

@ -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"]

1
commitlint.config.js Normal file
View file

@ -0,0 +1 @@
module.exports = {extends: ['@commitlint/config-conventional']}

View file

@ -1,8 +1,8 @@
use std::time::SystemTime; use std::time::SystemTime;
use crate::{Error, Result};
use crate::ctx::Ctx; use crate::ctx::Ctx;
use crate::error::ClientError; use crate::error::ClientError;
use crate::{Error, Result};
use axum::http::{Method, Uri}; use axum::http::{Method, Uri};
use serde::Serialize; use serde::Serialize;
use serde_json::{json, Value}; use serde_json::{json, Value};
@ -16,11 +16,16 @@ pub async fn log_request(
ctx: Option<Ctx>, ctx: Option<Ctx>,
service_error: Option<&Error>, service_error: Option<&Error>,
client_error: Option<ClientError>, client_error: Option<ClientError>,
) -> Result<()> { ) -> Result<()> {
let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis(); 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_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. // Create the RequestLogLine.
let log_line = RequestLogLine { let log_line = RequestLogLine {

View file

@ -2,8 +2,8 @@
use crate::model::ModelController; use crate::model::ModelController;
pub use self::error::{Error, Result};
use self::ctx::Ctx; use self::ctx::Ctx;
pub use self::error::{Error, Result};
use self::log::log_request; use self::log::log_request;
use std::net::SocketAddr; use std::net::SocketAddr;
@ -12,19 +12,19 @@ use axum::extract::{Path, Query};
use axum::http::{Method, Uri}; use axum::http::{Method, Uri};
use axum::response::{Html, IntoResponse, Response}; use axum::response::{Html, IntoResponse, Response};
use axum::routing::{get, get_service}; use axum::routing::{get, get_service};
use axum::{middleware, Json, Router};
use axum::Server; use axum::Server;
use axum::{middleware, Json, Router};
use serde::Deserialize; use serde::Deserialize;
use serde_json::json; use serde_json::json;
use tower_http::services::ServeDir;
use tower_cookies::CookieManagerLayer; use tower_cookies::CookieManagerLayer;
use tower_http::services::ServeDir;
use uuid::Uuid; use uuid::Uuid;
mod ctx; mod ctx;
mod error; mod error;
mod log; mod log;
mod web;
mod model; mod model;
mod web;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct HelloParams { struct HelloParams {
@ -32,17 +32,21 @@ struct HelloParams {
} }
#[tokio::main] #[tokio::main]
async fn main() -> Result<()>{ async fn main() -> Result<()> {
let mc = ModelController::new().await?; 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() let routes_all = Router::new()
.merge(routes_hello()) .merge(routes_hello())
.merge(web::routes_login::routes()) .merge(web::routes_login::routes())
.nest("/api", routes_apis) .nest("/api", routes_apis)
.layer(middleware::map_response(main_response_mapper)) .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 .layer(CookieManagerLayer::new()) // must be above? the auth routes
.fallback_service(routes_static()); .fallback_service(routes_static());
@ -56,11 +60,15 @@ async fn main() -> Result<()>{
Ok(()) Ok(())
} }
/// Map the response to add headers, etc. /// Map the response to add headers, etc.
/// ///
/// * `res`: the response to map /// * `res`: the response to map
async fn main_response_mapper(ctx: Option<Ctx>, uri: Uri, req_method: Method, res: Response) -> Response { async fn main_response_mapper(
ctx: Option<Ctx>,
uri: Uri,
req_method: Method,
res: Response,
) -> Response {
println!("->> {:<12} - main_response_mapper", "RES_MAPPER"); println!("->> {:<12} - main_response_mapper", "RES_MAPPER");
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
@ -99,7 +107,8 @@ fn routes_static() -> Router {
} }
fn routes_hello() -> 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)) .route("/hello2/:name", get(handler_hello2))
} }

View file

@ -1,7 +1,7 @@
//! Simplistic model layer //! Simplistic model layer
//! (with mock-store layer) //! (with mock-store layer)
use crate::{Error, Result, ctx::Ctx}; use crate::{ctx::Ctx, Error, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -32,7 +32,9 @@ pub struct ModelController {
// Constructor // Constructor
impl ModelController { impl ModelController {
pub async fn new() -> Result<Self> { pub async fn new() -> Result<Self> {
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<Property> { pub async fn create_property(&self, ctx: Ctx, property: PropertyForCreate) -> Result<Property> {
let mut store = self.property_store.lock().unwrap(); let mut store = self.property_store.lock().unwrap();
let id = store.len() as u64; 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())); store.push(Some(property.clone()));
Ok(property) Ok(property)
} }

View file

@ -1,12 +1,12 @@
use async_trait::async_trait; use async_trait::async_trait;
use axum::RequestPartsExt;
use axum::extract::FromRequestParts; use axum::extract::FromRequestParts;
use axum::http::Request;
use axum::http::request::Parts; use axum::http::request::Parts;
use axum::http::Request;
use axum::middleware::Next; use axum::middleware::Next;
use axum::response::Response; use axum::response::Response;
use axum::RequestPartsExt;
use lazy_regex::regex_captures; use lazy_regex::regex_captures;
use tower_cookies::{Cookies, Cookie}; use tower_cookies::{Cookie, Cookies};
use crate::ctx::Ctx; use crate::ctx::Ctx;
use crate::web::AUTH_TOKEN; use crate::web::AUTH_TOKEN;
@ -16,21 +16,22 @@ pub async fn mw_ctx_resolver<B>(
cookies: Cookies, cookies: Cookies,
mut req: Request<B>, mut req: Request<B>,
next: Next<B>, next: Next<B>,
) -> Result<Response> { ) -> Result<Response> {
println!("->> {:<12} - mw_ctx_resolver", "MIDDLEWARE"); println!("->> {:<12} - mw_ctx_resolver", "MIDDLEWARE");
let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.value().to_string()); let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.value().to_string());
// Compute Result<Ctx>. // Compute Result<Ctx>.
let result_ctx = match auth_token. let result_ctx = match auth_token
ok_or(Error::AuthFailNoAuthTokenCookie) .ok_or(Error::AuthFailNoAuthTokenCookie)
.and_then(parse_token) { .and_then(parse_token)
Ok((user_id, _exp, _sign)) => { {
// TODO: Token components validations. Ok((user_id, _exp, _sign)) => {
Ok(Ctx::new(user_id)) // TODO: Token components validations.
}, Ok(Ctx::new(user_id))
Err(e) => Err(e), }
}; Err(e) => Err(e),
};
// Remove the cookie if something went wrong other than NoAuthTokenCookie. // Remove the cookie if something went wrong other than NoAuthTokenCookie.
if result_ctx.is_err() && !matches!(result_ctx, Err(Error::AuthFailNoAuthTokenCookie)) { if result_ctx.is_err() && !matches!(result_ctx, Err(Error::AuthFailNoAuthTokenCookie)) {
@ -47,7 +48,7 @@ pub async fn mw_ctx_resolver<B>(
pub async fn mw_require_auth<B>( pub async fn mw_require_auth<B>(
ctx: Result<Ctx>, ctx: Result<Ctx>,
req: Request<B>, req: Request<B>,
next: Next<B> next: Next<B>,
) -> Result<Response> { ) -> Result<Response> {
println!("->> {:<12} - mw_require_auth - {ctx:?}", "MIDDLEWARE"); println!("->> {:<12} - mw_require_auth - {ctx:?}", "MIDDLEWARE");
@ -60,17 +61,17 @@ pub async fn mw_require_auth<B>(
#[async_trait] #[async_trait]
impl<S: Send + Sync> FromRequestParts<S> for Ctx { impl<S: Send + Sync> FromRequestParts<S> for Ctx {
type Rejection = Error; type Rejection = Error;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self> { async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self> {
println!("->> {:<12} - Ctx", "EXTRACTOR"); println!("->> {:<12} - Ctx", "EXTRACTOR");
parts parts
.extensions .extensions
.get::<Result<Ctx>>() .get::<Result<Ctx>>()
.ok_or(Error::AuthFailCtxNotInRequestExt)? .ok_or(Error::AuthFailCtxNotInRequestExt)?
.clone() .clone()
} }
} }
// endregion: --- Ctx Extractor // endregion: --- Ctx Extractor
@ -80,7 +81,8 @@ impl<S: Send + Sync> FromRequestParts<S> for Ctx {
fn parse_token(token: String) -> Result<(u64, String, String)> { fn parse_token(token: String) -> Result<(u64, String, String)> {
let (_whole, user_id, exp, sign) = regex_captures!( let (_whole, user_id, exp, sign) = regex_captures!(
r#"^user-(\d+)\.(.+)\.(.+)"#, // a literal regex r#"^user-(\d+)\.(.+)\.(.+)"#, // a literal regex
&token) &token
)
.ok_or(Error::AuthFailTokenWrongFormat)?; .ok_or(Error::AuthFailTokenWrongFormat)?;
let user_id: u64 = user_id let user_id: u64 = user_id
@ -88,5 +90,4 @@ fn parse_token(token: String) -> Result<(u64, String, String)> {
.map_err(|_| Error::AuthFailTokenWrongFormat)?; .map_err(|_| Error::AuthFailTokenWrongFormat)?;
Ok((user_id, exp.to_string(), sign.to_string())) Ok((user_id, exp.to_string(), sign.to_string()))
} }

View file

@ -1,6 +1,6 @@
use crate::{Error, Result, web}; use crate::{web, Error, Result};
use axum::{Json, Router};
use axum::routing::post; use axum::routing::post;
use axum::{Json, Router};
use serde::Deserialize; use serde::Deserialize;
use serde_json::{json, Value}; use serde_json::{json, Value};
use tower_cookies::{Cookie, Cookies}; use tower_cookies::{Cookie, Cookies};
@ -9,7 +9,7 @@ pub fn routes() -> Router {
Router::new().route("/api/login", post(api_login)) Router::new().route("/api/login", post(api_login))
} }
async fn api_login(cookies: Cookies, payload: Json<LoginPayload>) -> Result<Json<Value>>{ async fn api_login(cookies: Cookies, payload: Json<LoginPayload>) -> Result<Json<Value>> {
println!("->> {:<12} - api_login", "HANDLER"); println!("->> {:<12} - api_login", "HANDLER");
if payload.username != "demo1" || payload.password != "demo1" { if payload.username != "demo1" || payload.password != "demo1" {
@ -18,7 +18,7 @@ async fn api_login(cookies: Cookies, payload: Json<LoginPayload>) -> Result<Json
// FIXME: Implement real auth-token generation/signature. // FIXME: Implement real auth-token generation/signature.
cookies.add(Cookie::new(web::AUTH_TOKEN, "user-1.exp.sign")); cookies.add(Cookie::new(web::AUTH_TOKEN, "user-1.exp.sign"));
let body = Json(json!({ let body = Json(json!({
"result": { "result": {
"success": true "success": true

View file

@ -14,19 +14,30 @@ pub fn routes(mc: ModelController) -> Router {
// region: --- REST Handlers // region: --- REST Handlers
async fn create_property(State(mc): State<ModelController>, ctx: Ctx, Json(property_fc): Json<PropertyForCreate>) -> Result<Json<Property>> { async fn create_property(
State(mc): State<ModelController>,
ctx: Ctx,
Json(property_fc): Json<PropertyForCreate>,
) -> Result<Json<Property>> {
println!("->> {:<12} - create_property", "HANDLER"); println!("->> {:<12} - create_property", "HANDLER");
let property = mc.create_property(ctx, property_fc).await?; let property = mc.create_property(ctx, property_fc).await?;
Ok(Json(property)) Ok(Json(property))
} }
async fn list_properties(State(mc): State<ModelController>, ctx: Ctx) -> Result<Json<Vec<Property>>> { async fn list_properties(
State(mc): State<ModelController>,
ctx: Ctx,
) -> Result<Json<Vec<Property>>> {
println!("->> {:<12} - list_properties", "HANDLER"); println!("->> {:<12} - list_properties", "HANDLER");
let properties = mc.list_properties(ctx).await?; let properties = mc.list_properties(ctx).await?;
Ok(Json(properties)) Ok(Json(properties))
} }
async fn delete_property(State(mc): State<ModelController>, ctx: Ctx, Path(id): Path<u64>) -> Result<Json<Property>> { async fn delete_property(
State(mc): State<ModelController>,
ctx: Ctx,
Path(id): Path<u64>,
) -> Result<Json<Property>> {
println!("->> {:<12} - delete_property", "HANDLER"); println!("->> {:<12} - delete_property", "HANDLER");
let property = mc.delete_property(ctx, id).await?; let property = mc.delete_property(ctx, id).await?;
Ok(Json(property)) Ok(Json(property))

View file

@ -11,7 +11,6 @@ async fn test_quick_dev() -> Result<()> {
let hc = httpc_test::new_client("http://localhost:3000")?; let hc = httpc_test::new_client("http://localhost:3000")?;
hc.do_get("/hello2/mike").await?.print().await?; hc.do_get("/hello2/mike").await?.print().await?;
hc.do_get("/src/main.rs").await?.print().await?; hc.do_get("/src/main.rs").await?.print().await?;
hc.do_get("/src/blub.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( let req_login = hc.do_post(
"/api/login", "/api/login",
json!( json!(
{ {
"username": "demo1", "username": "demo1",
"password": "demowrong" "password": "demowrong"
} }
) ),
); );
req_login.await?.print().await?; req_login.await?.print().await?;
let req_login = hc.do_post( let req_login = hc.do_post(
"/api/login", "/api/login",
json!( json!(
{ {
"username": "demo1", "username": "demo1",
"password": "demo1" "password": "demo1"
} }
) ),
); );
req_login.await?.print().await?; req_login.await?.print().await?;
hc.do_get("/hello2/mike").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( let req_create_property = hc.do_post(
"/api/properties", "/api/properties",
json!( json!(
{ {
"address": "Lolilat Street 1", "address": "Lolilat Street 1",
"contact": "01234 567890" "contact": "01234 567890"
} }
) ),
); );
req_create_property.await?.print().await?; req_create_property.await?.print().await?;
let req_create_property = hc.do_post( let req_create_property = hc.do_post(
"/api/properties", "/api/properties",
json!( json!(
{ {
"address": "Lolilat Street 2", "address": "Lolilat Street 2",
"contact": "01243 217890" "contact": "01243 217890"
} }
) ),
); );
req_create_property.await?.print().await?; req_create_property.await?.print().await?;
let req_get_properties = hc.do_get("/api/properties").await?; let req_get_properties = hc.do_get("/api/properties").await?;
req_get_properties.print().await?; req_get_properties.print().await?;