systemprompt_models/validators/
rate_limits.rs1use super::ValidationConfigProvider;
7use crate::config::RateLimitConfig;
8use systemprompt_traits::validation_report::{
9 ValidationError, ValidationReport, ValidationWarning,
10};
11use systemprompt_traits::{ConfigProvider, DomainConfig, DomainConfigError};
12
13#[derive(Debug, Default, Clone, Copy)]
14pub struct RateLimitsConfigValidator {
15 config: Option<RateLimitConfig>,
16}
17
18impl RateLimitsConfigValidator {
19 pub fn new() -> Self {
20 Self::default()
21 }
22}
23
24impl DomainConfig for RateLimitsConfigValidator {
25 fn domain_id(&self) -> &'static str {
26 "rate_limits"
27 }
28
29 fn priority(&self) -> u32 {
30 10
31 }
32
33 fn load(&mut self, config: &dyn ConfigProvider) -> Result<(), DomainConfigError> {
34 let provider = config
35 .as_any()
36 .downcast_ref::<ValidationConfigProvider>()
37 .ok_or_else(|| DomainConfigError::LoadError {
38 message: "Expected ValidationConfigProvider".into(),
39 })?;
40
41 self.config = Some(provider.config().rate_limits);
42 Ok(())
43 }
44
45 fn validate(&self) -> Result<ValidationReport, DomainConfigError> {
46 let mut report = ValidationReport::new("rate_limits");
47 let config = self
48 .config
49 .as_ref()
50 .ok_or_else(|| DomainConfigError::ValidationError {
51 message: "Not loaded".into(),
52 })?;
53
54 if config.disabled {
55 return Ok(report);
56 }
57
58 Self::validate_quota_limits(&mut report, config);
59 Self::validate_stream_limits(&mut report, config);
60 Self::validate_agent_limits(&mut report, config);
61
62 Ok(report)
63 }
64}
65
66impl RateLimitsConfigValidator {
67 fn validate_quota_limits(report: &mut ValidationReport, config: &RateLimitConfig) {
68 if config.burst_multiplier == 0 {
69 report.add_error(ValidationError::new(
70 "rate_limits.burst_multiplier",
71 "burst_multiplier must be at least 1; a zero burst leaves every route unlimited",
72 ));
73 }
74
75 let limits: [(&str, u64); 11] = [
76 ("oauth_public_per_second", config.oauth_public_per_second),
77 ("oauth_auth_per_second", config.oauth_auth_per_second),
78 ("contexts_per_second", config.contexts_per_second),
79 ("tasks_per_second", config.tasks_per_second),
80 ("artifacts_per_second", config.artifacts_per_second),
81 (
82 "agent_registry_per_second",
83 config.agent_registry_per_second,
84 ),
85 ("agents_per_second", config.agents_per_second),
86 ("mcp_registry_per_second", config.mcp_registry_per_second),
87 ("mcp_per_second", config.mcp_per_second),
88 ("stream_per_second", config.stream_per_second),
89 ("content_per_second", config.content_per_second),
90 ];
91
92 for (field, per_second) in limits {
93 if per_second == 0 {
94 report.add_error(ValidationError::new(
95 format!("rate_limits.{field}"),
96 "must be at least 1; a zero rate leaves the route unlimited",
97 ));
98 continue;
99 }
100
101 if per_second.saturating_mul(config.burst_multiplier) > u64::from(u32::MAX) {
102 report.add_error(ValidationError::new(
103 format!("rate_limits.{field}"),
104 format!(
105 "{per_second} x burst_multiplier {} exceeds the maximum representable \
106 burst of {}",
107 config.burst_multiplier,
108 u32::MAX
109 ),
110 ));
111 }
112 }
113 }
114
115 fn validate_stream_limits(report: &mut ValidationReport, config: &RateLimitConfig) {
116 if config.stream_per_second < 10 {
117 report.add_warning(
118 ValidationWarning::new(
119 "rate_limits.stream_per_second",
120 format!(
121 "stream_per_second={} is restrictive. Users may experience connection \
122 issues.",
123 config.stream_per_second
124 ),
125 )
126 .with_suggestion("Consider increasing to at least 10 for production use"),
127 );
128 }
129 }
130 fn validate_agent_limits(report: &mut ValidationReport, config: &RateLimitConfig) {
131 if config.agents_per_second < 5 {
132 report.add_warning(
133 ValidationWarning::new(
134 "rate_limits.agents_per_second",
135 format!(
136 "agents_per_second={} may cause agent timeouts under load.",
137 config.agents_per_second
138 ),
139 )
140 .with_suggestion("Consider at least 10 for stable agent operations"),
141 );
142 }
143
144 if config.contexts_per_second < 20 {
145 report.add_warning(
146 ValidationWarning::new(
147 "rate_limits.contexts_per_second",
148 format!(
149 "contexts_per_second={} may slow down conversation operations.",
150 config.contexts_per_second
151 ),
152 )
153 .with_suggestion("Consider at least 50 for responsive context management"),
154 );
155 }
156 }
157}