Skip to main content

systemprompt_traits/
domain_config.rs

1//! Per-domain configuration loading and validation.
2//!
3//! Each domain crate registers a [`DomainConfig`] implementation in the
4//! [`DomainConfigRegistry`]; the runtime walks the registry at startup,
5//! ordered by [`DomainConfig::priority`], to load and validate domain
6//! configuration.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use std::fmt::Debug;
12
13use crate::context::ConfigProvider;
14use crate::validation_report::ValidationReport;
15
16#[derive(Debug, thiserror::Error)]
17#[non_exhaustive]
18pub enum DomainConfigError {
19    #[error("Failed to load config: {message}")]
20    LoadError { message: String },
21
22    #[error("Failed to load config: {context}: {source}")]
23    Load {
24        context: &'static str,
25        #[source]
26        source: crate::BoxedSource,
27    },
28
29    #[error("Failed to load config: {context}: {source}")]
30    Io {
31        context: String,
32        #[source]
33        source: std::io::Error,
34    },
35
36    #[error("Config file not found: {0}")]
37    NotFound(String),
38
39    #[error("Failed to parse config: {message}")]
40    ParseError { message: String },
41
42    #[error("Validation failed: {message}")]
43    ValidationError { message: String },
44}
45
46impl DomainConfigError {
47    pub fn load(context: &'static str, source: impl Into<crate::BoxedSource>) -> Self {
48        Self::Load {
49            context,
50            source: source.into(),
51        }
52    }
53}
54
55pub trait DomainConfig: Send + Sync + Debug {
56    fn domain_id(&self) -> &'static str;
57
58    fn load(&mut self, config: &dyn ConfigProvider) -> Result<(), DomainConfigError>;
59
60    fn validate(&self) -> Result<ValidationReport, DomainConfigError>;
61
62    fn dependencies(&self) -> &[&'static str] {
63        &[]
64    }
65
66    fn priority(&self) -> u32 {
67        100
68    }
69}
70
71pub struct DomainConfigRegistry {
72    validators: Vec<Box<dyn DomainConfig>>,
73}
74
75impl DomainConfigRegistry {
76    #[must_use]
77    pub fn new() -> Self {
78        Self {
79            validators: Vec::new(),
80        }
81    }
82
83    pub fn register(&mut self, validator: Box<dyn DomainConfig>) {
84        self.validators.push(validator);
85    }
86
87    pub fn validators_sorted(&self) -> Vec<&dyn DomainConfig> {
88        let mut validators: Vec<_> = self.validators.iter().map(AsRef::as_ref).collect();
89        validators.sort_by_key(|v| v.priority());
90        validators
91    }
92
93    pub fn validators_mut(&mut self) -> impl Iterator<Item = &mut Box<dyn DomainConfig>> {
94        self.validators.sort_by_key(|v| v.priority());
95        self.validators.iter_mut()
96    }
97}
98
99impl Default for DomainConfigRegistry {
100    fn default() -> Self {
101        Self::new()
102    }
103}
104
105impl Debug for DomainConfigRegistry {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        f.debug_struct("DomainConfigRegistry")
108            .field("validator_count", &self.validators.len())
109            .finish()
110    }
111}