#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub use async_trait::async_trait;
pub use axum::extract::ws::{self, WebSocket, WebSocketUpgrade};
use axum::extract::FromRef;
pub use axum::extract::{Extension, Form, Json, Multipart, Path, Query, State};
pub use axum::extract::{MatchedPath, OriginalUri};
pub use axum::middleware;
pub use axum::response::sse::{self, Event, KeepAlive, Sse};
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, ServerConfig};
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;
pub use crate::error::{GotchaError, GotchaResult};
pub use gotcha_macro::{config, state};
pub mod message;
#[cfg(feature = "openapi")]
#[cfg_attr(docsrs, doc(cfg(feature = "openapi")))]
pub use gotcha_core::Responsible;
#[cfg(feature = "openapi")]
#[cfg_attr(docsrs, doc(cfg(feature = "openapi")))]
pub use crate::openapi::schematic::{ParameterProvider, Schematic};
#[cfg(feature = "openapi")]
#[cfg_attr(docsrs, doc(cfg(feature = "openapi")))]
pub use gotcha_macro::api;
#[cfg(feature = "openapi")]
#[cfg_attr(docsrs, doc(cfg(feature = "openapi")))]
pub use oas;
pub use crate::message::{Message, Messager};
#[cfg(feature = "openapi")]
#[cfg_attr(docsrs, doc(cfg(feature = "openapi")))]
pub use crate::openapi::Operable;
pub use crate::params::{Cookie, CookieParam, Header, HeaderParam, ParamRejection};
pub use crate::validation::{Valid, ValidRejection};
pub use axum_extra::{headers, TypedHeader};
pub use validator::Validate;
pub mod builder;
pub mod config;
pub mod error;
#[cfg(feature = "openapi")]
#[cfg_attr(docsrs, doc(cfg(feature = "openapi")))]
pub mod openapi;
pub mod params;
pub mod prelude;
pub mod router;
#[cfg(feature = "task")]
#[cfg_attr(docsrs, doc(cfg(feature = "task")))]
pub mod task;
pub mod validation;
#[cfg(feature = "prometheus")]
#[cfg_attr(docsrs, doc(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")]
#[cfg_attr(docsrs, doc(cfg(feature = "openapi")))]
pub use crate::openapi::schematic::EnhancedSchema;
pub use serde_json;
#[cfg(feature = "task")]
#[cfg_attr(docsrs, doc(cfg(feature = "task")))]
pub use task::TaskScheduler;
#[cfg(feature = "static_files")]
#[cfg_attr(docsrs, doc(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()
}
}
impl<State, Config> FromRef<GotchaContext<State, Config>> for crate::config::ServerConfig
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.server.clone()
}
}
pub trait GotchaConfig: Clone + Send + Sync + 'static + Serialize + for<'de> Deserialize<'de> + Default {}
impl<T> GotchaConfig for T where T: Clone + Send + Sync + 'static + Serialize + for<'de> Deserialize<'de> + Default {}
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 = GotchaResult<ConfigWrapper<Self::Config>>> + Send {
async move {
let config = GotchaConfigLoader::load::<ConfigWrapper<Self::Config>>(std::env::var("GOTCHA_ACTIVE_PROFILE").ok())?;
Ok(config)
}
}
fn logger(&self) -> GotchaResult<()> {
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 = GotchaResult<Self::State>> + Send;
#[cfg(feature = "task")]
fn tasks(&self, _task_scheduler: &mut TaskScheduler<Self::State, Self::Config>) -> impl std::future::Future<Output = GotchaResult<()>> + Send {
async { Ok(()) }
}
fn build_router(&self, context: GotchaContext<Self::State, Self::Config>) -> impl std::future::Future<Output = GotchaResult<axum::Router>> + Send {
async move {
let router = GotchaRouter::<GotchaContext<Self::State, Self::Config>>::default();
let router = self.routes(router);
Ok(router.into_axum_router(context))
}
}
fn run(self) -> impl std::future::Future<Output = GotchaResult<()>> + 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 ip = Ipv4Addr::from_str(&config.server.host).map_err(|_| GotchaError::InvalidAddress(config.server.host.clone()))?;
let addr = SocketAddrV4::new(ip, config.server.port);
let listener = tokio::net::TcpListener::bind(addr).await.map_err(|source| GotchaError::Bind {
addr: addr.to_string(),
source,
})?;
axum::serve(listener, router).await.map_err(GotchaError::Io)?;
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");
}
}