Skip to main content

systemprompt_models/modules/
mod.rs

1//! `modules` module — see crate-level docs for context.
2
3mod api_paths;
4mod cli_paths;
5mod service_category;
6mod types;
7
8pub use api_paths::ApiPaths;
9pub use cli_paths::CliPaths;
10pub use service_category::ServiceCategory;
11pub use types::{
12    ApiConfig, Module, ModuleDefinition, ModulePermission, ModuleSchema, ModuleSeed, ModuleType,
13    SchemaSource, SeedSource,
14};
15
16use crate::errors::ModuleError;
17
18#[derive(Clone, Debug)]
19pub struct Modules {
20    modules: Vec<Module>,
21}
22
23impl Modules {
24    pub fn from_vec(modules: Vec<Module>) -> Result<Self, ModuleError> {
25        let modules = Self::resolve_dependencies(modules)?;
26        Ok(Self { modules })
27    }
28
29    pub const fn all(&self) -> &Vec<Module> {
30        &self.modules
31    }
32
33    pub fn get(&self, name: &str) -> Option<&Module> {
34        self.modules.iter().find(|m| m.name == name)
35    }
36
37    pub fn resolve_dependencies(mut modules: Vec<Module>) -> Result<Vec<Module>, ModuleError> {
38        use std::collections::HashSet;
39
40        let mut ordered = Vec::new();
41        let mut processed = HashSet::new();
42        let all_module_names: HashSet<String> = modules.iter().map(|m| m.name.clone()).collect();
43
44        while !modules.is_empty() {
45            let to_process: Vec<_> = modules
46                .iter()
47                .filter(|m| {
48                    m.dependencies
49                        .iter()
50                        .all(|dep| processed.contains(dep.as_str()))
51                })
52                .cloned()
53                .collect();
54
55            if to_process.is_empty() && !modules.is_empty() {
56                let missing_deps: Vec<_> = modules
57                    .iter()
58                    .flat_map(|m| {
59                        m.dependencies
60                            .iter()
61                            .filter(|dep| {
62                                !all_module_names.contains(*dep)
63                                    && !processed.contains(dep.as_str())
64                            })
65                            .map(move |dep| (m.name.clone(), dep.clone()))
66                    })
67                    .collect();
68
69                if !missing_deps.is_empty() {
70                    let missing_list: Vec<_> = missing_deps
71                        .iter()
72                        .map(|(m, d)| format!("{m} -> {d}"))
73                        .collect();
74                    return Err(ModuleError::MissingDependencies(missing_list.join(", ")));
75                }
76
77                let remaining: Vec<_> = modules.iter().map(|m| m.name.clone()).collect();
78                return Err(ModuleError::Cycle(format!("{remaining:?}")));
79            }
80
81            for module in &to_process {
82                ordered.push(module.clone());
83                processed.insert(module.name.clone());
84            }
85
86            modules.retain(|module| !processed.contains(module.name.as_str()));
87        }
88
89        Ok(ordered)
90    }
91
92    pub fn list_names(&self) -> Vec<String> {
93        self.modules.iter().map(|m| m.name.clone()).collect()
94    }
95
96    pub fn get_provided_audiences() -> Vec<String> {
97        vec!["a2a".to_string(), "api".to_string(), "mcp".to_string()]
98    }
99
100    pub fn get_valid_audiences(&self, module_name: &str) -> Vec<String> {
101        self.get(module_name)
102            .map_or_else(Self::get_provided_audiences, |module| {
103                module.audience.clone()
104            })
105    }
106
107    pub fn get_server_audiences(_server_name: &str, _port: u16) -> Vec<String> {
108        Self::get_provided_audiences()
109    }
110}