Skip to main content

ito_core/
backend_module_repository.rs

1//! Backend-backed module repository adapter.
2//!
3//! Delegates module reads to a [`BackendModuleReader`] when backend mode is
4//! enabled. The filesystem repository remains the fallback when backend mode
5//! is disabled.
6
7use ito_common::id::parse_module_id as parse_common_module_id;
8use ito_domain::backend::BackendModuleReader;
9use ito_domain::errors::{DomainError, DomainResult};
10use ito_domain::modules::{
11    Module, ModuleRepository as DomainModuleRepository, ModuleSummary, SubModule, SubModuleSummary,
12};
13
14/// Backend-backed module repository.
15///
16/// Wraps a [`BackendModuleReader`] implementation and delegates module reads
17/// to the backend API.
18pub struct BackendModuleRepository<R: BackendModuleReader> {
19    reader: R,
20}
21
22impl<R: BackendModuleReader> BackendModuleRepository<R> {
23    /// Create a backend-backed module repository.
24    pub fn new(reader: R) -> Self {
25        Self { reader }
26    }
27}
28
29impl<R: BackendModuleReader> DomainModuleRepository for BackendModuleRepository<R> {
30    fn exists(&self, id: &str) -> bool {
31        let module_id = resolve_backend_module_key(id);
32        match self.reader.get_module(&module_id) {
33            Ok(_) => true,
34            Err(DomainError::NotFound { .. }) => false,
35            Err(err) => {
36                tracing::warn!("backend module exists check failed: {err}");
37                true
38            }
39        }
40    }
41
42    fn get(&self, id_or_name: &str) -> DomainResult<Module> {
43        let module_id = resolve_backend_module_key(id_or_name);
44        match self.reader.get_module(&module_id) {
45            Ok(module) => Ok(module),
46            Err(DomainError::NotFound { .. }) => Err(DomainError::not_found("module", id_or_name)),
47            Err(err) => Err(err),
48        }
49    }
50
51    fn list(&self) -> DomainResult<Vec<ModuleSummary>> {
52        let mut modules = self.reader.list_modules()?;
53        modules.sort_by(|a, b| a.id.cmp(&b.id));
54        Ok(modules)
55    }
56
57    fn list_sub_modules(&self, parent_id: &str) -> DomainResult<Vec<SubModuleSummary>> {
58        let module_id = resolve_backend_module_key(parent_id);
59        let module = match self.reader.get_module(&module_id) {
60            Ok(m) => m,
61            Err(DomainError::NotFound { .. }) => {
62                return Err(DomainError::not_found("module", parent_id));
63            }
64            Err(err) => return Err(err),
65        };
66        let mut sub_modules = Vec::with_capacity(module.sub_modules.len());
67        for s in module.sub_modules {
68            sub_modules.push(SubModuleSummary {
69                id: s.id,
70                name: s.name,
71                change_count: s.change_count,
72            });
73        }
74        sub_modules.sort_by(|a, b| a.id.cmp(&b.id));
75        Ok(sub_modules)
76    }
77
78    fn get_sub_module(&self, composite_id: &str) -> DomainResult<SubModule> {
79        // Extract the parent module ID from the composite sub-module ID (e.g., "005.01" -> "005").
80        let parent_id = composite_id.split('.').next().unwrap_or(composite_id);
81        let module_id = resolve_backend_module_key(parent_id);
82        let module = match self.reader.get_module(&module_id) {
83            Ok(m) => m,
84            Err(DomainError::NotFound { .. }) => {
85                return Err(DomainError::not_found("sub-module", composite_id));
86            }
87            Err(err) => return Err(err),
88        };
89        for sub in module.sub_modules {
90            if sub.id == composite_id {
91                return Ok(sub);
92            }
93        }
94        Err(DomainError::not_found("sub-module", composite_id))
95    }
96}
97
98fn resolve_backend_module_key(id_or_name: &str) -> String {
99    if let Ok(parsed) = parse_common_module_id(id_or_name) {
100        return parsed.module_id.as_str().to_string();
101    }
102
103    id_or_name.to_string()
104}