backend-kit 0.0.3

Provides a set of tools and helpers for building backend services in Rust.
Documentation
use axum::{
    extract::{rejection::JsonRejection, FromRequest},
    http::StatusCode,
    response::{IntoResponse, Response},
    Router,
};
use serde::Serialize;
use serde_json::json;
use tokio::net::TcpListener;

use crate::{error, shutdown};

#[derive(FromRequest)]
#[from_request(via(axum::Json), rejection(error::Error))]
pub struct Json<T>(pub T);

impl<T: Serialize> IntoResponse for Json<T> {
    fn into_response(self) -> Response {
        let Self(value) = self;
        axum::Json(value).into_response()
    }
}

impl From<JsonRejection> for error::Error {
    fn from(err: JsonRejection) -> Self {
        error::invalid_argument_with_message(&err.to_string())
    }
}

impl IntoResponse for error::Error {
    fn into_response(self) -> Response {
        let code = StatusCode::from_u16(self.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
        let payload = json!({
            "message": self.message,
        });

        (code, axum::Json(payload)).into_response()
    }
}

pub async fn serve(router: Router, port: u16) -> Result<(), axum::BoxError> {
    let addr = format!("0.0.0.0:{}", port);
    let listener = TcpListener::bind(addr).await.unwrap();

    log::info!("running http server on {}", listener.local_addr().unwrap());

    let r = Router::new()
        .merge(router)
        .fallback(not_found)
        .method_not_allowed_fallback(method_not_allowed);

    axum::serve(listener, r)
        .with_graceful_shutdown(shutdown::wait_for_signal())
        .await?;

    Ok(())
}

async fn not_found() -> Response {
    (StatusCode::NOT_FOUND, Json(json!({"message": "not found"}))).into_response()
}

async fn method_not_allowed() -> Response {
    (
        StatusCode::METHOD_NOT_ALLOWED,
        Json(json!({"message": "method not allowed"})),
    )
        .into_response()
}