Skip to main content

systemprompt_runtime/
context.rs

1//! [`AppContext`] — the application-wide runtime container.
2//!
3//! Holds shared handles (config, database pool, extension registry,
4//! analytics, route classifier, etc.) cloned cheaply via [`Arc`].
5//! Constructed via [`crate::AppContextBuilder`] or [`AppContext::new`].
6
7use std::sync::{Arc, OnceLock};
8
9use tokio::task::JoinHandle;
10
11use systemprompt_analytics::{AnalyticsService, FingerprintRepository, GeoIpReader};
12use systemprompt_database::DbPool;
13use systemprompt_extension::ExtensionRegistry;
14use systemprompt_marketplace::MarketplaceFilter;
15use systemprompt_mcp::services::registry::RegistryManager;
16use systemprompt_models::services::SystemAdmin;
17use systemprompt_models::{AppPaths, Config, ContentConfigRaw, ContentRouting, RouteClassifier};
18use systemprompt_security::authz::SharedAuthzHook;
19use systemprompt_users::UserService;
20
21use crate::builder::AppContextBuilder;
22use crate::context_loaders;
23use crate::error::RuntimeResult;
24use crate::registry::ModuleApiRegistry;
25
26#[derive(Clone)]
27pub struct AppContext {
28    pub(crate) config: Arc<Config>,
29    pub(crate) database: DbPool,
30    pub(crate) api_registry: Arc<ModuleApiRegistry>,
31    pub(crate) extension_registry: Arc<ExtensionRegistry>,
32    pub(crate) geoip_reader: Option<GeoIpReader>,
33    pub(crate) content_config: Option<Arc<ContentConfigRaw>>,
34    pub(crate) route_classifier: Arc<RouteClassifier>,
35    pub(crate) analytics_service: Arc<AnalyticsService>,
36    pub(crate) fingerprint_repo: Option<Arc<FingerprintRepository>>,
37    pub(crate) user_service: Option<Arc<UserService>>,
38    pub(crate) app_paths: Arc<AppPaths>,
39    pub(crate) marketplace_filter: Arc<dyn MarketplaceFilter>,
40    pub(crate) event_bridge: Arc<OnceLock<JoinHandle<()>>>,
41    pub(crate) system_admin: Arc<SystemAdmin>,
42    pub(crate) mcp_registry: RegistryManager,
43    pub(crate) authz_hook: SharedAuthzHook,
44}
45
46impl std::fmt::Debug for AppContext {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.debug_struct("AppContext")
49            .field("config", &"Config")
50            .field("database", &"DbPool")
51            .field("api_registry", &"ModuleApiRegistry")
52            .field("extension_registry", &self.extension_registry)
53            .field("geoip_reader", &self.geoip_reader.is_some())
54            .field("content_config", &self.content_config.is_some())
55            .field("route_classifier", &"RouteClassifier")
56            .field("analytics_service", &"AnalyticsService")
57            .field("fingerprint_repo", &self.fingerprint_repo.is_some())
58            .field("user_service", &self.user_service.is_some())
59            .field("app_paths", &"AppPaths")
60            .field("marketplace_filter", &self.marketplace_filter)
61            .field("event_bridge", &self.event_bridge.get().is_some())
62            .field("system_admin", &self.system_admin.username())
63            .field("mcp_registry", &"RegistryManager")
64            .field("authz_hook", &"SharedAuthzHook")
65            .finish()
66    }
67}
68
69#[derive(Debug)]
70pub struct AppContextParts {
71    pub config: Arc<Config>,
72    pub database: DbPool,
73    pub api_registry: Arc<ModuleApiRegistry>,
74    pub extension_registry: Arc<ExtensionRegistry>,
75    pub geoip_reader: Option<GeoIpReader>,
76    pub content_config: Option<Arc<ContentConfigRaw>>,
77    pub route_classifier: Arc<RouteClassifier>,
78    pub analytics_service: Arc<AnalyticsService>,
79    pub fingerprint_repo: Option<Arc<FingerprintRepository>>,
80    pub user_service: Option<Arc<UserService>>,
81    pub app_paths: Arc<AppPaths>,
82    pub marketplace_filter: Arc<dyn MarketplaceFilter>,
83    pub event_bridge: Arc<OnceLock<JoinHandle<()>>>,
84    pub system_admin: Arc<SystemAdmin>,
85    pub mcp_registry: RegistryManager,
86    pub authz_hook: SharedAuthzHook,
87}
88
89impl AppContext {
90    pub async fn new() -> RuntimeResult<Self> {
91        Self::builder().build().await
92    }
93
94    #[must_use]
95    pub fn builder() -> AppContextBuilder {
96        AppContextBuilder::new()
97    }
98
99    pub fn from_parts(parts: AppContextParts) -> Self {
100        Self {
101            config: parts.config,
102            database: parts.database,
103            api_registry: parts.api_registry,
104            extension_registry: parts.extension_registry,
105            geoip_reader: parts.geoip_reader,
106            content_config: parts.content_config,
107            route_classifier: parts.route_classifier,
108            analytics_service: parts.analytics_service,
109            fingerprint_repo: parts.fingerprint_repo,
110            user_service: parts.user_service,
111            app_paths: parts.app_paths,
112            marketplace_filter: parts.marketplace_filter,
113            event_bridge: parts.event_bridge,
114            system_admin: parts.system_admin,
115            mcp_registry: parts.mcp_registry,
116            authz_hook: parts.authz_hook,
117        }
118    }
119
120    pub fn load_geoip_database(config: &Config, show_warnings: bool) -> Option<GeoIpReader> {
121        context_loaders::load_geoip_database(config, show_warnings)
122    }
123
124    pub fn load_content_config(
125        config: &Config,
126        app_paths: &AppPaths,
127    ) -> Option<Arc<ContentConfigRaw>> {
128        context_loaders::load_content_config(config, app_paths)
129    }
130
131    pub fn config(&self) -> &Config {
132        &self.config
133    }
134
135    pub fn content_config(&self) -> Option<&ContentConfigRaw> {
136        self.content_config.as_ref().map(AsRef::as_ref)
137    }
138
139    pub fn content_routing(&self) -> Option<Arc<dyn ContentRouting>> {
140        let concrete = Arc::clone(self.content_config.as_ref()?);
141        let routing: Arc<dyn ContentRouting> = concrete;
142        Some(routing)
143    }
144
145    pub const fn db_pool(&self) -> &DbPool {
146        &self.database
147    }
148
149    pub const fn database(&self) -> &DbPool {
150        &self.database
151    }
152
153    pub fn api_registry(&self) -> &ModuleApiRegistry {
154        &self.api_registry
155    }
156
157    pub fn extension_registry(&self) -> &ExtensionRegistry {
158        &self.extension_registry
159    }
160
161    pub fn server_address(&self) -> String {
162        format!("{}:{}", self.config.host, self.config.port)
163    }
164
165    pub const fn geoip_reader(&self) -> Option<&GeoIpReader> {
166        self.geoip_reader.as_ref()
167    }
168
169    pub const fn analytics_service(&self) -> &Arc<AnalyticsService> {
170        &self.analytics_service
171    }
172
173    pub const fn route_classifier(&self) -> &Arc<RouteClassifier> {
174        &self.route_classifier
175    }
176
177    pub fn app_paths(&self) -> &AppPaths {
178        &self.app_paths
179    }
180
181    pub const fn app_paths_arc(&self) -> &Arc<AppPaths> {
182        &self.app_paths
183    }
184
185    pub fn marketplace_filter(&self) -> &Arc<dyn MarketplaceFilter> {
186        &self.marketplace_filter
187    }
188
189    pub const fn event_bridge(&self) -> &Arc<OnceLock<JoinHandle<()>>> {
190        &self.event_bridge
191    }
192
193    pub fn system_admin(&self) -> &SystemAdmin {
194        &self.system_admin
195    }
196
197    pub const fn mcp_registry(&self) -> &RegistryManager {
198        &self.mcp_registry
199    }
200
201    pub const fn authz_hook(&self) -> &SharedAuthzHook {
202        &self.authz_hook
203    }
204}