cja/app_state.rs
1use crate::server::cookies::CookieKey;
2
3/// A trait representing the application state that must be implemented by users of the cja framework.
4///
5/// This trait provides access to core components like the database pool and cookie encryption key.
6/// The `AppState` is cloned for each request, so it should be cheap to clone (usually by wrapping
7/// expensive resources in Arc).
8///
9/// # Example
10///
11/// ```rust
12/// use cja::app_state::AppState;
13/// use cja::server::cookies::CookieKey;
14/// use sqlx::PgPool;
15///
16/// #[derive(Clone)]
17/// struct MyAppState {
18/// db: PgPool,
19/// cookie_key: CookieKey,
20/// }
21///
22/// impl AppState for MyAppState {
23/// fn version(&self) -> &str {
24/// "1.0.0"
25/// }
26///
27/// fn db(&self) -> &PgPool {
28/// &self.db
29/// }
30///
31/// fn cookie_key(&self) -> &CookieKey {
32/// &self.cookie_key
33/// }
34/// }
35/// ```
36///
37/// # Using with Axum
38///
39/// ```rust
40/// use cja::app_state::AppState;
41/// use cja::server::cookies::CookieKey;
42/// use sqlx::postgres::PgPoolOptions;
43/// use axum::{Router, routing::get, extract::State};
44///
45/// #[derive(Clone)]
46/// struct MyAppState {
47/// db: sqlx::PgPool,
48/// cookie_key: CookieKey,
49/// }
50///
51/// impl AppState for MyAppState {
52/// fn version(&self) -> &str { "1.0.0" }
53/// fn db(&self) -> &sqlx::PgPool { &self.db }
54/// fn cookie_key(&self) -> &CookieKey { &self.cookie_key }
55/// }
56///
57/// async fn handler(State(state): State<MyAppState>) -> String {
58/// format!("App version: {}", state.version())
59/// }
60///
61/// async fn example() -> Result<(), Box<dyn std::error::Error>> {
62/// use sqlx::postgres::PgPoolOptions;
63/// let db = PgPoolOptions::new()
64/// .connect(&std::env::var("DATABASE_URL").unwrap_or_else(|_| "postgres://test".to_string()))
65/// .await
66/// .map_err(|_| "Database connection failed")?;
67///
68/// let state = MyAppState {
69/// db,
70/// cookie_key: CookieKey::from_env_or_generate()?,
71/// };
72///
73/// let app: Router = Router::new()
74/// .route("/", get(handler))
75/// .with_state(state);
76///
77/// // Run the server
78/// # Ok(())
79/// # }
80/// ```
81pub trait AppState: Clone + Send + Sync + 'static {
82 /// Returns the version string for the application.
83 /// This is useful for health checks and debugging.
84 fn version(&self) -> &str;
85
86 /// Returns a reference to the database connection pool.
87 /// The pool is typically shared across all requests.
88 fn db(&self) -> &sqlx::PgPool;
89
90 /// Called after a successful autocommit job enqueue. An override may wake
91 /// a process-local idle poll gate; this is not a transaction-commit hook.
92 fn job_enqueued(&self) {}
93
94 /// Returns the cookie encryption key used for secure cookies.
95 /// This key should be consistent across application restarts
96 /// to maintain session continuity.
97 fn cookie_key(&self) -> &CookieKey;
98
99 /// Returns the application's public HTTP(S) origin or base URL for Eyes monitors.
100 ///
101 /// Root-relative monitor targets use URL join semantics and therefore discard
102 /// any path component here (`https://example.com/app` plus `/health` becomes
103 /// `https://example.com/health`). Path-prefixed apps should declare absolute
104 /// monitor targets instead.
105 fn eyes_base_url(&self) -> Option<&str> {
106 None
107 }
108
109 /// Returns the app-level HTTP monitors reported to Eyes.
110 ///
111 /// Monitor IDs must be stable identifiers. Applications can use the cja
112 /// re-export without depending on `eyes-subscriber` directly:
113 ///
114 /// ```
115 /// use cja::eyes_subscriber::{HttpMethod, HttpMonitor};
116 /// let monitors = vec![
117 /// HttpMonitor::new("health", "/health"),
118 /// HttpMonitor::new("headers", "https://example.com/").method(HttpMethod::Head),
119 /// ];
120 /// assert_eq!(monitors.len(), 2);
121 /// ```
122 fn eyes_http_monitors(&self) -> Vec<crate::eyes_subscriber::HttpMonitor> {
123 Vec::new()
124 }
125}