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;
8
9use systemprompt_analytics::{AnalyticsService, FingerprintRepository, GeoIpReader};
10use systemprompt_database::DbPool;
11use systemprompt_extension::ExtensionRegistry;
12use systemprompt_models::{AppPaths, Config, ContentConfigRaw, ContentRouting, RouteClassifier};
13use systemprompt_users::UserService;
14
15use crate::builder::AppContextBuilder;
16use crate::context_loaders;
17use crate::error::RuntimeResult;
18use crate::registry::ModuleApiRegistry;
19
20#[derive(Clone)]
21pub struct AppContext {
22    pub(crate) config: Arc<Config>,
23    pub(crate) database: DbPool,
24    pub(crate) api_registry: Arc<ModuleApiRegistry>,
25    pub(crate) extension_registry: Arc<ExtensionRegistry>,
26    pub(crate) geoip_reader: Option<GeoIpReader>,
27    pub(crate) content_config: Option<Arc<ContentConfigRaw>>,
28    pub(crate) route_classifier: Arc<RouteClassifier>,
29    pub(crate) analytics_service: Arc<AnalyticsService>,
30    pub(crate) fingerprint_repo: Option<Arc<FingerprintRepository>>,
31    pub(crate) user_service: Option<Arc<UserService>>,
32    pub(crate) app_paths: Arc<AppPaths>,
33}
34
35impl std::fmt::Debug for AppContext {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        f.debug_struct("AppContext")
38            .field("config", &"Config")
39            .field("database", &"DbPool")
40            .field("api_registry", &"ModuleApiRegistry")
41            .field("extension_registry", &self.extension_registry)
42            .field("geoip_reader", &self.geoip_reader.is_some())
43            .field("content_config", &self.content_config.is_some())
44            .field("route_classifier", &"RouteClassifier")
45            .field("analytics_service", &"AnalyticsService")
46            .field("fingerprint_repo", &self.fingerprint_repo.is_some())
47            .field("user_service", &self.user_service.is_some())
48            .field("app_paths", &"AppPaths")
49            .finish()
50    }
51}
52
53#[derive(Debug)]
54pub struct AppContextParts {
55    pub config: Arc<Config>,
56    pub database: DbPool,
57    pub api_registry: Arc<ModuleApiRegistry>,
58    pub extension_registry: Arc<ExtensionRegistry>,
59    pub geoip_reader: Option<GeoIpReader>,
60    pub content_config: Option<Arc<ContentConfigRaw>>,
61    pub route_classifier: Arc<RouteClassifier>,
62    pub analytics_service: Arc<AnalyticsService>,
63    pub fingerprint_repo: Option<Arc<FingerprintRepository>>,
64    pub user_service: Option<Arc<UserService>>,
65    pub app_paths: Arc<AppPaths>,
66}
67
68impl AppContext {
69    pub async fn new() -> RuntimeResult<Self> {
70        Self::builder().build().await
71    }
72
73    #[must_use]
74    pub fn builder() -> AppContextBuilder {
75        AppContextBuilder::new()
76    }
77
78    pub fn from_parts(parts: AppContextParts) -> Self {
79        Self {
80            config: parts.config,
81            database: parts.database,
82            api_registry: parts.api_registry,
83            extension_registry: parts.extension_registry,
84            geoip_reader: parts.geoip_reader,
85            content_config: parts.content_config,
86            route_classifier: parts.route_classifier,
87            analytics_service: parts.analytics_service,
88            fingerprint_repo: parts.fingerprint_repo,
89            user_service: parts.user_service,
90            app_paths: parts.app_paths,
91        }
92    }
93
94    pub fn load_geoip_database(config: &Config, show_warnings: bool) -> Option<GeoIpReader> {
95        context_loaders::load_geoip_database(config, show_warnings)
96    }
97
98    pub fn load_content_config(
99        config: &Config,
100        app_paths: &AppPaths,
101    ) -> Option<Arc<ContentConfigRaw>> {
102        context_loaders::load_content_config(config, app_paths)
103    }
104
105    pub fn config(&self) -> &Config {
106        &self.config
107    }
108
109    pub fn content_config(&self) -> Option<&ContentConfigRaw> {
110        self.content_config.as_ref().map(AsRef::as_ref)
111    }
112
113    pub fn content_routing(&self) -> Option<Arc<dyn ContentRouting>> {
114        let concrete = Arc::clone(self.content_config.as_ref()?);
115        let routing: Arc<dyn ContentRouting> = concrete;
116        Some(routing)
117    }
118
119    pub const fn db_pool(&self) -> &DbPool {
120        &self.database
121    }
122
123    pub const fn database(&self) -> &DbPool {
124        &self.database
125    }
126
127    pub fn api_registry(&self) -> &ModuleApiRegistry {
128        &self.api_registry
129    }
130
131    pub fn extension_registry(&self) -> &ExtensionRegistry {
132        &self.extension_registry
133    }
134
135    pub fn server_address(&self) -> String {
136        format!("{}:{}", self.config.host, self.config.port)
137    }
138
139    pub fn get_provided_audiences() -> Vec<String> {
140        vec!["a2a".to_string(), "api".to_string(), "mcp".to_string()]
141    }
142
143    pub fn get_valid_audiences(_module_name: &str) -> Vec<String> {
144        Self::get_provided_audiences()
145    }
146
147    pub fn get_server_audiences(_server_name: &str, _port: u16) -> Vec<String> {
148        Self::get_provided_audiences()
149    }
150
151    pub const fn geoip_reader(&self) -> Option<&GeoIpReader> {
152        self.geoip_reader.as_ref()
153    }
154
155    pub const fn analytics_service(&self) -> &Arc<AnalyticsService> {
156        &self.analytics_service
157    }
158
159    pub const fn route_classifier(&self) -> &Arc<RouteClassifier> {
160        &self.route_classifier
161    }
162
163    pub fn app_paths(&self) -> &AppPaths {
164        &self.app_paths
165    }
166
167    pub const fn app_paths_arc(&self) -> &Arc<AppPaths> {
168        &self.app_paths
169    }
170}