systemprompt_runtime/context/
mod.rs1use std::sync::{Arc, OnceLock};
11
12use tokio::task::JoinHandle;
13
14use systemprompt_analytics::{AnalyticsService, FingerprintRepository, GeoIpReader};
15use systemprompt_database::DbPool;
16use systemprompt_extension::ExtensionRegistry;
17use systemprompt_marketplace::MarketplaceFilter;
18use systemprompt_mcp::services::registry::RegistryService;
19use systemprompt_models::services::SystemAdmin;
20use systemprompt_models::{AppPaths, Config, ContentConfigRaw, ContentRouting, RouteClassifier};
21use systemprompt_security::authz::SharedAuthzHook;
22use systemprompt_users::UserService;
23
24mod context_loaders;
25
26use crate::builder::AppContextBuilder;
27use crate::error::RuntimeResult;
28use crate::registry::ModuleApiRegistry;
29
30#[derive(Clone)]
35pub struct DataPlane {
36 pub database: DbPool,
37 pub analytics_service: Arc<AnalyticsService>,
38 pub fingerprint_repo: Option<Arc<FingerprintRepository>>,
39 pub user_service: Option<Arc<UserService>>,
40}
41
42#[derive(Clone)]
43pub struct ConfigPlane {
44 pub config: Arc<Config>,
45 pub app_paths: Arc<AppPaths>,
46 pub content_config: Option<Arc<ContentConfigRaw>>,
47 pub route_classifier: Arc<RouteClassifier>,
48}
49
50#[derive(Clone)]
51pub struct Plugins {
52 pub extension_registry: Arc<ExtensionRegistry>,
53 pub api_registry: Arc<ModuleApiRegistry>,
54 pub mcp_registry: RegistryService,
55 pub marketplace_filter: Arc<dyn MarketplaceFilter>,
56}
57
58#[derive(Clone)]
59pub struct Subsystems {
60 pub system_admin: Arc<SystemAdmin>,
61 pub authz_hook: SharedAuthzHook,
62 pub event_bridge: Arc<OnceLock<JoinHandle<()>>>,
63 pub geoip_reader: Option<GeoIpReader>,
64}
65
66#[derive(Clone)]
78pub struct AppContext {
79 pub(crate) data: DataPlane,
80 pub(crate) cfg: ConfigPlane,
81 pub(crate) plugins: Plugins,
82 pub(crate) subsystems: Subsystems,
83}
84
85impl std::fmt::Debug for AppContext {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 f.debug_struct("AppContext")
88 .field("config", &"Config")
89 .field("database", &"DbPool")
90 .field("api_registry", &"ModuleApiRegistry")
91 .field("extension_registry", &self.plugins.extension_registry)
92 .field("geoip_reader", &self.subsystems.geoip_reader.is_some())
93 .field("content_config", &self.cfg.content_config.is_some())
94 .field("route_classifier", &"RouteClassifier")
95 .field("analytics_service", &"AnalyticsService")
96 .field("fingerprint_repo", &self.data.fingerprint_repo.is_some())
97 .field("user_service", &self.data.user_service.is_some())
98 .field("app_paths", &"AppPaths")
99 .field("marketplace_filter", &self.plugins.marketplace_filter)
100 .field(
101 "event_bridge",
102 &self.subsystems.event_bridge.get().is_some(),
103 )
104 .field("system_admin", &self.subsystems.system_admin.username())
105 .field("mcp_registry", &"RegistryService")
106 .field("authz_hook", &"SharedAuthzHook")
107 .finish()
108 }
109}
110
111impl std::fmt::Debug for DataPlane {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 f.debug_struct("DataPlane")
114 .field("database", &"DbPool")
115 .field("analytics_service", &"AnalyticsService")
116 .field("fingerprint_repo", &self.fingerprint_repo.is_some())
117 .field("user_service", &self.user_service.is_some())
118 .finish()
119 }
120}
121
122impl std::fmt::Debug for ConfigPlane {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 f.debug_struct("ConfigPlane")
125 .field("config", &"Config")
126 .field("app_paths", &"AppPaths")
127 .field("content_config", &self.content_config.is_some())
128 .field("route_classifier", &"RouteClassifier")
129 .finish()
130 }
131}
132
133impl std::fmt::Debug for Plugins {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 f.debug_struct("Plugins")
136 .field("extension_registry", &self.extension_registry)
137 .field("api_registry", &"ModuleApiRegistry")
138 .field("mcp_registry", &"RegistryService")
139 .field("marketplace_filter", &self.marketplace_filter)
140 .finish()
141 }
142}
143
144impl std::fmt::Debug for Subsystems {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 f.debug_struct("Subsystems")
147 .field("system_admin", &self.system_admin.username())
148 .field("authz_hook", &"SharedAuthzHook")
149 .field("event_bridge", &self.event_bridge.get().is_some())
150 .field("geoip_reader", &self.geoip_reader.is_some())
151 .finish()
152 }
153}
154
155impl AppContext {
156 pub async fn new() -> RuntimeResult<Self> {
157 Self::builder().build().await
158 }
159
160 #[must_use]
161 pub fn builder() -> AppContextBuilder {
162 AppContextBuilder::new()
163 }
164
165 #[must_use]
169 pub const fn from_parts(
170 data: DataPlane,
171 cfg: ConfigPlane,
172 plugins: Plugins,
173 subsystems: Subsystems,
174 ) -> Self {
175 Self {
176 data,
177 cfg,
178 plugins,
179 subsystems,
180 }
181 }
182
183 pub fn load_geoip_database(
184 config: &Config,
185 show_warnings: bool,
186 ) -> Result<Option<GeoIpReader>, crate::error::RuntimeError> {
187 context_loaders::load_geoip_database(config, show_warnings)
188 }
189
190 pub fn load_content_config(
191 config: &Config,
192 app_paths: &AppPaths,
193 ) -> Option<Arc<ContentConfigRaw>> {
194 context_loaders::load_content_config(config, app_paths)
195 }
196
197 pub fn config(&self) -> &Config {
198 &self.cfg.config
199 }
200
201 pub fn content_config(&self) -> Option<&ContentConfigRaw> {
202 self.cfg.content_config.as_ref().map(AsRef::as_ref)
203 }
204
205 pub fn content_routing(&self) -> Option<Arc<dyn ContentRouting>> {
206 let concrete = Arc::clone(self.cfg.content_config.as_ref()?);
207 let routing: Arc<dyn ContentRouting> = concrete;
208 Some(routing)
209 }
210
211 pub const fn db_pool(&self) -> &DbPool {
212 &self.data.database
213 }
214
215 pub fn api_registry(&self) -> &ModuleApiRegistry {
216 &self.plugins.api_registry
217 }
218
219 pub fn extension_registry(&self) -> &ExtensionRegistry {
220 &self.plugins.extension_registry
221 }
222
223 pub fn server_address(&self) -> String {
224 format!("{}:{}", self.cfg.config.host, self.cfg.config.port)
225 }
226
227 pub const fn geoip_reader(&self) -> Option<&GeoIpReader> {
228 self.subsystems.geoip_reader.as_ref()
229 }
230
231 pub const fn analytics_service(&self) -> &Arc<AnalyticsService> {
232 &self.data.analytics_service
233 }
234
235 pub const fn route_classifier(&self) -> &Arc<RouteClassifier> {
236 &self.cfg.route_classifier
237 }
238
239 pub fn app_paths(&self) -> &AppPaths {
240 &self.cfg.app_paths
241 }
242
243 pub const fn app_paths_arc(&self) -> &Arc<AppPaths> {
244 &self.cfg.app_paths
245 }
246
247 pub fn marketplace_filter(&self) -> &Arc<dyn MarketplaceFilter> {
248 &self.plugins.marketplace_filter
249 }
250
251 pub const fn event_bridge(&self) -> &Arc<OnceLock<JoinHandle<()>>> {
252 &self.subsystems.event_bridge
253 }
254
255 pub fn system_admin(&self) -> &SystemAdmin {
256 &self.subsystems.system_admin
257 }
258
259 pub const fn mcp_registry(&self) -> &RegistryService {
260 &self.plugins.mcp_registry
261 }
262
263 pub const fn authz_hook(&self) -> &SharedAuthzHook {
264 &self.subsystems.authz_hook
265 }
266}