use std::sync::Arc;
use axum::Router;
use axum::extract::Request;
use axum::http::header;
use axum::middleware::Next;
use axum::response::IntoResponse;
use axum::routing::get;
use tokio::net::{TcpListener, ToSocketAddrs};
use utoipa::{PartialSchema, ToSchema};
use crate::config::ServerustConfig;
use crate::container::Container;
use crate::openapi::{OpenApiState, redoc_html, swagger_ui_html};
use crate::pipeline::Interceptor;
use crate::route::IntoRoute;
type RouterMutator = Box<dyn FnOnce(Router<Container>) -> Router<Container> + Send + Sync>;
pub struct App {
router: Router<Container>,
container: Container,
openapi: OpenApiState,
openapi_path: &'static str,
docs_path: &'static str,
redoc_path: &'static str,
interceptors: Vec<RouterMutator>,
}
impl App {
pub fn new() -> Self {
Self {
router: Router::new(),
container: Container::new(),
openapi: OpenApiState::default(),
openapi_path: "/openapi.json",
docs_path: "/docs",
redoc_path: "/redoc",
interceptors: Vec::new(),
}
}
pub fn openapi_info(mut self, title: impl Into<String>, version: impl Into<String>) -> Self {
self.openapi.set_info(title, version);
self
}
pub fn register_schema<T: ToSchema + PartialSchema>(mut self) -> Self {
self.openapi.register_schema::<T>();
self
}
pub fn docs(mut self, path: &'static str) -> Self {
self.docs_path = path;
self
}
pub fn redoc(mut self, path: &'static str) -> Self {
self.redoc_path = path;
self
}
pub fn provide<T: ?Sized + Send + Sync + 'static>(mut self, value: Arc<T>) -> Self {
self.container.insert(value);
self
}
pub fn r#override<T: ?Sized + Send + Sync + 'static>(mut self, value: Arc<T>) -> Self {
self.container.insert(value);
self
}
pub fn interceptor<I: Interceptor>(mut self, interceptor: I) -> Self {
let interceptor = std::sync::Arc::new(interceptor);
let mutator: RouterMutator = Box::new(move |router: Router<Container>| {
let interceptor = interceptor.clone();
let layer = axum::middleware::from_fn(move |req: Request, next: Next| {
let interceptor = interceptor.clone();
async move { interceptor.intercept(req, next).await }
});
router.layer(layer)
});
self.interceptors.push(mutator);
self
}
pub fn config(self, cfg: ServerustConfig) -> Self {
self.provide::<ServerustConfig>(Arc::new(cfg))
}
pub fn route<R: IntoRoute>(mut self, handler: R) -> Self {
let route = handler.into_route();
self.openapi
.push_operation(route.path, route.method, route.operation);
self.router = self.router.route(route.path, route.method_router);
self
}
pub fn into_router(self) -> Router {
let doc = self.openapi.build();
let json = doc.to_json().unwrap_or_else(|_| "{}".to_string());
let swagger_html = swagger_ui_html(self.openapi_path);
let redoc_page = redoc_html(self.openapi_path);
let mut user_router = self.router;
for mutator in self.interceptors {
user_router = mutator(user_router);
}
user_router
.route(
self.openapi_path,
get(move || {
let json = json.clone();
async move {
(
[(header::CONTENT_TYPE, "application/json")],
json,
)
.into_response()
}
}),
)
.route(
self.docs_path,
get(move || {
let html = swagger_html.clone();
async move {
(
[(header::CONTENT_TYPE, "text/html; charset=utf-8")],
html,
)
.into_response()
}
}),
)
.route(
self.redoc_path,
get(move || {
let html = redoc_page.clone();
async move {
(
[(header::CONTENT_TYPE, "text/html; charset=utf-8")],
html,
)
.into_response()
}
}),
)
.with_state(self.container)
}
pub async fn run_http<A: ToSocketAddrs>(self, addr: A) -> std::io::Result<()> {
let listener = TcpListener::bind(addr).await?;
let local = listener.local_addr()?;
let docs_path = self.docs_path;
let openapi_path = self.openapi_path;
let router = self.into_router();
eprintln!();
eprintln!(" 🦀 serverust on http://{local}");
eprintln!(" docs: http://{local}{docs_path}");
eprintln!(" openapi: http://{local}{openapi_path}");
eprintln!();
axum::serve(listener, router).await
}
}
impl Default for App {
fn default() -> Self {
Self::new()
}
}