Skip to main content

canlink_hal/queue/
config.rs

1//! Queue configuration (FR-011, FR-017)
2//!
3//! Provides configuration structures for loading queue settings from TOML.
4
5use std::time::Duration;
6
7use serde::Deserialize;
8
9use super::{BoundedQueue, QueueOverflowPolicy};
10
11/// Queue configuration from TOML
12///
13/// # Example TOML
14///
15/// ```toml
16/// [queue]
17/// capacity = 2000
18///
19/// [queue.overflow_policy]
20/// type = "drop_oldest"
21/// ```
22///
23/// Or with block policy:
24///
25/// ```toml
26/// [queue]
27/// capacity = 1000
28///
29/// [queue.overflow_policy]
30/// type = "block"
31/// timeout_ms = 100
32/// ```
33#[derive(Debug, Clone, Deserialize)]
34pub struct QueueConfig {
35    /// Queue capacity (default: 1000)
36    #[serde(default = "default_capacity")]
37    pub capacity: usize,
38
39    /// Overflow policy configuration
40    #[serde(default)]
41    pub overflow_policy: OverflowPolicyConfig,
42}
43
44fn default_capacity() -> usize {
45    super::bounded::DEFAULT_QUEUE_CAPACITY
46}
47
48impl Default for QueueConfig {
49    fn default() -> Self {
50        Self {
51            capacity: default_capacity(),
52            overflow_policy: OverflowPolicyConfig::default(),
53        }
54    }
55}
56
57/// Overflow policy configuration from TOML
58#[derive(Debug, Clone, Deserialize)]
59#[serde(tag = "type", rename_all = "snake_case")]
60#[derive(Default)]
61pub enum OverflowPolicyConfig {
62    /// Drop oldest message
63    #[default]
64    DropOldest,
65    /// Drop newest message
66    DropNewest,
67    /// Block with timeout
68    Block {
69        /// Timeout in milliseconds
70        #[serde(default = "default_timeout_ms")]
71        timeout_ms: u64,
72    },
73}
74
75fn default_timeout_ms() -> u64 {
76    100
77}
78
79impl From<OverflowPolicyConfig> for QueueOverflowPolicy {
80    fn from(config: OverflowPolicyConfig) -> Self {
81        match config {
82            OverflowPolicyConfig::DropOldest => QueueOverflowPolicy::DropOldest,
83            OverflowPolicyConfig::DropNewest => QueueOverflowPolicy::DropNewest,
84            OverflowPolicyConfig::Block { timeout_ms } => QueueOverflowPolicy::Block {
85                timeout: Duration::from_millis(timeout_ms),
86            },
87        }
88    }
89}
90
91impl QueueConfig {
92    /// Create a `BoundedQueue` from this configuration
93    #[must_use]
94    pub fn into_queue(self) -> BoundedQueue {
95        BoundedQueue::with_policy(self.capacity, self.overflow_policy.into())
96    }
97
98    /// Load configuration from TOML string
99    ///
100    /// # Errors
101    ///
102    /// Returns `toml::de::Error` if the TOML string is invalid or cannot be
103    /// deserialized into a `QueueConfig`.
104    pub fn from_toml(toml_str: &str) -> Result<Self, toml::de::Error> {
105        toml::from_str(toml_str)
106    }
107}
108
109impl BoundedQueue {
110    /// Create a `BoundedQueue` from configuration
111    #[must_use]
112    pub fn from_config(config: &QueueConfig) -> Self {
113        BoundedQueue::with_policy(config.capacity, config.overflow_policy.clone().into())
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_default_config() {
123        let config = QueueConfig::default();
124        assert_eq!(config.capacity, 1000);
125        assert!(matches!(
126            config.overflow_policy,
127            OverflowPolicyConfig::DropOldest
128        ));
129    }
130
131    #[test]
132    fn test_parse_drop_oldest() {
133        let toml = r#"
134            capacity = 500
135            [overflow_policy]
136            type = "drop_oldest"
137        "#;
138
139        let config: QueueConfig = toml::from_str(toml).unwrap();
140        assert_eq!(config.capacity, 500);
141        assert!(matches!(
142            config.overflow_policy,
143            OverflowPolicyConfig::DropOldest
144        ));
145    }
146
147    #[test]
148    fn test_parse_drop_newest() {
149        let toml = r#"
150            capacity = 200
151            [overflow_policy]
152            type = "drop_newest"
153        "#;
154
155        let config: QueueConfig = toml::from_str(toml).unwrap();
156        assert_eq!(config.capacity, 200);
157        assert!(matches!(
158            config.overflow_policy,
159            OverflowPolicyConfig::DropNewest
160        ));
161    }
162
163    #[test]
164    fn test_parse_block() {
165        let toml = r#"
166            capacity = 100
167            [overflow_policy]
168            type = "block"
169            timeout_ms = 250
170        "#;
171
172        let config: QueueConfig = toml::from_str(toml).unwrap();
173        assert_eq!(config.capacity, 100);
174        match config.overflow_policy {
175            OverflowPolicyConfig::Block { timeout_ms } => {
176                assert_eq!(timeout_ms, 250);
177            }
178            _ => panic!("Expected Block policy"),
179        }
180    }
181
182    #[test]
183    fn test_into_queue() {
184        let config = QueueConfig {
185            capacity: 50,
186            overflow_policy: OverflowPolicyConfig::DropNewest,
187        };
188
189        let queue = config.into_queue();
190        assert_eq!(queue.capacity(), 50);
191        assert!(matches!(queue.policy(), QueueOverflowPolicy::DropNewest));
192    }
193
194    #[test]
195    fn test_policy_conversion() {
196        let policy: QueueOverflowPolicy = OverflowPolicyConfig::DropOldest.into();
197        assert!(matches!(policy, QueueOverflowPolicy::DropOldest));
198
199        let policy: QueueOverflowPolicy = OverflowPolicyConfig::DropNewest.into();
200        assert!(matches!(policy, QueueOverflowPolicy::DropNewest));
201
202        let policy: QueueOverflowPolicy = OverflowPolicyConfig::Block { timeout_ms: 100 }.into();
203        assert!(matches!(policy, QueueOverflowPolicy::Block { .. }));
204        assert_eq!(policy.timeout(), Some(Duration::from_millis(100)));
205    }
206}