Skip to main content

systemprompt_models/modules/
mod.rs

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