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 serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13pub use systemprompt_models::modules::ServiceCategory;
14
15use crate::AppContext;
16
17#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub enum ModuleType {
19    Regular,
20    Proxy,
21}
22
23#[derive(Debug)]
24pub struct ModuleApiRegistry {
25    registry: HashMap<String, ModuleApiImpl>,
26}
27
28#[derive(Debug)]
29struct ModuleApiImpl {
30    category: ServiceCategory,
31    module_type: ModuleType,
32    router_fn: fn(&AppContext) -> Router,
33    auth_required: bool,
34}
35
36#[derive(Debug, Copy, Clone)]
37pub struct ModuleApiRegistration {
38    pub module_name: &'static str,
39    pub category: ServiceCategory,
40    pub module_type: ModuleType,
41    pub router_fn: fn(&AppContext) -> Router,
42    pub auth_required: bool,
43}
44
45inventory::collect!(ModuleApiRegistration);
46
47#[derive(Debug, Clone, Copy)]
48pub struct WellKnownRoute {
49    pub path: &'static str,
50    pub handler_fn: fn(&AppContext) -> Router,
51    pub methods: &'static [axum::http::Method],
52}
53
54inventory::collect!(WellKnownRoute);
55
56impl Default for ModuleApiRegistry {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62impl ModuleApiRegistry {
63    pub fn new() -> Self {
64        let mut registry = HashMap::new();
65
66        for registration in inventory::iter::<ModuleApiRegistration> {
67            let api_impl = ModuleApiImpl {
68                category: registration.category,
69                module_type: registration.module_type,
70                router_fn: registration.router_fn,
71                auth_required: registration.auth_required,
72            };
73            registry.insert(registration.module_name.to_string(), api_impl);
74        }
75
76        Self { registry }
77    }
78
79    pub fn get_routes(&self, module_name: &str, ctx: &AppContext) -> Option<Router> {
80        self.registry
81            .get(module_name)
82            .map(|impl_| (impl_.router_fn)(ctx))
83    }
84
85    pub fn get_category(&self, module_name: &str) -> Option<ServiceCategory> {
86        self.registry.get(module_name).map(|impl_| impl_.category)
87    }
88
89    pub fn get_module_type(&self, module_name: &str) -> Option<ModuleType> {
90        self.registry
91            .get(module_name)
92            .map(|impl_| impl_.module_type)
93    }
94
95    pub fn get_auth_required(&self, module_name: &str) -> Option<bool> {
96        self.registry
97            .get(module_name)
98            .map(|impl_| impl_.auth_required)
99    }
100
101    pub fn modules_by_category(&self, category: ServiceCategory) -> Vec<String> {
102        self.registry
103            .iter()
104            .filter(|(_, impl_)| matches!(impl_.category, c if c as u8 == category as u8))
105            .map(|(name, _)| name.clone())
106            .collect()
107    }
108}