Skip to main content

purwa_core/
lib.rs

1//! Purwa core — HTTP kernel built on Axum (see workspace `purwa` facade).
2//!
3//! Applications can take full control of routing by using the [`AxumRouter`] type
4//! and Tower services directly (escape hatch; PRD §3.1).
5
6pub mod config;
7pub mod error;
8pub mod extract;
9pub mod logging;
10pub mod routing;
11
12use std::sync::Arc;
13
14use axum::extract::FromRef;
15use axum::{Router, routing::get};
16
17pub use config::{
18    AppConfig, AppSection, DatabaseSection, InertiaSection, PurwaConfigError, ServerSection,
19};
20pub use error::{PurwaError, ValidationErrorBody, flatten_validation_errors};
21pub use extract::{ValidatedForm, ValidatedJson};
22pub use logging::{init_tracing, init_tracing_with_filter};
23pub use routing::{
24    RegisteredRoute, RouteDescriptor, format_route_table, route_descriptors, router_from_inventory,
25};
26pub use sqlx::PgPool;
27
28/// Shared application state (PRD §5.3). Holds `Arc` resources only — no globals.
29#[derive(Clone)]
30pub struct AppState {
31    pub config: Arc<AppConfig>,
32    pub db: Arc<PgPool>,
33}
34
35impl AppState {
36    pub fn new(config: Arc<AppConfig>, db: Arc<PgPool>) -> Self {
37        Self { config, db }
38    }
39}
40
41impl FromRef<AppState> for Arc<AppConfig> {
42    fn from_ref(state: &AppState) -> Self {
43        state.config.clone()
44    }
45}
46
47impl FromRef<AppState> for PgPool {
48    fn from_ref(state: &AppState) -> Self {
49        state.db.as_ref().clone()
50    }
51}
52
53/// Type alias for the default Axum router with unit state — use this when composing custom routes.
54pub type AxumRouter = Router;
55
56/// Default application router: Sprint 1 hello on `/` plus any routes registered via Purwa macros
57/// ([`router_from_inventory`]).
58pub fn app_router() -> AxumRouter {
59    Router::new()
60        .route("/", get(|| async { "Hello, Purwa" }))
61        .merge(router_from_inventory())
62}
63
64#[cfg(test)]
65mod tests {
66    use super::app_router;
67    use axum::body::Body;
68    use axum::http::{Request, StatusCode};
69    use tower::ServiceExt;
70
71    #[tokio::test]
72    async fn root_returns_200() {
73        let app = app_router();
74        let response = app
75            .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
76            .await
77            .unwrap();
78        assert_eq!(response.status(), StatusCode::OK);
79    }
80}