#![allow(non_snake_case)]
use crate::core::{RiResult, RiServiceContext, ServiceModule, AsyncServiceModule};
use super::module_sorter::sort_modules;
use super::module_types::{ModuleSlot, ModuleType};
use super::lifecycle::RiLifecycleObserver;
use super::analytics::RiLogAnalyticsModule;
use std::sync::Arc;
#[cfg(feature = "pyo3")]
use pyo3::PyResult;
#[cfg(feature = "pyo3")]
use crate::core::app_runtime::RiAppRuntime;
#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
pub struct RiAppBuilder {
modules: Vec<ModuleSlot>,
config_paths: Vec<String>,
logging_config: Option<crate::log::RiLogConfig>,
observability_config: Option<crate::observability::RiObservabilityConfig>,
}
impl Default for RiAppBuilder {
fn default() -> Self {
Self::new()
}
}
impl RiAppBuilder {
pub fn new() -> Self {
RiAppBuilder {
modules: Vec::new(),
config_paths: Vec::new(),
logging_config: None,
observability_config: None,
}
}
pub fn with_module(mut self, module: Box<dyn ServiceModule>) -> Self {
self.modules.push(ModuleSlot { module: ModuleType::Sync(module), failed: false });
self
}
#[cfg(feature = "pyo3")]
pub fn with_python_module(mut self, module: crate::core::module::RiPythonModuleAdapter) -> Self {
self.modules.push(ModuleSlot {
module: ModuleType::Async(Box::new(module)),
failed: false
});
self
}
pub fn with_async_module(mut self, module: Box<dyn AsyncServiceModule>) -> Self {
self.modules.push(ModuleSlot { module: ModuleType::Async(module), failed: false });
self
}
pub fn with_dms_module(mut self, module: Box<dyn crate::core::RiModule>) -> Self {
struct RiModuleAdapter(Box<dyn crate::core::RiModule + Send + Sync + 'static>);
#[async_trait::async_trait]
impl AsyncServiceModule for RiModuleAdapter {
fn name(&self) -> &str {
self.0.name()
}
fn is_critical(&self) -> bool {
self.0.is_critical()
}
fn priority(&self) -> i32 {
self.0.priority()
}
fn dependencies(&self) -> Vec<&str> {
self.0.dependencies()
}
async fn init(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
self.0.init(ctx).await
}
async fn before_start(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
self.0.before_start(ctx).await
}
async fn start(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
self.0.start(ctx).await
}
async fn after_start(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
self.0.after_start(ctx).await
}
async fn before_shutdown(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
self.0.before_shutdown(ctx).await
}
async fn shutdown(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
self.0.shutdown(ctx).await
}
async fn after_shutdown(&mut self, ctx: &mut RiServiceContext) -> RiResult<()> {
self.0.after_shutdown(ctx).await
}
}
self.modules.push(ModuleSlot {
module: ModuleType::Async(Box::new(RiModuleAdapter(module))),
failed: false
});
self
}
pub fn with_modules(mut self, modules: Vec<Box<dyn ServiceModule>>) -> Self {
for module in modules {
self.modules.push(ModuleSlot { module: ModuleType::Sync(module), failed: false });
}
self
}
pub fn with_async_modules(mut self, modules: Vec<Box<dyn AsyncServiceModule>>) -> Self {
for module in modules {
self.modules.push(ModuleSlot { module: ModuleType::Async(module), failed: false });
}
self
}
pub fn with_dms_modules(mut self, modules: Vec<Box<dyn crate::core::RiModule>>) -> Self {
for module in modules {
self = self.with_dms_module(module);
}
self
}
pub fn with_config(mut self, config_path: impl Into<String>) -> RiResult<Self> {
self.config_paths.push(config_path.into());
Ok(self)
}
pub fn with_logging(mut self, logging_config: crate::log::RiLogConfig) -> Self {
self.logging_config = Some(logging_config);
self
}
pub fn with_observability(mut self, observability_config: crate::observability::RiObservabilityConfig) -> Self {
self.observability_config = Some(observability_config);
self
}
pub fn with_cache_module<F>(mut self, config_fn: F) -> Self
where
F: FnOnce(&mut crate::cache::RiCacheConfig) -> &mut crate::cache::RiCacheConfig,
{
let mut config = crate::cache::RiCacheConfig::default();
config_fn(&mut config);
let cache_module = crate::cache::RiCacheModule::with_config(config);
self.modules.push(ModuleSlot {
module: ModuleType::Sync(Box::new(cache_module)),
failed: false,
});
self
}
pub fn with_auth_module<F>(mut self, config_fn: F) -> Self
where
F: FnOnce(&mut crate::auth::RiAuthConfig) -> &mut crate::auth::RiAuthConfig,
{
let mut config = crate::auth::RiAuthConfig::default();
config_fn(&mut config);
let auth_module = crate::auth::RiAuthModule::with_config(config);
self.modules.push(ModuleSlot {
module: ModuleType::Sync(Box::new(auth_module)),
failed: false,
});
self
}
pub fn with_queue_module<F>(mut self, config_fn: F) -> Self
where
F: FnOnce(&mut crate::queue::RiQueueConfig) -> &mut crate::queue::RiQueueConfig,
{
let mut config = crate::queue::RiQueueConfig::default();
config_fn(&mut config);
match crate::queue::RiQueueModule::with_config(config) {
Ok(queue_module) => {
self.modules.push(ModuleSlot {
module: ModuleType::Sync(Box::new(queue_module)),
failed: false,
});
}
Err(e) => {
log::error!("Failed to create queue module: {}", e);
}
}
self
}
pub fn with_device_module<F>(mut self, config_fn: F) -> Self
where
F: FnOnce(&mut crate::device::RiDeviceControlConfig) -> &mut crate::device::RiDeviceControlConfig,
{
let mut config = crate::device::RiDeviceControlConfig::default();
config_fn(&mut config);
let device_module = crate::device::RiDeviceControlModule::new().with_config(config);
self.modules.push(ModuleSlot {
module: ModuleType::Sync(Box::new(device_module)),
failed: false,
});
self
}
pub fn build(mut self) -> RiResult<super::app_runtime::RiAppRuntime> {
let mut config_manager = crate::config::RiConfigManager::new();
for path in &self.config_paths {
config_manager.add_file_source(path);
}
if self.config_paths.is_empty() {
if let Ok(cwd) = std::env::current_dir() {
let config_dir = cwd.join("config");
config_manager.add_file_source(config_dir.join("dms.yaml"));
config_manager.add_file_source(config_dir.join("dms.yml"));
config_manager.add_file_source(config_dir.join("dms.toml"));
config_manager.add_file_source(config_dir.join("dms.json"));
}
}
config_manager.add_environment_source();
config_manager.load()?;
let ctx = self.create_service_context(config_manager)?;
self.modules.push(ModuleSlot { module: ModuleType::Sync(Box::new(RiLogAnalyticsModule::new())), failed: false });
self.modules.push(ModuleSlot { module: ModuleType::Sync(Box::new(RiLifecycleObserver::new())), failed: false });
self.modules = sort_modules(self.modules)?;
let runtime = super::app_runtime::RiAppRuntime::new(ctx, self.modules);
Ok(runtime)
}
fn create_service_context(&self, config_manager: crate::config::RiConfigManager) -> RiResult<RiServiceContext> {
let cfg = config_manager.config();
let project_root = std::env::current_dir()
.map_err(|e| crate::core::RiError::Other(format!("detect project root failed: {e}")))?;
let app_data_root = if let Some(root_str) = cfg.get_str("fs.app_data_root") {
project_root.join(root_str)
} else {
project_root.join(".dms")
};
let fs = crate::fs::RiFileSystem::new_with_roots(project_root, app_data_root);
let log_config: crate::log::RiLogConfig = if let Some(log_config) = &self.logging_config {
log_config.clone()
} else {
crate::log::RiLogConfig::from_config(&cfg)
};
let logger = crate::log::RiLogger::new(&log_config, fs.clone());
let hooks = crate::hooks::RiHookBus::new();
let metrics_registry = Some(Arc::new(crate::observability::RiMetricsRegistry::new()));
Ok(RiServiceContext::new_with(fs, logger, config_manager, hooks, metrics_registry))
}
}
#[cfg(feature = "pyo3")]
#[pyo3::prelude::pymethods]
impl RiAppBuilder {
#[new]
fn py_new() -> Self {
Self::new()
}
fn py_with_config(&mut self, config_path: &str) -> PyResult<Self> {
self.config_paths.push(config_path.to_string());
Ok(std::mem::take(self))
}
fn py_with_logging(&mut self, logging_config: crate::log::RiLogConfig) -> PyResult<Self> {
self.logging_config = Some(logging_config);
Ok(std::mem::take(self))
}
fn py_with_observability(&mut self, observability_config: crate::observability::RiObservabilityConfig) -> PyResult<Self> {
self.observability_config = Some(observability_config);
Ok(std::mem::take(self))
}
fn py_build(&mut self) -> PyResult<RiAppRuntime> {
let builder = std::mem::take(self);
RiAppBuilder::build(builder).map_err(|e| pyo3::prelude::PyErr::from(e))
}
fn py_with_module(&mut self, module: super::module::RiPythonServiceModule) -> PyResult<Self> {
self.modules.push(crate::core::module_types::ModuleSlot {
module: crate::core::module_types::ModuleType::Sync(Box::new(module)),
failed: false,
});
Ok(std::mem::take(self))
}
fn py_with_python_module(&mut self, module: super::module::RiPythonModuleAdapter) -> PyResult<Self> {
self.modules.push(crate::core::module_types::ModuleSlot {
module: crate::core::module_types::ModuleType::Async(Box::new(module)),
failed: false,
});
Ok(std::mem::take(self))
}
fn py_with_async_module(&mut self, module: super::module::RiPythonAsyncServiceModule) -> PyResult<Self> {
self.modules.push(crate::core::module_types::ModuleSlot {
module: crate::core::module_types::ModuleType::Async(Box::new(module)),
failed: false,
});
Ok(std::mem::take(self))
}
fn py_with_dms_module(&mut self, module: super::module::RiPythonModuleAdapter) -> PyResult<Self> {
self.modules.push(crate::core::module_types::ModuleSlot {
module: crate::core::module_types::ModuleType::Async(Box::new(module)),
failed: false,
});
Ok(std::mem::take(self))
}
fn py_with_modules(&mut self, modules: Vec<super::module::RiPythonServiceModule>) -> PyResult<Self> {
for module in modules {
self.modules.push(crate::core::module_types::ModuleSlot {
module: crate::core::module_types::ModuleType::Sync(Box::new(module)),
failed: false,
});
}
Ok(std::mem::take(self))
}
fn py_with_async_modules(&mut self, modules: Vec<super::module::RiPythonAsyncServiceModule>) -> PyResult<Self> {
for module in modules {
self.modules.push(crate::core::module_types::ModuleSlot {
module: crate::core::module_types::ModuleType::Async(Box::new(module)),
failed: false,
});
}
Ok(std::mem::take(self))
}
fn py_with_dms_modules(&mut self, modules: Vec<super::module::RiPythonModuleAdapter>) -> PyResult<Self> {
for module in modules {
self.modules.push(crate::core::module_types::ModuleSlot {
module: crate::core::module_types::ModuleType::Async(Box::new(module)),
failed: false,
});
}
Ok(std::mem::take(self))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_app_builder_creation() {
let builder = RiAppBuilder::new();
assert!(builder.modules.is_empty());
assert!(builder.config_paths.is_empty());
assert!(builder.logging_config.is_none());
assert!(builder.observability_config.is_none());
}
#[test]
fn test_app_builder_with_config() {
let builder = RiAppBuilder::new()
.with_config("config.yaml")
.unwrap();
assert_eq!(builder.config_paths.len(), 1);
assert_eq!(builder.config_paths[0], "config.yaml");
}
#[test]
fn test_app_builder_method_chaining() {
let builder = RiAppBuilder::new()
.with_config("config.yaml")
.unwrap()
.with_logging(crate::log::RiLogConfig::default());
assert_eq!(builder.config_paths.len(), 1);
assert!(builder.logging_config.is_some());
}
#[test]
fn test_app_builder_with_observability() {
let builder = RiAppBuilder::new()
.with_observability(crate::observability::RiObservabilityConfig::default());
assert!(builder.observability_config.is_some());
}
#[test]
fn test_app_builder_with_multiple_configs() {
let builder = RiAppBuilder::new()
.with_config("config1.yaml")
.unwrap()
.with_config("config2.yaml")
.unwrap();
assert_eq!(builder.config_paths.len(), 2);
}
}