Skip to main content

cognee_http_server/
state.rs

1//! Application state — a single `Clone`-able struct injected into every handler
2//! via `axum::extract::State`.
3//!
4//! All fields are `Arc<…>` so `AppState::clone()` is cheap.  Axum clones the
5//! state once per request.
6
7use std::sync::Arc;
8
9#[cfg(feature = "telemetry")]
10use cognee_observability::TelemetryGuard;
11
12use cognee_core::PipelineRunRegistry;
13use cognee_core::pipeline_run_registry::DefaultPipelineRunRegistry;
14use cognee_database::{
15    DatabaseConnection, NoopPipelineRunRepository, PipelineRunRepository,
16    SeaOrmPipelineRunRepository,
17};
18
19use crate::{
20    auth_resolver::AuthResolver,
21    components::ComponentHandles,
22    config::{HttpServerConfig, RegistryConfig},
23    error::ServerError,
24    observability::{BufferConfig, SpanBuffer},
25    sync::SyncRegistry,
26};
27
28// ─── AppState ────────────────────────────────────────────────────────────────
29
30/// Per-server dependency container shared across all handlers.
31///
32/// Fields that depend on closed-side features (auth chain, mailer, etc.)
33/// are kept as injection seams (`Option<Arc<dyn ...>>`) so the closed
34/// `cognee-http-cloud` crate can populate them via the `RouterBuilder`.
35#[derive(Clone)]
36pub struct AppState {
37    /// HTTP server config (host, port, CORS, JWT, …).
38    pub config: Arc<HttpServerConfig>,
39
40    /// Background pipeline-run lifecycle registry.
41    ///
42    /// The inner `Arc<dyn PipelineRunRegistry>` is `Clone`-able cheaply.
43    pub pipelines: Arc<dyn PipelineRunRegistry>,
44
45    /// Pre-built component handles (database, storage, delete_service,
46    /// ontology_manager). `None` until `AppState::build` fully initialises
47    /// the backends — most tests leave this `None` and stub out the
48    /// relevant functionality directly.
49    pub lib: Option<Arc<ComponentHandles>>,
50
51    /// Closed-side authentication chain. `None` in pure-OSS builds; closed
52    /// embedders install one via `RouterBuilder::with_auth_resolver(...)`
53    /// or `RouterBuilder::with_extra_validator(...)`. When `None`, the
54    /// `AuthenticatedUser` extractor falls through to either a synthetic
55    /// default user (`require_authentication=false`) or a 401
56    /// (`require_authentication=true`).
57    pub auth_resolver: Option<Arc<dyn AuthResolver>>,
58
59    /// Health checker for /health endpoints. `None` falls back to a
60    /// synthetic `MockHealthChecker`. Embedders populate by calling
61    /// [`AppState::install_real_health_checker`] after wiring `lib`.
62    pub health: Option<Arc<dyn crate::routers::health::HealthChecker>>,
63
64    /// In-memory span buffer feeding `GET /api/v1/activity/spans`.
65    /// Always populated — `BufferConfig::from_env()` reads the cap. To
66    /// effectively disable the buffer pass `BufferConfig { max_traces: 0, .. }`.
67    pub spans: Arc<SpanBuffer>,
68
69    /// In-memory registry tracking one running cloud sync per user. Always
70    /// populated; the registry itself starts empty.
71    pub sync: Arc<SyncRegistry>,
72
73    /// Flush-on-drop guard for the OpenTelemetry exporter (decision 9).
74    /// Held only for its `Drop` side effect: the last `Arc` released calls
75    /// `provider.force_flush()` + `provider.shutdown()`. `None` when built
76    /// without explicit telemetry init (test paths, library embedders).
77    #[cfg(feature = "telemetry")]
78    pub telemetry_guard: Option<Arc<TelemetryGuard>>,
79}
80
81impl AppState {
82    /// Build a no-op `Arc<dyn PipelineRunRegistry>` backed by a
83    /// `NoopPipelineRunRepository`.  Useful in tests that construct `AppState`
84    /// directly without a real database.
85    pub fn noop_pipelines() -> Arc<dyn PipelineRunRegistry> {
86        let repo = NoopPipelineRunRepository::arc();
87        let cfg = RegistryConfig::default();
88        DefaultPipelineRunRegistry::new(repo, cfg)
89    }
90
91    /// Construct an `AppState` with the given config; all optional components
92    /// default to `None`.  Later phases call this and then set individual fields.
93    ///
94    /// Builds `DefaultPipelineRunRegistry` from the config's registry knobs and
95    /// runs the startup orphan-reset per pipelines.md §12 — any `INITIATED` /
96    /// `STARTED` rows left over from a previous unclean shutdown are rewritten to
97    /// `ERRORED` with `reason = "server_restart_orphan"`.
98    pub async fn build(config: HttpServerConfig) -> Result<Self, ServerError> {
99        // Build an in-memory-only pipeline run repository backed by a temporary
100        // SQLite database.  The real repository (backed by the server's own DB)
101        // is wired when `lib` is populated.  For now we use the shared
102        // `cognee_database::NoopPipelineRunRepository` (gap 08-07) so the
103        // registry is always non-None.
104        let repo = NoopPipelineRunRepository::arc();
105        let registry_cfg = config.to_registry_config();
106        let pipelines: Arc<dyn PipelineRunRegistry> =
107            DefaultPipelineRunRegistry::new(repo, registry_cfg);
108
109        Ok(Self {
110            config: Arc::new(config),
111            pipelines,
112            lib: None,
113            auth_resolver: None,
114            health: None,
115            spans: Arc::new(SpanBuffer::new(BufferConfig::from_env())),
116            sync: Arc::new(SyncRegistry::new()),
117            #[cfg(feature = "telemetry")]
118            telemetry_guard: None,
119        })
120    }
121
122    /// Convenience accessor for the component handles.
123    ///
124    /// Returns `None` when the server is running in test mode without backends
125    /// wired. Most integration tests build their own `ComponentHandles` directly.
126    pub fn components(&self) -> Option<&ComponentHandles> {
127        self.lib.as_deref()
128    }
129
130    /// Replace the `health` field with a `RealHealthChecker` built from the
131    /// currently-wired `ComponentHandles`. No-op when `lib` is `None`.
132    pub fn install_real_health_checker(&mut self) {
133        if let Some(handles) = &self.lib {
134            let checker = crate::health::RealHealthChecker::new(Arc::clone(handles), &self.config);
135            self.health = Some(Arc::new(checker));
136        }
137    }
138}
139
140// ─── Build state with a real database ─────────────────────────────────────────
141
142impl AppState {
143    /// Build `AppState` with a real `DatabaseConnection` wired into the pipeline
144    /// registry.  Used by the server startup path when backend env vars are
145    /// present.
146    ///
147    /// Runs the orphan-reset once on startup per pipelines.md §12.
148    pub async fn build_with_db(
149        config: HttpServerConfig,
150        db: Arc<DatabaseConnection>,
151    ) -> Result<Self, ServerError> {
152        let repo = Arc::new(SeaOrmPipelineRunRepository::new(Arc::clone(&db)))
153            as Arc<dyn PipelineRunRepository>;
154        let registry_cfg = config.to_registry_config();
155
156        // Run orphan reset on startup (best-effort — non-fatal).
157        let pipelines: Arc<dyn PipelineRunRegistry> =
158            match DefaultPipelineRunRegistry::new_with_orphan_reset(repo, registry_cfg).await {
159                Ok(r) => r,
160                Err(e) => {
161                    tracing::warn!(
162                        "pipeline registry startup orphan-reset failed (non-fatal): {e}"
163                    );
164                    // Fall back to plain new() without reset.
165                    let repo2 = Arc::new(SeaOrmPipelineRunRepository::new(Arc::clone(&db)))
166                        as Arc<dyn PipelineRunRepository>;
167                    DefaultPipelineRunRegistry::new(repo2, config.to_registry_config())
168                }
169            };
170
171        Ok(Self {
172            config: Arc::new(config),
173            pipelines,
174            lib: None,
175            auth_resolver: None,
176            health: None,
177            spans: Arc::new(SpanBuffer::new(BufferConfig::from_env())),
178            sync: Arc::new(SyncRegistry::new()),
179            #[cfg(feature = "telemetry")]
180            telemetry_guard: None,
181        })
182    }
183}