1use std::sync::Arc;
6#[cfg(feature = "prometheus")]
7use std::sync::OnceLock;
8
9use axum::extract::FromRef;
10#[cfg(feature = "prometheus")]
11use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
12use uuid::Uuid;
13
14use ironflow_auth::jwt::JwtConfig;
15use ironflow_engine::engine::Engine;
16use ironflow_store::entities::Run;
17use ironflow_store::store::RunStore;
18use ironflow_store::user_store::UserStore;
19
20use crate::error::ApiError;
21
22#[derive(Clone)]
53pub struct AppState {
54 pub store: Arc<dyn RunStore>,
56 pub user_store: Arc<dyn UserStore>,
58 pub engine: Arc<Engine>,
60 pub jwt_config: Arc<JwtConfig>,
62 pub worker_token: String,
64 #[cfg(feature = "prometheus")]
66 pub prometheus_handle: PrometheusHandle,
67}
68
69impl FromRef<AppState> for Arc<dyn RunStore> {
70 fn from_ref(state: &AppState) -> Self {
71 Arc::clone(&state.store)
72 }
73}
74
75impl FromRef<AppState> for Arc<dyn UserStore> {
76 fn from_ref(state: &AppState) -> Self {
77 Arc::clone(&state.user_store)
78 }
79}
80
81impl FromRef<AppState> for Arc<JwtConfig> {
82 fn from_ref(state: &AppState) -> Self {
83 Arc::clone(&state.jwt_config)
84 }
85}
86
87#[cfg(feature = "prometheus")]
88impl FromRef<AppState> for PrometheusHandle {
89 fn from_ref(state: &AppState) -> Self {
90 state.prometheus_handle.clone()
91 }
92}
93
94impl AppState {
95 pub fn new(
111 store: Arc<dyn RunStore>,
112 user_store: Arc<dyn UserStore>,
113 engine: Arc<Engine>,
114 jwt_config: Arc<JwtConfig>,
115 worker_token: String,
116 ) -> Self {
117 Self {
118 store,
119 user_store,
120 engine,
121 jwt_config,
122 worker_token,
123 #[cfg(feature = "prometheus")]
124 prometheus_handle: Self::global_prometheus_handle(),
125 }
126 }
127
128 #[cfg(feature = "prometheus")]
130 fn global_prometheus_handle() -> PrometheusHandle {
131 static HANDLE: OnceLock<PrometheusHandle> = OnceLock::new();
132 HANDLE
133 .get_or_init(|| {
134 PrometheusBuilder::new()
135 .install_recorder()
136 .expect("failed to install Prometheus recorder")
137 })
138 .clone()
139 }
140
141 pub async fn get_run_or_404(&self, id: Uuid) -> Result<Run, ApiError> {
148 self.store
149 .get_run(id)
150 .await
151 .map_err(ApiError::from)?
152 .ok_or(ApiError::RunNotFound(id))
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use ironflow_core::providers::claude::ClaudeCodeProvider;
160 use ironflow_store::memory::InMemoryStore;
161
162 fn test_state() -> AppState {
163 let store = Arc::new(InMemoryStore::new());
164 let user_store: Arc<dyn UserStore> = Arc::new(InMemoryStore::new());
165 let provider = Arc::new(ClaudeCodeProvider::new());
166 let engine = Arc::new(Engine::new(store.clone(), provider));
167 let jwt_config = Arc::new(JwtConfig {
168 secret: "test-secret".to_string(),
169 access_token_ttl_secs: 900,
170 refresh_token_ttl_secs: 604800,
171 cookie_domain: None,
172 cookie_secure: false,
173 });
174 AppState::new(
175 store,
176 user_store,
177 engine,
178 jwt_config,
179 "test-worker-token".to_string(),
180 )
181 }
182
183 #[test]
184 fn app_state_cloneable() {
185 let state = test_state();
186 let _cloned = state.clone();
187 }
188
189 #[test]
190 fn app_state_from_ref() {
191 let state = test_state();
192 let extracted: Arc<dyn RunStore> = Arc::from_ref(&state);
193 assert!(Arc::ptr_eq(&extracted, &state.store));
194 }
195}