Skip to main content

ironflow_api/
state.rs

1//! Application state and dependency injection.
2//!
3//! [`AppState`] holds the shared [`RunStore`] and [`Engine`] used by all handlers.
4
5use axum::extract::FromRef;
6use ironflow_auth::jwt::JwtConfig;
7use ironflow_engine::engine::Engine;
8use ironflow_store::entities::Run;
9use ironflow_store::store::RunStore;
10use ironflow_store::user_store::UserStore;
11use std::sync::Arc;
12use uuid::Uuid;
13
14use crate::error::ApiError;
15
16/// Global application state.
17///
18/// Holds the shared run store and engine, extracted by handlers using Axum's
19/// state extraction mechanism.
20///
21/// # Examples
22///
23/// ```no_run
24/// use ironflow_api::state::AppState;
25/// use ironflow_auth::jwt::JwtConfig;
26/// use ironflow_store::prelude::*;
27/// use ironflow_engine::engine::Engine;
28/// use ironflow_core::providers::claude::ClaudeCodeProvider;
29/// use std::sync::Arc;
30///
31/// # async fn example() {
32/// let store = Arc::new(InMemoryStore::new());
33/// let user_store: Arc<dyn UserStore> = Arc::new(InMemoryStore::new());
34/// let provider = Arc::new(ClaudeCodeProvider::new());
35/// let engine = Arc::new(Engine::new(store.clone(), provider));
36/// let jwt_config = Arc::new(JwtConfig {
37///     secret: "secret".to_string(),
38///     access_token_ttl_secs: 900,
39///     refresh_token_ttl_secs: 604800,
40///     cookie_domain: None,
41///     cookie_secure: false,
42/// });
43/// let state = AppState { store, user_store, engine, jwt_config, worker_token: "token".to_string() };
44/// # }
45/// ```
46#[derive(Clone)]
47pub struct AppState {
48    /// The backing store for runs and steps.
49    pub store: Arc<dyn RunStore>,
50    /// The backing store for users.
51    pub user_store: Arc<dyn UserStore>,
52    /// The workflow orchestration engine.
53    pub engine: Arc<Engine>,
54    /// JWT configuration for auth tokens.
55    pub jwt_config: Arc<JwtConfig>,
56    /// Static token for worker-to-API authentication.
57    pub worker_token: String,
58}
59
60impl FromRef<AppState> for Arc<dyn RunStore> {
61    fn from_ref(state: &AppState) -> Self {
62        Arc::clone(&state.store)
63    }
64}
65
66impl FromRef<AppState> for Arc<dyn UserStore> {
67    fn from_ref(state: &AppState) -> Self {
68        Arc::clone(&state.user_store)
69    }
70}
71
72impl FromRef<AppState> for Arc<JwtConfig> {
73    fn from_ref(state: &AppState) -> Self {
74        Arc::clone(&state.jwt_config)
75    }
76}
77
78impl AppState {
79    /// Fetch a run by ID or return 404.
80    ///
81    /// # Errors
82    ///
83    /// Returns `ApiError::RunNotFound` if the run does not exist.
84    /// Returns `ApiError::Store` if there is a store error.
85    pub async fn get_run_or_404(&self, id: Uuid) -> Result<Run, ApiError> {
86        self.store
87            .get_run(id)
88            .await
89            .map_err(ApiError::from)?
90            .ok_or(ApiError::RunNotFound(id))
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use ironflow_core::providers::claude::ClaudeCodeProvider;
98    use ironflow_store::memory::InMemoryStore;
99
100    fn test_state() -> AppState {
101        let store = Arc::new(InMemoryStore::new());
102        let user_store = Arc::new(InMemoryStore::new());
103        let provider = Arc::new(ClaudeCodeProvider::new());
104        let engine = Arc::new(Engine::new(store.clone(), provider));
105        let jwt_config = Arc::new(JwtConfig {
106            secret: "test-secret".to_string(),
107            access_token_ttl_secs: 900,
108            refresh_token_ttl_secs: 604800,
109            cookie_domain: None,
110            cookie_secure: false,
111        });
112        AppState {
113            store,
114            user_store,
115            engine,
116            jwt_config,
117            worker_token: "test-worker-token".to_string(),
118        }
119    }
120
121    #[test]
122    fn app_state_cloneable() {
123        let state = test_state();
124        let _cloned = state.clone();
125    }
126
127    #[test]
128    fn app_state_from_ref() {
129        let state = test_state();
130        let extracted: Arc<dyn RunStore> = Arc::from_ref(&state);
131        assert!(Arc::ptr_eq(&extracted, &state.store));
132    }
133}