Skip to main content

systemprompt_runtime/
registry.rs

1//! Module API and well-known route registries built from `inventory`.
2//!
3//! Modules register HTTP routers with
4//! [`register_module_api!`](crate::register_module_api) and well-known
5//! endpoints with
6//! [`register_wellknown_route!`](crate::register_wellknown_route). Both submit
7//! to `inventory` collectors that this module materialises into runtime maps.
8
9use axum::Router;
10use std::collections::HashMap;
11
12pub use systemprompt_models::modules::{Module, ModuleType, Modules, ServiceCategory};
13
14use crate::AppContext;
15
16#[derive(Debug)]
17pub struct ModuleApiRegistry {
18    registry: HashMap<String, ModuleApiImpl>,
19}
20
21#[derive(Debug)]
22struct ModuleApiImpl {
23    category: ServiceCategory,
24    module_type: ModuleType,
25    router_fn: fn(&AppContext) -> Router,
26    auth_required: bool,
27}
28
29#[derive(Debug, Copy, Clone)]
30pub struct ModuleApiRegistration {
31    pub module_name: &'static str,
32    pub category: ServiceCategory,
33    pub module_type: ModuleType,
34    pub router_fn: fn(&AppContext) -> Router,
35    pub auth_required: bool,
36}
37
38inventory::collect!(ModuleApiRegistration);
39
40#[derive(Debug, Clone, Copy)]
41pub struct WellKnownRoute {
42    pub path: &'static str,
43    pub handler_fn: fn(&AppContext) -> Router,
44    pub methods: &'static [axum::http::Method],
45}
46
47inventory::collect!(WellKnownRoute);
48
49impl Default for ModuleApiRegistry {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl ModuleApiRegistry {
56    pub fn new() -> Self {
57        let mut registry = HashMap::new();
58
59        for registration in inventory::iter::<ModuleApiRegistration> {
60            let api_impl = ModuleApiImpl {
61                category: registration.category,
62                module_type: registration.module_type,
63                router_fn: registration.router_fn,
64                auth_required: registration.auth_required,
65            };
66            registry.insert(registration.module_name.to_string(), api_impl);
67        }
68
69        Self { registry }
70    }
71
72    pub fn get_routes(&self, module_name: &str, ctx: &AppContext) -> Option<Router> {
73        self.registry
74            .get(module_name)
75            .map(|impl_| (impl_.router_fn)(ctx))
76    }
77
78    pub fn get_category(&self, module_name: &str) -> Option<ServiceCategory> {
79        self.registry.get(module_name).map(|impl_| impl_.category)
80    }
81
82    pub fn get_module_type(&self, module_name: &str) -> Option<ModuleType> {
83        self.registry
84            .get(module_name)
85            .map(|impl_| impl_.module_type)
86    }
87
88    pub fn get_auth_required(&self, module_name: &str) -> Option<bool> {
89        self.registry
90            .get(module_name)
91            .map(|impl_| impl_.auth_required)
92    }
93
94    pub fn modules_by_category(&self, category: ServiceCategory) -> Vec<String> {
95        self.registry
96            .iter()
97            .filter(|(_, impl_)| matches!(impl_.category, c if c as u8 == category as u8))
98            .map(|(name, _)| name.clone())
99            .collect()
100    }
101}
102
103pub trait ModuleRuntime {
104    fn routes(&self, ctx: &AppContext, registry: &ModuleApiRegistry) -> Option<Router>;
105    fn create_api_registry(&self) -> ModuleApiRegistry;
106}
107
108impl ModuleRuntime for Module {
109    fn routes(&self, ctx: &AppContext, registry: &ModuleApiRegistry) -> Option<Router> {
110        if let Some(api) = &self.api {
111            if api.enabled {
112                return registry.get_routes(&self.name, ctx);
113            }
114        }
115        None
116    }
117
118    fn create_api_registry(&self) -> ModuleApiRegistry {
119        ModuleApiRegistry::new()
120    }
121}
122
123impl ModuleRuntime for Modules {
124    fn routes(&self, _ctx: &AppContext, _registry: &ModuleApiRegistry) -> Option<Router> {
125        None
126    }
127
128    fn create_api_registry(&self) -> ModuleApiRegistry {
129        ModuleApiRegistry::new()
130    }
131}