dmsc/core/context.rs
1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//! http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! # Service Context
19//!
20//! The service context provides access to all core functionalities of DMSC,
21//! acting as a central hub for accessing various components such as logging,
22//! configuration, file system, and hooks.
23
24#![allow(non_snake_case)]
25
26use crate::fs::DMSCFileSystem;
27use crate::log::{DMSCLogConfig, DMSCLogger};
28use crate::config::DMSCConfigManager;
29use crate::hooks::DMSCHookBus;
30use crate::core::DMSCResult;
31use crate::observability::DMSCMetricsRegistry;
32use std::sync::Arc;
33
34/// Internal service context implementation. Not exposed directly to users.
35///
36/// This struct contains all the core components of the service context,
37/// but is wrapped by `DMSCServiceContext` for controlled access.
38#[derive(Clone)]
39pub struct ServiceContextInner {
40 /// File system accessor for secure file operations
41 pub fs: DMSCFileSystem,
42 /// Logger for structured logging
43 pub logger: Arc<DMSCLogger>,
44 /// Configuration manager for accessing application settings
45 pub config: Arc<DMSCConfigManager>,
46 /// Hook bus for emitting and handling lifecycle events
47 pub hooks: Arc<DMSCHookBus>,
48 /// Metrics registry for observability (optional)
49 pub metrics_registry: Option<Arc<DMSCMetricsRegistry>>,
50}
51
52impl ServiceContextInner {
53 /// Create a new `ServiceContextInner` instance with the provided components.
54 ///
55 /// # Parameters
56 ///
57 /// - `fs`: File system accessor
58 /// - `logger`: Structured logger
59 /// - `config`: Configuration manager
60 /// - `hooks`: Hook bus for lifecycle events
61 /// - `metrics_registry`: Optional metrics registry for observability
62 ///
63 /// # Returns
64 ///
65 /// A new `ServiceContextInner` instance.
66 pub fn new(fs: DMSCFileSystem, logger: DMSCLogger, config: DMSCConfigManager, hooks: DMSCHookBus, metrics_registry: Option<Arc<DMSCMetricsRegistry>>) -> Self {
67 ServiceContextInner {
68 fs,
69 logger: Arc::new(logger),
70 config: Arc::new(config),
71 hooks: Arc::new(hooks),
72 metrics_registry
73 }
74 }
75
76
77}
78
79/// Public-facing service context for DMSC applications.
80///
81/// The `DMSCServiceContext` is the primary way for modules and business logic to
82/// access core DMSC functionalities. It follows the dependency injection pattern,
83/// providing a centralized access point to all core components.
84///
85/// ## Design Principle
86///
87/// The service context is designed to be immutable from the outside, with controlled
88/// access to mutable components through dedicated methods. This ensures thread safety
89/// while allowing for necessary mutations in specific contexts.
90///
91/// ## Usage
92///
93/// ```rust
94/// use dmsc::prelude::*;
95///
96/// async fn handle_request(ctx: &DMSCServiceContext) -> DMSCResult<()> {
97/// // Access logger
98/// ctx.logger().info("request", "Handling request");
99///
100/// // Access configuration
101/// let config_value = ctx.config().config().get_str("app.name");
102///
103/// // Access file system
104/// let file_path = ctx.fs().app_data_path("logs/app.log");
105///
106/// Ok(())
107/// }
108/// ```
109#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
110#[derive(Clone)]
111pub struct DMSCServiceContext {
112 /// Internal implementation details
113 inner: ServiceContextInner,
114}
115
116impl DMSCServiceContext {
117 /// Create a new `DMSCServiceContext` with the provided components.
118 ///
119 /// This method is typically used by the framework itself during application startup,
120 /// but can be used for testing or custom initialization.
121 ///
122 /// # Parameters
123 ///
124 /// - `fs`: File system accessor
125 /// - `logger`: Structured logger
126 /// - `config`: Configuration manager
127 /// - `hooks`: Hook bus for lifecycle events
128 /// - `metrics_registry`: Optional metrics registry for observability
129 ///
130 /// # Returns
131 ///
132 /// A new `DMSCServiceContext` instance.
133 pub fn new_with(fs: DMSCFileSystem, logger: DMSCLogger, config: DMSCConfigManager, hooks: DMSCHookBus, metrics_registry: Option<Arc<DMSCMetricsRegistry>>) -> Self {
134 let inner = ServiceContextInner::new(fs, logger, config, hooks, metrics_registry);
135 DMSCServiceContext { inner }
136 }
137
138
139
140 /// Create a new `DMSCServiceContext` with default configuration.
141 ///
142 /// This is the most common way to create a service context, as it handles
143 /// the initialization of all core components automatically.
144 ///
145 /// # Returns
146 ///
147 /// A `DMSCResult` containing the new service context, or an error if initialization failed.
148 ///
149 /// # Errors
150 ///
151 /// - If the project root directory cannot be determined
152 /// - If there are issues initializing any of the core components
153 pub fn new_default() -> DMSCResult<Self> {
154 // Create default configuration manager
155 let config = DMSCConfigManager::new_default();
156 let cfg = config.config();
157
158 // Determine project root directory
159 let project_root = std::env::current_dir()
160 .map_err(|e| crate::core::DMSCError::Other(format!("detect project root failed: {e}")))?;
161
162 // Determine application data root directory
163 let app_data_root = if let Some(root_str) = cfg.get_str("fs.app_data_root") {
164 project_root.join(root_str)
165 } else {
166 project_root.join(".dms")
167 };
168
169 // Initialize file system
170 let fs = DMSCFileSystem::new_with_roots(project_root, app_data_root);
171
172 // Initialize logging
173 let log_config = DMSCLogConfig::from_config(&cfg);
174 let logger = DMSCLogger::new(&log_config, fs.clone());
175
176 // Initialize hook bus
177 let hooks = DMSCHookBus::new();
178
179 Ok(DMSCServiceContext::new_with(fs, logger, config, hooks, None))
180 }
181
182 /// Get a reference to the file system accessor.
183 ///
184 /// # Returns
185 ///
186 /// A reference to the `DMSCFileSystem` instance.
187 pub fn fs(&self) -> &DMSCFileSystem {
188 &self.inner.fs
189 }
190
191
192
193 /// Get a reference to the structured logger.
194 ///
195 /// # Returns
196 ///
197 /// A reference to the `DMSCLogger` instance.
198 pub fn logger(&self) -> &DMSCLogger {
199 self.inner.logger.as_ref()
200 }
201
202
203
204 /// Get a reference to the configuration manager.
205 ///
206 /// # Returns
207 ///
208 /// A reference to the `DMSCConfigManager` instance.
209 pub fn config(&self) -> Arc<DMSCConfigManager> {
210 self.inner.config.clone()
211 }
212
213
214
215 /// Get a reference to the hook bus for emitting events.
216 ///
217 /// # Returns
218 ///
219 /// A reference to the `DMSCHookBus` instance.
220 pub fn hooks(&self) -> Arc<DMSCHookBus> {
221 self.inner.hooks.clone()
222 }
223
224
225
226 /// Get a mutable reference to the hook bus for registering handlers.
227 ///
228 /// # Returns
229 ///
230 /// A mutable reference to the `DMSCHookBus` instance.
231 pub fn hooks_mut(&mut self) -> &mut DMSCHookBus {
232 Arc::get_mut(&mut self.inner.hooks).expect("Cannot get mutable reference to hooks - shared ownership")
233 }
234
235 /// Get a mutable reference to the configuration manager.
236 ///
237 /// # Returns
238 ///
239 /// A mutable reference to the `DMSCConfigManager` instance.
240 pub fn config_mut(&mut self) -> &mut DMSCConfigManager {
241 Arc::get_mut(&mut self.inner.config).expect("Cannot get mutable reference to config - shared ownership")
242 }
243
244 /// Get a mutable reference to the file system accessor.
245 ///
246 /// # Returns
247 ///
248 /// A mutable reference to to the `DMSCFileSystem` instance.
249 pub fn fs_mut(&mut self) -> &mut DMSCFileSystem {
250 &mut self.inner.fs
251 }
252
253 /// Get a mutable reference to the structured logger.
254 ///
255 /// # Returns
256 ///
257 /// A mutable reference to the `DMSCLogger` instance.
258 pub fn logger_mut(&mut self) -> &mut DMSCLogger {
259 Arc::get_mut(&mut self.inner.logger).expect("Cannot get mutable reference to logger - shared ownership")
260 }
261
262 /// Get a reference to the metrics registry if available.
263 ///
264 /// # Returns
265 ///
266 /// An optional reference to the `DMSCMetricsRegistry` instance.
267 pub fn metrics_registry(&self) -> Option<Arc<DMSCMetricsRegistry>> {
268 self.inner.metrics_registry.clone()
269 }
270}
271
272#[cfg(feature = "pyo3")]
273/// Python bindings for DMSCServiceContext
274#[pyo3::prelude::pymethods]
275impl DMSCServiceContext {
276 /// Create a new DMSCServiceContext with default configuration
277 #[new]
278 fn py_new() -> pyo3::PyResult<Self> {
279 match Self::new_default() {
280 Ok(ctx) => Ok(ctx),
281 Err(err) => Err(pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create service context: {err}"))),
282 }
283 }
284
285 #[pyo3(name = "fs")]
286 fn fs_py(&self) -> crate::fs::DMSCFileSystem {
287 self.inner.fs.clone()
288 }
289
290 #[pyo3(name = "logger")]
291 fn logger_py(&self) -> crate::log::DMSCLogger {
292 (*self.inner.logger).clone()
293 }
294
295 #[pyo3(name = "config")]
296 fn config_py(&self) -> crate::config::DMSCConfigManager {
297 (*self.inner.config).clone()
298 }
299
300 #[pyo3(name = "metrics_registry")]
301 fn metrics_registry_py(&self) -> Option<crate::observability::DMSCMetricsRegistry> {
302 self.inner.metrics_registry.as_ref().map(|r| (**r).clone())
303 }
304
305 #[pyo3(name = "hooks")]
306 fn hooks_py(&self) -> crate::hooks::DMSCHookBus {
307 crate::hooks::DMSCHookBus::new()
308 }
309}