aimdb_core/remote/
config.rs1use std::{collections::HashSet, path::PathBuf, string::String, vec::Vec};
4
5use crate::record_id::StringKey;
6
7#[derive(Debug, Clone)]
12pub struct AimxConfig {
13 pub socket_path: PathBuf,
15
16 pub security_policy: SecurityPolicy,
18
19 pub max_connections: usize,
21
22 pub subscription_queue_size: usize,
24
25 pub auth_token: Option<String>,
27
28 pub socket_permissions: Option<u32>,
31}
32
33impl AimxConfig {
34 pub fn uds_default() -> Self {
44 Self {
45 socket_path: PathBuf::from("/tmp/aimdb.sock"),
46 security_policy: SecurityPolicy::ReadOnly,
47 max_connections: 16,
48 subscription_queue_size: 100,
49 auth_token: None,
50 socket_permissions: Some(0o600),
51 }
52 }
53
54 pub fn socket_path(mut self, path: impl Into<PathBuf>) -> Self {
56 self.socket_path = path.into();
57 self
58 }
59
60 pub fn security_policy(mut self, policy: SecurityPolicy) -> Self {
62 self.security_policy = policy;
63 self
64 }
65
66 pub fn max_connections(mut self, max: usize) -> Self {
68 self.max_connections = max;
69 self
70 }
71
72 pub fn subscription_queue_size(mut self, size: usize) -> Self {
74 self.subscription_queue_size = size;
75 self
76 }
77
78 pub fn auth_token(mut self, token: impl Into<String>) -> Self {
80 self.auth_token = Some(token.into());
81 self
82 }
83
84 pub fn socket_permissions(mut self, mode: u32) -> Self {
92 self.socket_permissions = Some(mode);
93 self
94 }
95}
96
97#[derive(Debug, Clone)]
101pub enum SecurityPolicy {
102 ReadOnly,
107
108 ReadWrite {
113 writable_records: HashSet<String>,
115 },
116}
117
118impl SecurityPolicy {
119 pub fn read_only() -> Self {
121 Self::ReadOnly
122 }
123
124 pub fn read_write() -> Self {
126 Self::ReadWrite {
127 writable_records: HashSet::new(),
128 }
129 }
130
131 pub fn allow_write_key(&mut self, key: impl Into<String>) {
135 match self {
136 Self::ReadWrite { writable_records } => {
137 writable_records.insert(key.into());
138 }
139 Self::ReadOnly => {
140 panic!("Cannot allow writes in ReadOnly security policy");
141 }
142 }
143 }
144
145 pub fn with_writable_key(mut self, key: impl Into<String>) -> Self {
150 match self {
151 Self::ReadWrite {
152 ref mut writable_records,
153 } => {
154 writable_records.insert(key.into());
155 self
156 }
157 Self::ReadOnly => {
158 panic!("Cannot allow writes in ReadOnly security policy");
159 }
160 }
161 }
162
163 pub fn is_writable_key(&self, key: &str) -> bool {
165 match self {
166 Self::ReadOnly => false,
167 Self::ReadWrite { writable_records } => writable_records.contains(key),
168 }
169 }
170
171 pub fn permissions(&self) -> &[&str] {
173 match self {
174 Self::ReadOnly => &["read", "subscribe"],
175 Self::ReadWrite { .. } => &["read", "subscribe", "write"],
176 }
177 }
178
179 pub fn writable_records(&self) -> Vec<String> {
181 match self {
182 Self::ReadOnly => Vec::new(),
183 Self::ReadWrite { writable_records } => writable_records.iter().cloned().collect(),
184 }
185 }
186
187 pub fn writable_record_keys(&self) -> Vec<StringKey> {
189 match self {
190 Self::ReadOnly => Vec::new(),
191 Self::ReadWrite { writable_records } => writable_records
192 .iter()
193 .map(|s| StringKey::from_dynamic(s.as_str()))
194 .collect(),
195 }
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 #[cfg(feature = "std")]
205 fn test_default_config() {
206 let config = AimxConfig::uds_default();
207 assert_eq!(config.socket_path, PathBuf::from("/tmp/aimdb.sock"));
208 assert_eq!(config.max_connections, 16);
209 assert_eq!(config.subscription_queue_size, 100);
210 assert!(matches!(config.security_policy, SecurityPolicy::ReadOnly));
211 assert!(config.auth_token.is_none());
212 }
213
214 #[test]
215 #[cfg(feature = "std")]
216 fn test_config_builder() {
217 let config = AimxConfig::uds_default()
218 .socket_path("/var/run/aimdb.sock")
219 .max_connections(32)
220 .subscription_queue_size(200)
221 .auth_token("secret-token")
222 .socket_permissions(0o660);
223
224 assert_eq!(config.socket_path, PathBuf::from("/var/run/aimdb.sock"));
225 assert_eq!(config.max_connections, 32);
226 assert_eq!(config.subscription_queue_size, 200);
227 assert_eq!(config.auth_token, Some("secret-token".to_string()));
228 assert_eq!(config.socket_permissions, Some(0o660));
229 }
230
231 #[test]
232 fn test_security_policy_read_only() {
233 let policy = SecurityPolicy::read_only();
234 assert!(!policy.is_writable_key("test.record"));
235 assert_eq!(policy.permissions(), &["read", "subscribe"]);
236 }
237
238 #[test]
239 fn test_security_policy_read_write() {
240 let mut policy = SecurityPolicy::read_write();
241 assert!(!policy.is_writable_key("test.record"));
242
243 policy.allow_write_key("test.record");
244 assert!(policy.is_writable_key("test.record"));
245 assert!(!policy.is_writable_key("other.record"));
246 assert_eq!(policy.permissions(), &["read", "subscribe", "write"]);
247 }
248
249 #[test]
250 #[should_panic(expected = "Cannot allow writes in ReadOnly security policy")]
251 fn test_security_policy_read_only_panic() {
252 let mut policy = SecurityPolicy::read_only();
253 policy.allow_write_key("test.record");
254 }
255
256 #[test]
257 #[should_panic(expected = "Cannot allow writes in ReadOnly security policy")]
258 fn test_security_policy_read_only_builder_panic() {
259 let _policy = SecurityPolicy::read_only().with_writable_key("test.record");
260 }
261
262 #[test]
263 fn test_security_policy_builder() {
264 let policy = SecurityPolicy::read_write()
265 .with_writable_key("sensor.temperature")
266 .with_writable_key("config.app");
267
268 assert!(policy.is_writable_key("sensor.temperature"));
269 assert!(policy.is_writable_key("config.app"));
270 assert!(!policy.is_writable_key("other.record"));
271 }
272}