Initial commit

This commit is contained in:
Martin Berg Alstad
2024-06-22 17:19:55 +02:00
parent 9eb7c2883e
commit d5974dda20
22 changed files with 713 additions and 6 deletions

View File

@ -0,0 +1,20 @@
#[macro_export]
#[cfg(feature = "axum")]
macro_rules! create_app {
($router:expr) => {
$router
};
($router:expr, $($layer:expr),* $(,)?) => {
$router$(.layer($layer))*
};
}
#[cfg(all(test, feature = "axum"))]
mod tests {
use axum::Router;
#[test]
fn test_create_app_router_only() {
let _app: Router<()> = create_app!(Router::new());
}
}

View File

@ -0,0 +1,93 @@
#[cfg(all(feature = "tokio", feature = "axum"))]
use {crate::io::file, axum::body::Body, axum::response::Html, std::io};
/// Load an HTML file from the given file path, relative to the current directory.
/// # Arguments
/// * `file_path` - The path to the HTML file.
/// # Returns
/// The HTML file as a `Html` object containing the content-type 'text/html' or an error message if the file is not found or cannot be read.
/// # Examples
/// ```
/// let html = async { lib::axum::load::load_html("openapi.html").await.unwrap() };
/// ```
#[cfg(all(feature = "tokio", feature = "axum"))]
pub async fn load_html<Path>(file_path: Path) -> Result<Html<Body>, io::Error>
where
Path: AsRef<std::path::Path>,
{
load_file(file_path).await.map(Html)
}
#[cfg(all(feature = "tokio", feature = "axum"))]
pub async fn load_file<Path>(file_path: Path) -> Result<Body, io::Error>
where
Path: AsRef<std::path::Path>,
{
file::load_file(file_path).await.map(Body::from_stream)
}
/// Load an HTML file from the given file path, relative to the resource directory.
/// The file is loading on compile time as a string literal.
/// # Arguments
/// * `filename` - The path to the HTML file.
/// # Returns
/// The HTML file as a `Html` object containing the content-type 'text/html'.
/// # Examples
/// ```
/// let _html = lib::load_html!("load.rs");
/// ```
// TODO check platform and use correct path separator
#[macro_export]
#[cfg(feature = "axum")]
macro_rules! load_html {
($filepath:expr) => {
axum::response::Html(
axum::body::Body::new(
include_str!($filepath).to_string()
)
)
};
($filepath:expr, $($key:expr => $value:expr),*) => {
axum::response::Html(
axum::body::Body::new(
include_str!($filepath)$(
.replace($key, $value)
)*
)
)
};
}
#[cfg(all(test, feature = "axum"))]
mod tests {
#[test]
fn test_load_html() {
let _html = load_html!("load.rs");
}
#[test]
fn test_load_html_with_replacements() {
let _html =
load_html!("load.rs", "{{replace_me}}" => "hello", "{{replace_me_too}}" => "world");
}
#[cfg(feature = "tokio")]
mod tokio {
use super::super::*;
#[tokio::test]
async fn test_load_html() {
assert!(load_html("Cargo.toml").await.is_ok());
}
#[tokio::test]
async fn test_load_file() {
assert!(load_file("Cargo.toml").await.is_ok());
}
#[tokio::test]
async fn test_load_file_not_found() {
assert!(load_file("not_found.rs").await.is_err());
}
}
}

View File

@ -0,0 +1,4 @@
pub mod app;
pub mod load;
pub mod response;
pub mod router;

View File

@ -0,0 +1,56 @@
#[cfg(all(feature = "axum", feature = "serde"))]
use {
crate::serde::response::BaseResponse,
axum::{
response::{IntoResponse, Response},
Json,
},
serde::Serialize,
};
#[cfg(all(feature = "axum", feature = "serde"))]
impl<T: Serialize> IntoResponse for BaseResponse<T> {
fn into_response(self) -> Response {
Json(self).into_response()
}
}
#[cfg(all(test, feature = "axum", feature = "serde"))]
mod tests {
use axum::http::header::CONTENT_TYPE;
use axum::http::{HeaderValue, StatusCode};
use axum::response::IntoResponse;
use serde::Serialize;
use crate::serde::response::BaseResponse;
#[derive(Serialize)]
struct Response {
message: String,
}
#[test]
fn test_into_response() {
let response = BaseResponse::new(
"",
Response {
message: "Hi".to_string(),
},
);
let json_response = response.into_response();
assert_eq!(json_response.status(), StatusCode::OK);
assert_eq!(
json_response.headers().get(CONTENT_TYPE),
Some(&HeaderValue::from_static("application/json"))
);
}
#[test]
fn test_into_response_with_primitive() {
let response = BaseResponse::new("", 42);
assert_eq!(
response.into_response().status(),
StatusCode::INTERNAL_SERVER_ERROR
);
}
}

View File

@ -0,0 +1,109 @@
/// Create an axum router function with the given body or routes.
/// # Examples
/// ```
/// use lib::router;
/// async fn index() {}
///
/// router!(
/// get "/" => index,
/// get "/openapi" => || async {}
/// );
/// ```
/// ```
/// use lib::router;
/// async fn simplify(path: axum::extract::path::Path<String>) {}
/// router!("/simplify", lib::routes!(
/// get "/:exp" => simplify,
/// get "/table/:exp" => || async {}
/// ));
/// ```
#[macro_export]
#[cfg(feature = "axum")]
macro_rules! router {
($body:expr) => {
pub(crate) fn router() -> axum::Router<()> {
$body
}
};
($route:expr, $router:expr) => {
router!(axum::Router::new().nest($route, $router));
};
($($method:ident $route:expr => $func:expr),* $(,)?) => {
router!($crate::routes!($($method $route => $func),*));
};
}
/// Create a router with the given routes.
/// # Examples
/// ```
/// async fn index() {}
///
/// let _: axum::Router<()> = lib::routes!(
/// get "/" => index,
/// post "/" => || async {}
/// );
/// ```
#[macro_export]
#[cfg(feature = "axum")]
macro_rules! routes {
($($method:ident $route:expr => $func:expr),* $(,)?) => {
axum::Router::new()
$(
.route($route, axum::routing::$method($func))
)*
};
}
#[macro_export]
#[cfg(feature = "axum")]
macro_rules! join_routes {
($($route:expr),* $(,)?) => {
axum::Router::new()$(
.merge($route)
)*
};
}
#[cfg(all(test, feature = "axum"))]
mod tests {
use axum::Router;
async fn index() {}
#[test]
fn test_router() {
router!(
get "/" => index,
post "/" => || async {}
);
}
#[test]
fn test_router_with_expression() {
router!(Router::new());
}
#[test]
fn test_nested_router() {
router!(
"/simplify",
routes!(
get "/:exp" => || async {},
get "/table/:exp" => || async {}
)
);
}
#[test]
fn test_routes() {
let _router: Router<()> = routes!(
get "/" => index,
post "/" => || async {}
);
}
#[test]
fn test_join_routes() {
let _router: Router<()> = join_routes![Router::new(), Router::new()];
}
}