ri/queue/core.rs
1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of Ri.
4//! The Ri project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//! http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18#![allow(non_snake_case)]
19
20//! # Queue Core Implementation
21//!
22//! This file defines the core queueing interfaces and message structures for the Ri queue system.
23//! It provides the fundamental building blocks for implementing various queue backends.
24//!
25//! ## Key Components
26//!
27//! - **RiQueueMessage**: Message structure for queue operations
28//! - **QueueStats**: Statistics for queue monitoring
29//! - **RiQueueProducer**: Trait for producing messages to queues
30//! - **RiQueueConsumer**: Trait for consuming messages from queues
31//! - **RiQueue**: Main queue trait defining queue operations
32//!
33//! ## Design Principles
34//!
35//! 1. **Async-First**: All queue operations are asynchronous
36//! 2. **Type Safety**: Strongly typed message structures
37//! 3. **Retry Mechanism**: Built-in support for message retry with configurable maximum attempts
38//! 4. **Header Support**: Allows adding custom headers to messages
39//! 5. **Statistics**: Comprehensive queue statistics for monitoring
40//! 6. **Extensible**: Easy to implement new queue backends
41//! 7. **Thread-safe**: All traits are Send + Sync for safe concurrent use
42//!
43//! ## Usage
44//!
45//! ```rust
46//! use ri::queue::{RiQueueMessage, RiQueueProducer, RiQueueConsumer, RiQueue};
47//! use ri::core::RiResult;
48//! use serde_json::json;
49//!
50//! async fn example(queue: &dyn RiQueue) -> RiResult<()> {
51//! // Create a producer
52//! let producer = queue.create_producer().await?;
53//!
54//! // Create a message
55//! let payload = json!({ "key": "value" }).to_string().into_bytes();
56//! let message = RiQueueMessage::new(payload)
57//! .with_max_retries(5);
58//!
59//! // Send the message
60//! producer.send(message).await?;
61//!
62//! // Create a consumer
63//! let consumer = queue.create_consumer("consumer_group_1").await?;
64//!
65//! // Receive a message
66//! if let Some(message) = consumer.receive().await? {
67//! // Process the message
68//! let payload = String::from_utf8_lossy(&message.payload);
69//! println!("Received message: {}", payload);
70//!
71//! // Acknowledge the message
72//! consumer.ack(&message.id).await?;
73//! }
74//!
75//! Ok(())
76//! }
77//! ```
78
79use async_trait::async_trait;
80use serde::{Serialize, Deserialize};
81use std::collections::HashMap as FxHashMap;
82use std::time::SystemTime;
83use crate::core::RiResult;
84
85/// Error types for queue operations.
86#[derive(Debug, Clone)]
87pub enum RiQueueError {
88 /// Backend-specific error with descriptive message
89 BackendError(String),
90 /// Configuration error
91 ConfigError(String),
92 /// Connection error
93 ConnectionError(String),
94 /// Message not found
95 MessageNotFound(String),
96 /// Consumer group error
97 ConsumerGroupError(String),
98 /// Serialization error
99 SerializationError(String),
100}
101
102impl std::fmt::Display for RiQueueError {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 match self {
105 RiQueueError::BackendError(msg) => write!(f, "Queue backend error: {}", msg),
106 RiQueueError::ConfigError(msg) => write!(f, "Queue configuration error: {}", msg),
107 RiQueueError::ConnectionError(msg) => write!(f, "Queue connection error: {}", msg),
108 RiQueueError::MessageNotFound(msg) => write!(f, "Message not found: {}", msg),
109 RiQueueError::ConsumerGroupError(msg) => write!(f, "Consumer group error: {}", msg),
110 RiQueueError::SerializationError(msg) => write!(f, "Serialization error: {}", msg),
111 }
112 }
113}
114
115impl std::error::Error for RiQueueError {}
116
117impl From<RiQueueError> for crate::core::RiError {
118 fn from(error: RiQueueError) -> Self {
119 crate::core::RiError::Queue(error.to_string())
120 }
121}
122
123/// Message structure for queue operations.
124///
125/// This struct represents a message that can be sent to and received from queues. It includes
126/// a unique ID, payload, headers, timestamp, and retry information.
127#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct RiQueueMessage {
130 /// Unique message ID
131 pub id: String,
132 /// Message payload as bytes
133 pub payload: Vec<u8>,
134 /// Custom headers for the message
135 pub headers: FxHashMap<String, String>,
136 /// Timestamp when the message was created
137 pub timestamp: SystemTime,
138 /// Number of times this message has been retried
139 pub retry_count: u32,
140 /// Maximum number of retry attempts allowed
141 pub max_retries: u32,
142}
143
144impl RiQueueMessage {
145 /// Maximum payload size in bytes (10 MB)
146 pub const MAX_PAYLOAD_SIZE: usize = 10 * 1024 * 1024;
147
148 /// Maximum header key length
149 pub const MAX_HEADER_KEY_LEN: usize = 256;
150
151 /// Maximum header value length
152 pub const MAX_HEADER_VALUE_LEN: usize = 4096;
153
154 /// Maximum number of headers
155 pub const MAX_HEADERS: usize = 64;
156
157 /// Creates a new message with the given payload.
158 ///
159 /// # Parameters
160 ///
161 /// - `payload`: The message payload as bytes
162 ///
163 /// # Returns
164 ///
165 /// A new `RiQueueMessage` instance
166 ///
167 /// # Errors
168 ///
169 /// Returns an error if the payload exceeds `MAX_PAYLOAD_SIZE` (10 MB)
170 pub fn new(payload: Vec<u8>) -> Self {
171 // Security: Truncate payload if it exceeds maximum size
172 let payload = if payload.len() > Self::MAX_PAYLOAD_SIZE {
173 log::warn!(
174 "[Ri.Queue] Payload size {} exceeds maximum {}, truncating",
175 payload.len(),
176 Self::MAX_PAYLOAD_SIZE
177 );
178 payload[..Self::MAX_PAYLOAD_SIZE].to_vec()
179 } else {
180 payload
181 };
182
183 Self {
184 id: uuid::Uuid::new_v4().to_string(),
185 payload,
186 headers: FxHashMap::default(),
187 timestamp: SystemTime::now(),
188 retry_count: 0,
189 max_retries: 3,
190 }
191 }
192
193 /// Creates a new message with validation.
194 ///
195 /// # Parameters
196 ///
197 /// - `payload`: The message payload as bytes
198 ///
199 /// # Returns
200 ///
201 /// A `RiResult<RiQueueMessage>` containing the new message or an error
202 pub fn new_validated(payload: Vec<u8>) -> RiResult<Self> {
203 if payload.len() > Self::MAX_PAYLOAD_SIZE {
204 return Err(RiQueueError::ConfigError(format!(
205 "Payload size {} exceeds maximum allowed {}",
206 payload.len(),
207 Self::MAX_PAYLOAD_SIZE
208 )).into());
209 }
210
211 Ok(Self {
212 id: uuid::Uuid::new_v4().to_string(),
213 payload,
214 headers: FxHashMap::default(),
215 timestamp: SystemTime::now(),
216 retry_count: 0,
217 max_retries: 3,
218 })
219 }
220
221 /// Adds custom headers to the message.
222 ///
223 /// # Parameters
224 ///
225 /// - `headers`: A HashMap of custom headers
226 ///
227 /// # Returns
228 ///
229 /// The updated `RiQueueMessage` instance
230 ///
231 /// # Security
232 ///
233 /// Headers are validated for size and count limits to prevent memory exhaustion attacks.
234 pub fn with_headers(mut self, headers: FxHashMap<String, String>) -> Self {
235 // Security: Limit number of headers
236 let mut safe_headers = FxHashMap::with_capacity(
237 headers.len().min(Self::MAX_HEADERS)
238 );
239
240 for (key, value) in headers {
241 if safe_headers.len() >= Self::MAX_HEADERS {
242 log::warn!("[Ri.Queue] Maximum header count {} reached, ignoring remaining headers", Self::MAX_HEADERS);
243 break;
244 }
245
246 // Security: Validate header key length
247 let safe_key = if key.len() > Self::MAX_HEADER_KEY_LEN {
248 log::warn!("[Ri.Queue] Header key '{}' truncated to {} characters", key, Self::MAX_HEADER_KEY_LEN);
249 key[..Self::MAX_HEADER_KEY_LEN].to_string()
250 } else {
251 key
252 };
253
254 // Security: Validate header value length
255 let safe_value = if value.len() > Self::MAX_HEADER_VALUE_LEN {
256 log::warn!("[Ri.Queue] Header value for key '{}' truncated to {} characters", safe_key, Self::MAX_HEADER_VALUE_LEN);
257 value[..Self::MAX_HEADER_VALUE_LEN].to_string()
258 } else {
259 value
260 };
261
262 // Security: Check for control characters
263 if safe_key.chars().any(|c| c.is_control()) {
264 log::warn!("[Ri.Queue] Header key contains control characters, skipping");
265 continue;
266 }
267
268 safe_headers.insert(safe_key, safe_value);
269 }
270
271 self.headers = safe_headers;
272 self
273 }
274
275 /// Sets the maximum number of retry attempts for this message.
276 ///
277 /// # Parameters
278 ///
279 /// - `max_retries`: The maximum number of retry attempts
280 ///
281 /// # Returns
282 ///
283 /// The updated `RiQueueMessage` instance
284 pub fn with_max_retries(mut self, max_retries: u32) -> Self {
285 self.max_retries = max_retries;
286 self
287 }
288
289 /// Increments the retry count for this message.
290 pub fn increment_retry(&mut self) {
291 self.retry_count += 1;
292 }
293
294 /// Checks if this message can be retried.
295 ///
296 /// # Returns
297 ///
298 /// `true` if the message can be retried, `false` otherwise
299 pub fn can_retry(&self) -> bool {
300 self.retry_count < self.max_retries
301 }
302}
303
304#[cfg(feature = "pyo3")]
305/// Python bindings for RiQueueMessage
306#[pyo3::prelude::pymethods]
307impl RiQueueMessage {
308 #[new]
309 fn py_new(payload: Vec<u8>) -> Self {
310 Self::new(payload)
311 }
312
313 #[staticmethod]
314 fn py_new_with_string(payload: String) -> Self {
315 Self::new(payload.into_bytes())
316 }
317
318 fn get_payload_string(&self) -> String {
319 String::from_utf8_lossy(&self.payload).to_string()
320 }
321
322 fn get_id(&self) -> String {
323 self.id.clone()
324 }
325}
326
327/// Statistics for queue monitoring.
328///
329/// This struct contains comprehensive statistics about a queue's performance and usage.
330#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
331#[derive(Debug, Clone)]
332pub struct RiQueueStats {
333 /// Name of the queue
334 pub queue_name: String,
335 /// Current number of messages in the queue
336 pub message_count: u64,
337 /// Number of active consumers
338 pub consumer_count: u32,
339 /// Number of active producers
340 pub producer_count: u32,
341 /// Total number of processed messages
342 pub processed_messages: u64,
343 /// Total number of failed messages
344 pub failed_messages: u64,
345 /// Average processing time in milliseconds
346 pub avg_processing_time_ms: f64,
347 /// Total bytes sent
348 pub total_bytes_sent: u64,
349 /// Total bytes received
350 pub total_bytes_received: u64,
351 /// Timestamp of last message (milliseconds since start)
352 pub last_message_time: u64,
353}
354
355#[cfg(feature = "pyo3")]
356/// Python bindings for RiQueueStats
357#[pyo3::prelude::pymethods]
358impl RiQueueStats {
359 #[new]
360 fn py_new(queue_name: String) -> Self {
361 Self {
362 queue_name,
363 message_count: 0,
364 consumer_count: 0,
365 producer_count: 0,
366 processed_messages: 0,
367 failed_messages: 0,
368 avg_processing_time_ms: 0.0,
369 total_bytes_sent: 0,
370 total_bytes_received: 0,
371 last_message_time: 0,
372 }
373 }
374}
375
376/// Trait for producing messages to queues.
377///
378/// This trait defines the interface for sending messages to queues, including single message
379/// sends and batch sends.
380#[async_trait]
381pub trait RiQueueProducer: Send + Sync {
382 async fn send(&self, message: RiQueueMessage) -> RiResult<()>;
383
384 async fn send_batch(&self, messages: Vec<RiQueueMessage>) -> RiResult<()>;
385
386 async fn send_multi(&self, messages: &[RiQueueMessage]) -> RiResult<()> {
387 for message in messages {
388 self.send(message.clone()).await?;
389 }
390 Ok(())
391 }
392}
393
394/// Trait for consuming messages from queues.
395///
396/// This trait defines the interface for receiving and acknowledging messages from queues.
397#[async_trait]
398pub trait RiQueueConsumer: Send + Sync {
399 /// Receives a message from the queue.
400 ///
401 /// # Returns
402 ///
403 /// A `RiResult<Option<RiQueueMessage>>` containing the message if available, or None if no message is available
404 async fn receive(&self) -> RiResult<Option<RiQueueMessage>>;
405
406 /// Acknowledges a message, indicating it has been successfully processed.
407 ///
408 /// # Parameters
409 ///
410 /// - `message_id`: The ID of the message to acknowledge
411 ///
412 /// # Returns
413 ///
414 /// A `RiResult<()>` indicating success or failure
415 async fn ack(&self, message_id: &str) -> RiResult<()>;
416
417 /// Negatively acknowledges a message, indicating it failed to process and should be retried.
418 ///
419 /// # Parameters
420 ///
421 /// - `message_id`: The ID of the message to negatively acknowledge
422 ///
423 /// # Returns
424 ///
425 /// A `RiResult<()>` indicating success or failure
426 async fn nack(&self, message_id: &str) -> RiResult<()>;
427
428 /// Pauses message consumption.
429 ///
430 /// # Returns
431 ///
432 /// A `RiResult<()>` indicating success or failure
433 async fn pause(&self) -> RiResult<()>;
434
435 /// Resumes message consumption after pausing.
436 ///
437 /// # Returns
438 ///
439 /// A `RiResult<()>` indicating success or failure
440 async fn resume(&self) -> RiResult<()>;
441
442 async fn receive_multi(&self, count: usize) -> RiResult<Vec<Option<RiQueueMessage>>> {
443 let mut messages = Vec::with_capacity(count);
444 for _ in 0..count {
445 messages.push(self.receive().await?);
446 }
447 Ok(messages)
448 }
449
450 async fn ack_multi(&self, message_ids: &[String]) -> RiResult<()> {
451 for id in message_ids {
452 self.ack(id).await?;
453 }
454 Ok(())
455 }
456}
457
458/// Main queue trait defining queue operations.
459///
460/// This trait defines the core operations for queues, including creating producers and consumers,
461/// getting statistics, purging queues, and deleting queues.
462#[async_trait]
463pub trait RiQueue: Send + Sync {
464 /// Creates a new producer for this queue.
465 ///
466 /// # Returns
467 ///
468 /// A `RiResult<Box<dyn RiQueueProducer>>` containing the producer
469 async fn create_producer(&self) -> RiResult<Box<dyn RiQueueProducer>>;
470
471 /// Creates a new consumer for this queue with the given consumer group.
472 ///
473 /// # Parameters
474 ///
475 /// - `consumer_group`: The name of the consumer group
476 ///
477 /// # Returns
478 ///
479 /// A `RiResult<Box<dyn RiQueueConsumer>>` containing the consumer
480 async fn create_consumer(&self, consumer_group: &str) -> RiResult<Box<dyn RiQueueConsumer>>;
481
482 /// Gets statistics for this queue.
483 ///
484 /// # Returns
485 ///
486 /// A `RiResult<RiQueueStats>` containing the queue statistics
487 async fn get_stats(&self) -> RiResult<RiQueueStats>;
488
489 /// Purges all messages from this queue.
490 ///
491 /// # Returns
492 ///
493 /// A `RiResult<()>` indicating success or failure
494 async fn purge(&self) -> RiResult<()>;
495
496 /// Deletes this queue.
497 ///
498 /// # Returns
499 ///
500 /// A `RiResult<()>` indicating success or failure
501 async fn delete(&self) -> RiResult<()>;
502}