pub mod driver; pub mod error; mod middleware; pub mod routes; pub mod shutdown; pub mod state; use std::time::Duration; use activitypub_federation::config::FederationMiddleware; use axum::{ Router, http::{HeaderName, StatusCode}, }; use tower_http::{ cors::{self, CorsLayer}, request_id::PropagateRequestIdLayer, timeout::TimeoutLayer, trace::TraceLayer, }; use tracing::{error, info_span}; use utoipa::OpenApi; use utoipa_axum::router::OpenApiRouter; use crate::{ config::Config, server::{ middleware::request_id::{REQUEST_ID_HEADER, add_request_id}, state::{AppState, federation}, }, }; #[derive(OpenApi)] #[openapi( tags( (name = routes::HEALTH, description = "Check API health"), ), )] pub struct ApiDoc; pub async fn router(config: &Config, state: AppState) -> anyhow::Result> { let state = federation::add_federation(state, config).await?; let mut doc = ApiDoc::openapi(); #[cfg(feature = "oauth")] doc.merge(routes::auth::OAuthDoc::openapi()); let stubs = OpenApiRouter::with_openapi(doc).routes(utoipa_axum::routes!(routes::health_check)); #[cfg(feature = "oauth")] let stubs = stubs.routes(utoipa_axum::routes!(routes::auth::auth)); let (router, _api) = stubs.split_for_parts(); #[cfg(feature = "swagger")] let router = router.merge( utoipa_swagger_ui::SwaggerUi::new("/swagger-ui") .url("/api-docs/swaggerdoc.json", _api.clone()), ); #[cfg(feature = "redoc")] let router = { use utoipa_redoc::Servable as _; router.merge(utoipa_redoc::Redoc::with_url("/redoc", _api.clone())) }; #[cfg(feature = "scalar")] let router = { use utoipa_scalar::Servable as _; router.merge(utoipa_scalar::Scalar::with_url("/scalar", _api.clone())) }; #[cfg(feature = "rapidoc")] let router = router.merge( utoipa_rapidoc::RapiDoc::with_openapi("/api-docs/rapidoc.json", _api).path("/rapidoc"), ); let router = router .layer( TraceLayer::new_for_http().make_span_with(|request: &axum::http::Request<_>| { if let Some(request_id) = request.headers().get(REQUEST_ID_HEADER) { info_span!( "http_request", request_id = ?request_id, ) } else { error!("could not extract request_id"); info_span!("http_request") } }), ) .layer(TimeoutLayer::with_status_code( StatusCode::REQUEST_TIMEOUT, Duration::from_secs(config.server.request_timeout), )) .layer(FederationMiddleware::new(state)) // send headers from request to response headers .layer(PropagateRequestIdLayer::new(HeaderName::from_static( REQUEST_ID_HEADER, ))) .layer(axum::middleware::from_fn(add_request_id)) .layer( CorsLayer::new() .allow_origin(cors::Any) .allow_headers(cors::Any) .allow_methods(cors::Any), ); Ok(router) } #[cfg(test)] pub mod bootstrap { use async_trait::async_trait; use crate::server::driver::SellershutDriver; #[derive(Debug, Default)] pub struct TestDriver {} #[async_trait] impl SellershutDriver for TestDriver { async fn hello(&self) { todo!() } } }