pub use async_trait::async_trait;
use axum::extract::FromRef;
pub use axum::extract::{Extension, Json, Path, Query, State};
pub use axum::response::IntoResponse as Responder;
pub use axum::routing::{delete, get, patch, post, put};
pub use axum_macros::debug_handler;
pub use config::ConfigWrapper;
pub use either::Either;
pub use once_cell::sync::Lazy;
pub use router::GotchaRouter;
use serde::{Deserialize, Serialize};
use tracing::level_filters::LevelFilter;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
pub use {axum, inventory, tracing};
pub use crate::builder::{EmptyConfig, EmptyState, Gotcha};
pub use crate::config::GotchaConfigLoader;
#[cfg(feature = "message")]
pub mod message;
#[cfg(feature = "openapi")]
pub use gotcha_core::Responsible;
#[cfg(feature = "openapi")]
pub use crate::openapi::schematic::{ParameterProvider, Schematic};
#[cfg(feature = "openapi")]
pub use gotcha_macro::api;
#[cfg(feature = "openapi")]
pub use oas;
#[cfg(feature = "message")]
pub use crate::message::{Message, Messager};
#[cfg(feature = "openapi")]
pub use crate::openapi::Operable;
pub mod builder;
pub mod config;
pub mod error;
#[cfg(feature = "openapi")]
pub mod openapi;
pub mod prelude;
pub mod router;
pub mod state;
#[cfg(feature = "task")]
pub mod task;
#[cfg(feature = "prometheus")]
pub mod prometheus {
pub use axum_prometheus::metrics::*;
}
pub mod layers {
#[cfg(feature = "cors")]
pub use tower_http::cors::{self, CorsLayer};
}
#[cfg(feature = "openapi")]
pub use crate::openapi::schematic::EnhancedSchema;
pub use serde_json;
#[cfg(feature = "task")]
pub use task::TaskScheduler;
#[cfg(feature = "static_files")]
pub use tower_http::services::{ServeDir, ServeFile};
#[derive(Clone)]
pub struct GotchaContext<State: Clone + Send + Sync + 'static, Config: Clone + Send + Sync + 'static + Serialize + for<'de> Deserialize<'de> + Default> {
pub config: ConfigWrapper<Config>,
pub state: State,
}
impl<State, Config> FromRef<GotchaContext<State, Config>> for ConfigWrapper<Config>
where
State: Clone + Send + Sync + 'static,
Config: Clone + Send + Sync + 'static + Serialize + for<'de> Deserialize<'de> + Default,
{
fn from_ref(context: &GotchaContext<State, Config>) -> Self {
context.config.clone()
}
}
pub trait GotchaApp: Sized + Send + Sync {
type State: Clone + Send + Sync + 'static;
type Config: Clone + Send + Sync + 'static + Serialize + for<'de> Deserialize<'de> + Default;
fn config(&self) -> impl std::future::Future<Output = Result<ConfigWrapper<Self::Config>, Box<dyn std::error::Error>>> + Send {
async move {
let config = GotchaConfigLoader::load::<ConfigWrapper<Self::Config>>(std::env::var("GOTCHA_ACTIVE_PROFILE").ok());
Ok(config)
}
}
fn logger(&self) -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::registry()
.with(fmt::layer())
.with(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.with_env_var("RUST_LOG")
.from_env_lossy(),
)
.try_init()
.ok();
Ok(())
}
fn routes(&self, router: GotchaRouter<GotchaContext<Self::State, Self::Config>>) -> GotchaRouter<GotchaContext<Self::State, Self::Config>>;
fn state(&self, config: &ConfigWrapper<Self::Config>) -> impl std::future::Future<Output = Result<Self::State, Box<dyn std::error::Error>>> + Send;
#[cfg(feature = "task")]
fn tasks(
&self, _task_scheduler: &mut TaskScheduler<Self::State, Self::Config>,
) -> impl std::future::Future<Output = Result<(), Box<dyn std::error::Error>>> + Send {
async { Ok(()) }
}
fn build_router(
&self, context: GotchaContext<Self::State, Self::Config>,
) -> impl std::future::Future<Output = Result<axum::Router, Box<dyn std::error::Error>>> + Send {
async move {
let router = GotchaRouter::<GotchaContext<Self::State, Self::Config>>::default();
let router = self.routes(router);
let GotchaRouter {
#[cfg(feature = "openapi")]
operations,
router: raw_router,
} = router;
#[cfg(feature = "openapi")]
let openapi_spec = crate::openapi::generate_openapi(operations);
cfg_if::cfg_if! {
if #[cfg(feature = "openapi")] {
let router = raw_router
.with_state(context.clone())
.route("/openapi.json", axum::routing::get(|| async move { Json(openapi_spec.clone()) }))
.route("/redoc", axum::routing::get(openapi::openapi_html))
.route("/scalar", axum::routing::get(openapi::scalar_html));
}else {
let router = raw_router
.with_state(context.clone());
}
}
Ok(router)
}
}
fn run(self) -> impl std::future::Future<Output = Result<(), Box<dyn std::error::Error>>> + Send {
async move {
use std::net::{Ipv4Addr, SocketAddrV4};
use std::str::FromStr;
self.logger()?;
tracing::info!("logger has been initialized");
let config: ConfigWrapper<Self::Config> = self.config().await?;
let state = self.state(&config).await?;
let context = GotchaContext { config: config.clone(), state };
let router = self.build_router(context.clone()).await?;
cfg_if::cfg_if! {
if #[cfg(feature = "task")] {
let mut task_scheduler = TaskScheduler::new(context.clone());
self.tasks(&mut task_scheduler).await?;
}
}
let addr = SocketAddrV4::new(Ipv4Addr::from_str(&config.basic.host)?, config.basic.port);
let listener = tokio::net::TcpListener::bind(addr).await?;
axum::serve(listener, router).await?;
Ok(())
}
}
}
#[cfg(test)]
mod test {
#[test]
#[cfg(feature = "openapi")]
fn pass() {
let t = trybuild::TestCases::new();
t.pass("tests/pass/openapi/*.rs");
}
#[test]
#[cfg(feature = "openapi")]
fn test_handler() {
let t = trybuild::TestCases::new();
t.pass("tests/pass/handler/*.rs");
}
}