diff --git a/src/filter_plugin/mod.rs b/src/filter_plugin/mod.rs index abbd133..d25c608 100644 --- a/src/filter_plugin/mod.rs +++ b/src/filter_plugin/mod.rs @@ -71,6 +71,9 @@ pub struct FilterOption { pub trait FilterPlugin: Send { /// Processes the input stream and writes the filtered output. /// + /// This method reads from the input reader and applies filtering logic, + /// writing the processed data to the output writer. + /// /// # Arguments /// /// * `reader` - A boxed mutable reference to the input reader providing the data to filter. @@ -79,18 +82,64 @@ pub trait FilterPlugin: Send { /// # Returns /// /// A `Result` indicating success (`Ok(())`) or failure with an `io::Error`. + /// + /// # Examples + /// + /// ``` + /// impl FilterPlugin for MyFilter { + /// fn filter(&mut self, reader: Box<&mut dyn Read>, writer: Box<&mut dyn Write>) -> Result<()> { + /// // Read and filter data + /// let mut buf = [0; 1024]; + /// while let Ok(n) = reader.as_mut().read(&mut buf) { + /// if n == 0 { break; } + /// // Apply filter logic to buf[0..n] + /// writer.as_mut().write_all(&buf[0..n])?; + /// } + /// Ok(()) + /// } + /// // ... other methods + /// } + /// ``` fn filter(&mut self, reader: Box<&mut dyn Read>, writer: Box<&mut dyn Write>) -> Result<()>; + /// Clones this plugin into a new boxed instance. /// + /// This method is required for dynamic dispatch and cloning in filter chains. + /// /// # Returns /// /// A new `Box` clone of the current plugin. + /// + /// # Examples + /// + /// ``` + /// fn clone_box(&self) -> Box { + /// Box::new(self.clone()) + /// } + /// ``` fn clone_box(&self) -> Box; + /// Returns the configuration options for this plugin. /// + /// Describes the configurable parameters, including names, defaults, and required flags. + /// /// # Returns /// /// A vector of `FilterOption` structs describing the plugin's options. + /// + /// # Examples + /// + /// ``` + /// fn options(&self) -> Vec { + /// vec![ + /// FilterOption { + /// name: "pattern".to_string(), + /// default: None, + /// required: true, + /// }, + /// ] + /// } + /// ``` fn options(&self) -> Vec; } @@ -127,6 +176,22 @@ pub struct FilterChain { plugins: Vec>, } +/// A chain of filter plugins applied sequentially. +/// +/// Chains multiple filters, applying them in order to the input stream. +/// +/// # Fields +/// +/// * `plugins` - Vector of boxed filter plugins. +/// +/// # Examples +/// +/// ``` +/// let mut chain = FilterChain::new(); +/// chain.add_plugin(Box::new(HeadLinesFilter::new(10))); +/// chain.filter(&mut reader, &mut writer)?; +/// ``` + impl Clone for FilterChain { /// Clones this filter chain. /// @@ -159,6 +224,13 @@ impl FilterChain { /// # Returns /// /// A new `FilterChain` with no plugins. + /// + /// # Examples + /// + /// ``` + /// let chain = FilterChain::new(); + /// assert!(chain.plugins.is_empty()); + /// ``` pub fn new() -> Self { Self { plugins: Vec::new(), @@ -167,15 +239,27 @@ impl FilterChain { /// Adds a plugin to the chain. /// + /// Plugins are applied in the order they are added. + /// /// # Arguments /// /// * `plugin` - The boxed filter plugin to add to the chain. + /// + /// # Examples + /// + /// ``` + /// let mut chain = FilterChain::new(); + /// chain.add_plugin(Box::new(GrepFilter::new("error".to_string()))); + /// ``` pub fn add_plugin(&mut self, plugin: Box) { self.plugins.push(plugin); } /// Applies the filter chain to the input and writes to the output. /// + /// If no plugins are present, data is copied directly from reader to writer. + /// For multiple plugins, intermediate results are buffered. + /// /// # Arguments /// /// * `reader` - A mutable reference to the input reader providing the data stream. @@ -184,6 +268,14 @@ impl FilterChain { /// # Returns /// /// A `Result` indicating success (`Ok(())`) or failure with an `io::Error` if any filter in the chain fails. + /// + /// # Examples + /// + /// ``` + /// let mut chain = FilterChain::new(); + /// chain.add_plugin(Box::new(HeadBytesFilter::new(100))); + /// chain.filter(&mut input_reader, &mut output_writer)?; + /// ``` pub fn filter(&mut self, reader: &mut dyn Read, writer: &mut dyn Write) -> Result<()> { if self.plugins.is_empty() { // If no plugins, just copy the input to output diff --git a/src/modes/server/common.rs b/src/modes/server/common.rs index 18a41b1..7c46b7d 100644 --- a/src/modes/server/common.rs +++ b/src/modes/server/common.rs @@ -55,6 +55,22 @@ pub struct ServerConfig { /// This struct encapsulates the shared state that is accessible to all request handlers, /// including database connections, file paths, services, and configuration. #[derive(Clone)] +/// Application state shared across all routes. +/// +/// This struct encapsulates the shared state that is accessible to all request handlers, +/// including database connections, file paths, services, and configuration. +/// +/// # Examples +/// +/// ``` +/// let state = AppState { +/// db: Arc::new(Mutex::new(conn)), +/// data_dir: PathBuf::from("/data"), +/// item_service: Arc::new(ItemService::new(data_dir.clone())), +/// cmd: Arc::new(Mutex::new(Command::new("keep"))), +/// settings: Arc::new(settings), +/// }; +/// ``` pub struct AppState { /// Database connection wrapped in Arc. /// @@ -84,6 +100,24 @@ pub struct AppState { /// This generic type is used for all API responses to provide a consistent structure across /// different endpoints. #[derive(Debug, Serialize, Deserialize, ToSchema)] +/// Standard API response wrapper containing success status, data payload, and error information. +/// +/// This generic type is used for all API responses to provide a consistent structure across +/// different endpoints. +/// +/// # Type Parameters +/// +/// * `T` - The type of the data payload. +/// +/// # Examples +/// +/// ``` +/// let response: ApiResponse> = ApiResponse { +/// success: true, +/// data: Some(items), +/// error: None, +/// }; +/// ``` #[schema(description = "Standard API response wrapper containing success status, data payload, and error information")] pub struct ApiResponse { /// Success indicator. @@ -104,6 +138,19 @@ pub struct ApiResponse { /// /// Specialized response for endpoints that return multiple items. #[derive(Serialize, Deserialize, ToSchema)] +/// Response type for list of item information. +/// +/// Specialized response for endpoints that return multiple items. +/// +/// # Examples +/// +/// ``` +/// let response = ItemInfoListResponse { +/// success: true, +/// data: Some(vec![item_info]), +/// error: None, +/// }; +/// ``` pub struct ItemInfoListResponse { /// Success indicator. /// @@ -123,6 +170,19 @@ pub struct ItemInfoListResponse { /// /// Specialized response for endpoints that return a single item's details. #[derive(Serialize, Deserialize, ToSchema)] +/// Response type for single item information. +/// +/// Specialized response for endpoints that return a single item's details. +/// +/// # Examples +/// +/// ``` +/// let response = ItemInfoResponse { +/// success: true, +/// data: Some(item_info), +/// error: None, +/// }; +/// ``` pub struct ItemInfoResponse { /// Success indicator. /// @@ -142,6 +202,19 @@ pub struct ItemInfoResponse { /// /// Specialized response for endpoints that return item content and related metadata. #[derive(Serialize, Deserialize, ToSchema)] +/// Response type for item content information. +/// +/// Specialized response for endpoints that return item content and related metadata. +/// +/// # Examples +/// +/// ``` +/// let response = ItemContentInfoResponse { +/// success: true, +/// data: Some(content_info), +/// error: None, +/// }; +/// ``` pub struct ItemContentInfoResponse { /// Success indicator. /// @@ -161,6 +234,19 @@ pub struct ItemContentInfoResponse { /// /// Specialized response for metadata-only endpoints. #[derive(Serialize, Deserialize, ToSchema)] +/// Response type for metadata. +/// +/// Specialized response for metadata-only endpoints. +/// +/// # Examples +/// +/// ``` +/// let response = MetadataResponse { +/// success: true, +/// data: Some(meta_map), +/// error: None, +/// }; +/// ``` pub struct MetadataResponse { /// Success indicator. /// @@ -180,6 +266,19 @@ pub struct MetadataResponse { /// /// Specialized response for system status endpoints. #[derive(Serialize, Deserialize, ToSchema)] +/// Response type for status information. +/// +/// Specialized response for system status endpoints. +/// +/// # Examples +/// +/// ``` +/// let response = StatusInfoResponse { +/// success: true, +/// data: Some(status_info), +/// error: None, +/// }; +/// ``` pub struct StatusInfoResponse { /// Success indicator. /// @@ -200,6 +299,23 @@ pub struct StatusInfoResponse { /// This structure represents the full details of an item, combining basic item /// properties with associated tags and metadata. #[derive(Serialize, Deserialize, ToSchema)] +/// Complete information about a stored item including metadata and tags. +/// +/// This structure represents the full details of an item, combining basic item +/// properties with associated tags and metadata. +/// +/// # Examples +/// +/// ``` +/// let item_info = ItemInfo { +/// id: 42, +/// ts: "2023-12-01T15:30:45Z".to_string(), +/// size: Some(1024), +/// compression: "gzip".to_string(), +/// tags: vec!["important".to_string()], +/// metadata: HashMap::from([("mime_type".to_string(), "text/plain".to_string())]), +/// }; +/// ``` #[schema(description = "Complete information about a stored item including metadata and tags")] pub struct ItemInfo { /// Item ID. @@ -245,6 +361,20 @@ pub struct ItemInfo { /// * `content` - Optional string content (text only). /// * `binary` - True if binary content. #[derive(Serialize, Deserialize, ToSchema)] +/// Item information including content and metadata, with binary detection. +/// +/// This structure provides item details along with its content, handling binary +/// content detection and safe string representation. +/// +/// # Examples +/// +/// ``` +/// let content_info = ItemContentInfo { +/// metadata: HashMap::from([("mime_type".to_string(), "text/plain".to_string())]), +/// content: Some("Hello, world!".to_string()), +/// binary: false, +/// }; +/// ``` #[schema(description = "Item information including content and metadata, with binary detection")] pub struct ItemContentInfo { /// Metadata hashmap. @@ -273,6 +403,15 @@ pub struct ItemContentInfo { /// /// * `tags` - Optional comma-separated tags for filtering. #[derive(Debug, Deserialize)] +/// Query parameters for tags. +/// +/// Structure for handling tag-based query parameters in API requests. +/// +/// # Examples +/// +/// ``` +/// let query = TagsQuery { tags: Some("tag1,tag2".to_string()) }; +/// ``` pub struct TagsQuery { /// Optional comma-separated tags. /// @@ -291,6 +430,20 @@ pub struct TagsQuery { /// * `start` - Optional start index. /// * `count` - Optional item limit. #[derive(Debug, Deserialize)] +/// Query parameters for listing items. +/// +/// Structure for pagination and sorting parameters in item listing endpoints. +/// +/// # Examples +/// +/// ``` +/// let query = ListItemsQuery { +/// tags: Some("important".to_string()), +/// order: Some("newest".to_string()), +/// start: Some(0), +/// count: Some(10), +/// }; +/// ``` pub struct ListItemsQuery { /// Optional comma-separated tags for filtering. /// @@ -322,6 +475,21 @@ pub struct ListItemsQuery { /// * `stream` - Enable streaming (default false). /// * `as_meta` - Return as JSON metadata (default false). #[derive(Debug, Deserialize, utoipa::ToSchema)] +/// Query parameters for item retrieval. +/// +/// Structure for content retrieval parameters, including binary handling and streaming options. +/// +/// # Examples +/// +/// ``` +/// let query = ItemQuery { +/// allow_binary: true, +/// offset: 0, +/// length: 1024, +/// stream: false, +/// as_meta: false, +/// }; +/// ``` pub struct ItemQuery { /// Allow binary content (default: true). /// @@ -363,6 +531,22 @@ pub struct ItemQuery { /// * `stream` - Enable streaming (default false). /// * `as_meta` - Return as JSON metadata (default false). #[derive(Debug, Deserialize, utoipa::ToSchema)] +/// Query parameters for item content retrieval. +/// +/// Extended query parameters for content-specific operations, including tag filtering. +/// +/// # Examples +/// +/// ``` +/// let query = ItemContentQuery { +/// tags: Some("important".to_string()), +/// allow_binary: true, +/// offset: 0, +/// length: 1024, +/// stream: false, +/// as_meta: false, +/// }; +/// ``` pub struct ItemContentQuery { /// Optional comma-separated tags for filtering. /// diff --git a/src/services/compression_service.rs b/src/services/compression_service.rs index 2d6444f..462a50c 100644 --- a/src/services/compression_service.rs +++ b/src/services/compression_service.rs @@ -7,11 +7,56 @@ use anyhow::anyhow; pub struct CompressionService; +/// Service for handling compression and decompression of item content. +/// +/// Provides methods to read compressed item files either fully into memory +/// or as streaming readers. Supports various compression types via engines. +/// +/// # Examples +/// +/// ``` +/// let service = CompressionService::new(); +/// let content = service.get_item_content(path, "gzip")?; +/// ``` + impl CompressionService { + /// Creates a new CompressionService instance. + /// + /// # Returns + /// + /// A new `CompressionService`. + /// + /// # Examples + /// + /// ``` + /// let service = CompressionService::new(); + /// ``` pub fn new() -> Self { Self } + /// Reads and decompresses the full content of an item file into memory. + /// + /// # Arguments + /// + /// * `item_path` - Path to the compressed item file. + /// * `compression` - Compression type string (e.g., "gzip"). + /// + /// # Returns + /// + /// * `Result, CoreError>` - Decompressed content bytes. + /// + /// # Errors + /// + /// * `CoreError::Compression(...)` - If compression type invalid. + /// * `CoreError::Other(...)` - If file open or read fails. + /// + /// # Examples + /// + /// ``` + /// let content = service.get_item_content(item_path, "lz4")?; + /// assert_eq!(content.len(), expected_size); + /// ``` pub fn get_item_content(&self, item_path: PathBuf, compression: &str) -> Result, CoreError> { let compression_type = CompressionType::from_str(compression) .map_err(|e| CoreError::Compression(e.to_string()))?; @@ -25,6 +70,32 @@ impl CompressionService { Ok(content) } + /// Opens a streaming reader for decompressing item content. + /// + /// For Send compatibility, reads full content into memory and returns a Cursor. + /// Note: Not suitable for very large files due to memory usage. + /// + /// # Arguments + /// + /// * `item_path` - Path to the compressed item file. + /// * `compression` - Compression type string. + /// + /// # Returns + /// + /// * `Result, CoreError>` - Boxed streaming reader. + /// + /// # Errors + /// + /// * `CoreError::Compression(...)` - If compression type invalid. + /// * `CoreError::Other(...)` - If file open or read fails. + /// + /// # Examples + /// + /// ``` + /// let mut reader = service.stream_item_content(item_path, "gzip")?; + /// let mut buf = [0; 1024]; + /// let n = reader.read(&mut buf)?; + /// ``` pub fn stream_item_content( &self, item_path: PathBuf,