use std::{collections::HashSet, path::PathBuf, string::String, vec::Vec};
use crate::record_id::StringKey;
#[derive(Debug, Clone)]
pub struct AimxConfig {
pub socket_path: PathBuf,
pub security_policy: SecurityPolicy,
pub max_connections: usize,
pub subscription_queue_size: usize,
pub auth_token: Option<String>,
pub socket_permissions: Option<u32>,
}
impl AimxConfig {
pub fn uds_default() -> Self {
Self {
socket_path: PathBuf::from("/tmp/aimdb.sock"),
security_policy: SecurityPolicy::ReadOnly,
max_connections: 16,
subscription_queue_size: 100,
auth_token: None,
socket_permissions: Some(0o600),
}
}
pub fn socket_path(mut self, path: impl Into<PathBuf>) -> Self {
self.socket_path = path.into();
self
}
pub fn security_policy(mut self, policy: SecurityPolicy) -> Self {
self.security_policy = policy;
self
}
pub fn max_connections(mut self, max: usize) -> Self {
self.max_connections = max;
self
}
pub fn subscription_queue_size(mut self, size: usize) -> Self {
self.subscription_queue_size = size;
self
}
pub fn auth_token(mut self, token: impl Into<String>) -> Self {
self.auth_token = Some(token.into());
self
}
pub fn socket_permissions(mut self, mode: u32) -> Self {
self.socket_permissions = Some(mode);
self
}
}
#[derive(Debug, Clone)]
pub enum SecurityPolicy {
ReadOnly,
ReadWrite {
writable_records: HashSet<String>,
},
}
impl SecurityPolicy {
pub fn read_only() -> Self {
Self::ReadOnly
}
pub fn read_write() -> Self {
Self::ReadWrite {
writable_records: HashSet::new(),
}
}
pub fn allow_write_key(&mut self, key: impl Into<String>) {
match self {
Self::ReadWrite { writable_records } => {
writable_records.insert(key.into());
}
Self::ReadOnly => {
panic!("Cannot allow writes in ReadOnly security policy");
}
}
}
pub fn with_writable_key(mut self, key: impl Into<String>) -> Self {
match self {
Self::ReadWrite {
ref mut writable_records,
} => {
writable_records.insert(key.into());
self
}
Self::ReadOnly => {
panic!("Cannot allow writes in ReadOnly security policy");
}
}
}
pub fn is_writable_key(&self, key: &str) -> bool {
match self {
Self::ReadOnly => false,
Self::ReadWrite { writable_records } => writable_records.contains(key),
}
}
pub fn permissions(&self) -> &[&str] {
match self {
Self::ReadOnly => &["read", "subscribe"],
Self::ReadWrite { .. } => &["read", "subscribe", "write"],
}
}
pub fn writable_records(&self) -> Vec<String> {
match self {
Self::ReadOnly => Vec::new(),
Self::ReadWrite { writable_records } => writable_records.iter().cloned().collect(),
}
}
pub fn writable_record_keys(&self) -> Vec<StringKey> {
match self {
Self::ReadOnly => Vec::new(),
Self::ReadWrite { writable_records } => writable_records
.iter()
.map(|s| StringKey::from_dynamic(s.as_str()))
.collect(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(feature = "std")]
fn test_default_config() {
let config = AimxConfig::uds_default();
assert_eq!(config.socket_path, PathBuf::from("/tmp/aimdb.sock"));
assert_eq!(config.max_connections, 16);
assert_eq!(config.subscription_queue_size, 100);
assert!(matches!(config.security_policy, SecurityPolicy::ReadOnly));
assert!(config.auth_token.is_none());
}
#[test]
#[cfg(feature = "std")]
fn test_config_builder() {
let config = AimxConfig::uds_default()
.socket_path("/var/run/aimdb.sock")
.max_connections(32)
.subscription_queue_size(200)
.auth_token("secret-token")
.socket_permissions(0o660);
assert_eq!(config.socket_path, PathBuf::from("/var/run/aimdb.sock"));
assert_eq!(config.max_connections, 32);
assert_eq!(config.subscription_queue_size, 200);
assert_eq!(config.auth_token, Some("secret-token".to_string()));
assert_eq!(config.socket_permissions, Some(0o660));
}
#[test]
fn test_security_policy_read_only() {
let policy = SecurityPolicy::read_only();
assert!(!policy.is_writable_key("test.record"));
assert_eq!(policy.permissions(), &["read", "subscribe"]);
}
#[test]
fn test_security_policy_read_write() {
let mut policy = SecurityPolicy::read_write();
assert!(!policy.is_writable_key("test.record"));
policy.allow_write_key("test.record");
assert!(policy.is_writable_key("test.record"));
assert!(!policy.is_writable_key("other.record"));
assert_eq!(policy.permissions(), &["read", "subscribe", "write"]);
}
#[test]
#[should_panic(expected = "Cannot allow writes in ReadOnly security policy")]
fn test_security_policy_read_only_panic() {
let mut policy = SecurityPolicy::read_only();
policy.allow_write_key("test.record");
}
#[test]
#[should_panic(expected = "Cannot allow writes in ReadOnly security policy")]
fn test_security_policy_read_only_builder_panic() {
let _policy = SecurityPolicy::read_only().with_writable_key("test.record");
}
#[test]
fn test_security_policy_builder() {
let policy = SecurityPolicy::read_write()
.with_writable_key("sensor.temperature")
.with_writable_key("config.app");
assert!(policy.is_writable_key("sensor.temperature"));
assert!(policy.is_writable_key("config.app"));
assert!(!policy.is_writable_key("other.record"));
}
}