forked from
smokesignal.events/quickdid
QuickDID is a high-performance AT Protocol identity resolution service written in Rust. It provides handle-to-DID resolution with Redis-backed caching and queue processing.
1//! No-operation queue adapter implementation.
2//!
3//! This module provides a queue adapter that discards all work items,
4//! useful for testing or when queue processing is disabled.
5
6use async_trait::async_trait;
7use std::time::Duration;
8use tokio::time::sleep;
9
10use super::adapter::QueueAdapter;
11use super::error::Result;
12
13/// No-operation queue adapter that discards all work items.
14///
15/// This adapter is useful for configurations where queuing is disabled
16/// or as a fallback when other queue adapters fail to initialize.
17/// All work items pushed to this queue are silently discarded.
18///
19/// # Features
20///
21/// - Zero resource usage
22/// - Always healthy
23/// - Discards all work items
24/// - Never returns items from pull
25///
26/// # Use Cases
27///
28/// - Testing environments where queue processing isn't needed
29/// - Graceful degradation when queue backends are unavailable
30/// - Configurations where queue processing is explicitly disabled
31///
32/// # Examples
33///
34/// ```
35/// use quickdid::queue::NoopQueueAdapter;
36/// use quickdid::queue::QueueAdapter;
37///
38/// # async fn example() -> anyhow::Result<()> {
39/// let queue = NoopQueueAdapter::<String>::new();
40///
41/// // Push is silently discarded
42/// queue.push("ignored".to_string()).await?;
43///
44/// // Pull never returns items (blocks indefinitely)
45/// // let item = queue.pull().await; // Would block forever
46///
47/// // Always reports healthy
48/// assert!(queue.is_healthy().await);
49///
50/// // Always reports empty
51/// assert_eq!(queue.depth().await, Some(0));
52/// # Ok(())
53/// # }
54/// ```
55pub struct NoopQueueAdapter<T>
56where
57 T: Send + Sync + 'static,
58{
59 _phantom: std::marker::PhantomData<T>,
60}
61
62impl<T> NoopQueueAdapter<T>
63where
64 T: Send + Sync + 'static,
65{
66 /// Create a new no-op queue adapter.
67 ///
68 /// # Examples
69 ///
70 /// ```
71 /// use quickdid::queue::NoopQueueAdapter;
72 ///
73 /// let queue = NoopQueueAdapter::<String>::new();
74 /// ```
75 pub fn new() -> Self {
76 Self {
77 _phantom: std::marker::PhantomData,
78 }
79 }
80}
81
82impl<T> Default for NoopQueueAdapter<T>
83where
84 T: Send + Sync + 'static,
85{
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91#[async_trait]
92impl<T> QueueAdapter<T> for NoopQueueAdapter<T>
93where
94 T: Send + Sync + 'static,
95{
96 async fn pull(&self) -> Option<T> {
97 // Never returns any work - sleeps to avoid busy-waiting
98 sleep(Duration::from_secs(60)).await;
99 None
100 }
101
102 async fn push(&self, _work: T) -> Result<()> {
103 // Silently discard the work
104 Ok(())
105 }
106
107 async fn ack(&self, _item: &T) -> Result<()> {
108 // No-op
109 Ok(())
110 }
111
112 async fn try_push(&self, _work: T) -> Result<()> {
113 // Silently discard the work
114 Ok(())
115 }
116
117 async fn depth(&self) -> Option<usize> {
118 // Always empty
119 Some(0)
120 }
121
122 async fn is_healthy(&self) -> bool {
123 // Always healthy
124 true
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[tokio::test]
133 async fn test_noop_queue_push() {
134 let queue = NoopQueueAdapter::<String>::new();
135
136 // Push should always succeed
137 queue.push("test1".to_string()).await.unwrap();
138 queue.push("test2".to_string()).await.unwrap();
139 queue.push("test3".to_string()).await.unwrap();
140 }
141
142 #[tokio::test]
143 async fn test_noop_queue_try_push() {
144 let queue = NoopQueueAdapter::<i32>::new();
145
146 // Try push should always succeed
147 queue.try_push(1).await.unwrap();
148 queue.try_push(2).await.unwrap();
149 queue.try_push(3).await.unwrap();
150 }
151
152 #[tokio::test]
153 async fn test_noop_queue_ack() {
154 let queue = NoopQueueAdapter::<String>::new();
155
156 // Ack should always succeed
157 queue.ack(&"any".to_string()).await.unwrap();
158 }
159
160 #[tokio::test]
161 async fn test_noop_queue_depth() {
162 let queue = NoopQueueAdapter::<String>::new();
163
164 // Should always report empty
165 assert_eq!(queue.depth().await, Some(0));
166
167 // Even after pushing items
168 queue.push("item".to_string()).await.unwrap();
169 assert_eq!(queue.depth().await, Some(0));
170 }
171
172 #[tokio::test]
173 async fn test_noop_queue_health() {
174 let queue = NoopQueueAdapter::<String>::new();
175
176 // Should always be healthy
177 assert!(queue.is_healthy().await);
178 }
179
180 #[tokio::test]
181 async fn test_noop_queue_default() {
182 let queue: NoopQueueAdapter<String> = Default::default();
183
184 // Default instance should work normally
185 queue.push("test".to_string()).await.unwrap();
186 assert!(queue.is_healthy().await);
187 }
188
189 #[tokio::test(flavor = "multi_thread")]
190 async fn test_noop_queue_pull_blocks() {
191 use tokio::time::timeout;
192
193 let queue = NoopQueueAdapter::<String>::new();
194
195 // Pull should block and not return immediately
196 let result = timeout(Duration::from_millis(100), queue.pull()).await;
197 assert!(result.is_err(), "Pull should have timed out");
198 }
199
200 #[tokio::test]
201 async fn test_noop_queue_with_custom_type() {
202 use serde::{Deserialize, Serialize};
203
204 #[derive(Debug, Clone, Serialize, Deserialize)]
205 struct CustomWork {
206 id: u64,
207 data: Vec<String>,
208 }
209
210 let queue = NoopQueueAdapter::<CustomWork>::new();
211
212 let work = CustomWork {
213 id: 123,
214 data: vec!["test".to_string()],
215 };
216
217 // Should handle custom types without issue
218 queue.push(work.clone()).await.unwrap();
219 queue.ack(&work).await.unwrap();
220 assert_eq!(queue.depth().await, Some(0));
221 }
222}