liminal_server/agents/
config.rs1use serde::Deserialize;
4
5use super::error::AgentError;
6use crate::config::types::ServerConfig;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
10#[serde(default, deny_unknown_fields)]
11pub struct AgentsConfig {
12 pub max_per_owner: usize,
15 pub max_per_exchange: usize,
17 pub max_field_bytes: usize,
20 pub max_event_bytes: usize,
22 pub replay_page_rows: usize,
24 pub max_watchers: usize,
26 pub watcher_depth: usize,
28 pub watcher_bytes: usize,
30 pub max_listened_subjects: usize,
33 pub max_subject_bytes: usize,
35}
36
37impl Default for AgentsConfig {
38 fn default() -> Self {
39 Self {
40 max_per_owner: 64,
41 max_per_exchange: 64,
42 max_field_bytes: 512,
43 max_event_bytes: 16 * 1024,
44 replay_page_rows: 64,
45 max_watchers: 256,
46 watcher_depth: 16,
47 watcher_bytes: 256 * 1024,
48 max_listened_subjects: 1024,
49 max_subject_bytes: 4096,
50 }
51 }
52}
53
54impl AgentsConfig {
55 pub fn validate(&self) -> Result<(), AgentError> {
60 for (name, value) in self.bounds() {
61 if value == 0 {
62 return Err(AgentError::Bound {
63 bound: name,
64 limit: 0,
65 count: 0,
66 });
67 }
68 }
69 if self.max_event_bytes < self.max_field_bytes {
70 return Err(AgentError::Invalid(
71 "max_event_bytes is below max_field_bytes",
72 ));
73 }
74 Ok(())
75 }
76
77 const fn bounds(&self) -> [(&'static str, usize); 10] {
78 [
79 ("max_per_owner", self.max_per_owner),
80 ("max_per_exchange", self.max_per_exchange),
81 ("max_field_bytes", self.max_field_bytes),
82 ("max_event_bytes", self.max_event_bytes),
83 ("replay_page_rows", self.replay_page_rows),
84 ("max_watchers", self.max_watchers),
85 ("watcher_depth", self.watcher_depth),
86 ("watcher_bytes", self.watcher_bytes),
87 ("max_listened_subjects", self.max_listened_subjects),
88 ("max_subject_bytes", self.max_subject_bytes),
89 ]
90 }
91
92 pub(crate) fn field(&self, value: &str, member: &'static str) -> Result<(), AgentError> {
93 if value.is_empty() || value.chars().any(char::is_control) {
94 return Err(AgentError::Invalid(member));
95 }
96 if value.len() > self.max_field_bytes {
97 return Err(AgentError::Bound {
98 bound: "max_field_bytes",
99 limit: self.max_field_bytes,
100 count: value.len(),
101 });
102 }
103 Ok(())
104 }
105}
106
107pub fn collect_errors(config: &ServerConfig, errors: &mut Vec<String>) {
110 let lys = config.auth.as_ref().and_then(|auth| auth.lys.as_ref());
111 if let Some(bounds) = lys.and_then(|lys| lys.agents.as_ref()) {
112 if let Err(error) = bounds.validate() {
113 errors.push(error.to_string());
114 }
115 } else if config
116 .channels
117 .iter()
118 .any(|channel| channel.name.starts_with("agents."))
119 || lys
120 .and_then(|lys| lys.membership.as_ref())
121 .is_some_and(|membership| membership.channel_kind == "liminal.agent")
122 {
123 errors.push("[auth.lys.agents] is required when a liminal.agent is placed".into());
124 }
125}