Skip to main content

xls_rs/
traits.rs

1//! Trait definitions for xls-rs operations
2//!
3//! This module provides trait-based interfaces for better testability,
4//! maintainability, and separation of concerns.
5
6use crate::csv_handler::CellRange;
7use anyhow::Result;
8
9/// Trait for reading data from files
10pub trait DataReader: Send + Sync {
11    /// Read all data from a file
12    fn read(&self, path: &str) -> Result<Vec<Vec<String>>>;
13
14    /// Read data with headers (first row contains column names)
15    fn read_with_headers(&self, path: &str) -> Result<Vec<Vec<String>>>;
16
17    /// Read a specific cell range from a file
18    fn read_range(&self, path: &str, range: &CellRange) -> Result<Vec<Vec<String>>>;
19
20    /// Read data as JSON string
21    fn read_as_json(&self, path: &str) -> Result<String>;
22
23    /// Check if the file format is supported
24    fn supports_format(&self, path: &str) -> bool;
25}
26
27/// Trait for writing data to files
28pub trait DataWriter: Send + Sync {
29    /// Write data to a file
30    fn write(&self, path: &str, data: &[Vec<String>], options: DataWriteOptions) -> Result<()>;
31
32    /// Write data to a specific cell range
33    fn write_range(
34        &self,
35        path: &str,
36        data: &[Vec<String>],
37        start_row: usize,
38        start_col: usize,
39    ) -> Result<()>;
40
41    /// Append data to an existing file
42    fn append(&self, path: &str, data: &[Vec<String>]) -> Result<()>;
43
44    /// Check if the file format is supported
45    fn supports_format(&self, path: &str) -> bool;
46}
47
48/// Options for writing data
49#[derive(Debug, Clone, Default)]
50pub struct DataWriteOptions {
51    /// Sheet name (for Excel/ODS files)
52    pub sheet_name: Option<String>,
53    /// Column names (for Parquet/Avro files)
54    pub column_names: Option<Vec<String>>,
55    /// For Parquet/Avro: when true, `data[0]` is column/field names and `data[1..]` are rows.
56    pub include_headers: bool,
57}
58
59/// Unified trait for file handlers that can both read and write
60pub trait FileHandler: DataReader + DataWriter {
61    /// Get the format name (e.g., "csv", "xlsx", "parquet")
62    fn format_name(&self) -> &'static str;
63
64    /// Get supported file extensions
65    fn supported_extensions(&self) -> &'static [&'static str];
66}
67
68/// Trait for format detection
69pub trait FormatDetector: Send + Sync {
70    /// Detect the format of a file based on its path/extension
71    fn detect_format(&self, path: &str) -> Result<String>;
72
73    /// Check if a format is supported
74    fn is_supported(&self, format: &str) -> bool;
75
76    /// Get all supported formats
77    fn supported_formats(&self) -> Vec<String>;
78}
79
80/// Trait for schema/metadata operations
81pub trait SchemaProvider: Send + Sync {
82    /// Get schema information from a file
83    fn get_schema(&self, path: &str) -> Result<Vec<(String, String)>>;
84
85    /// Get column names from a file
86    fn get_column_names(&self, path: &str) -> Result<Vec<String>>;
87
88    /// Get number of rows in a file
89    fn get_row_count(&self, path: &str) -> Result<usize>;
90
91    /// Get number of columns in a file
92    fn get_column_count(&self, path: &str) -> Result<usize>;
93}
94
95/// Trait for streaming data reading
96pub trait StreamingReader: Send + Sync {
97    /// Open a file for streaming read
98    fn open(&self, path: &str) -> Result<Box<dyn StreamingReadIterator>>;
99}
100
101/// Iterator for streaming reads
102pub trait StreamingReadIterator: Iterator<Item = Result<Vec<String>>> {
103    /// Get the current row number
104    fn current_row(&self) -> usize;
105}
106
107/// Trait for streaming data writing
108pub trait StreamingWriter: Send + Sync {
109    /// Create a file for streaming write
110    fn create(&self, path: &str) -> Result<Box<dyn StreamingWriteHandle>>;
111}
112
113/// Handle for streaming writes
114pub trait StreamingWriteHandle: Send + Sync {
115    /// Write a single row
116    fn write_row(&mut self, row: &[String]) -> Result<()>;
117
118    /// Get number of rows written
119    fn rows_written(&self) -> usize;
120
121    /// Flush buffered data
122    fn flush(&mut self) -> Result<()>;
123}
124
125/// Trait for cell range operations
126pub trait CellRangeProvider: Send + Sync {
127    /// Parse a cell range string (e.g., "A1:C10")
128    fn parse_range(&self, range_str: &str) -> Result<CellRange>;
129
130    /// Convert row/column indices to cell reference (e.g., (0, 0) -> "A1")
131    fn to_cell_reference(&self, row: usize, col: usize) -> String;
132
133    /// Convert cell reference to row/column indices (e.g., "A1" -> (0, 0))
134    fn from_cell_reference(&self, cell: &str) -> Result<(usize, usize)>;
135}
136
137/// Trait for sorting operations
138pub trait SortOperator: Send + Sync {
139    fn sort(&self, data: &mut Vec<Vec<String>>, column: usize, ascending: bool) -> Result<()>;
140}
141
142/// Trait for filtering operations
143pub trait FilterOperator: Send + Sync {
144    fn filter(
145        &self,
146        data: &[Vec<String>],
147        column: usize,
148        condition: FilterCondition,
149    ) -> Result<Vec<Vec<String>>>;
150}
151
152/// Trait for transformation operations
153pub trait TransformOperator: Send + Sync {
154    fn transform(&self, data: &mut Vec<Vec<String>>, operation: TransformOperation) -> Result<()>;
155}
156
157/// Combined trait for all data operations
158pub trait DataOperator: SortOperator + FilterOperator + TransformOperator {}
159
160/// Filter condition for data operations
161#[derive(Debug, Clone)]
162pub enum FilterCondition {
163    Equals(String),
164    NotEquals(String),
165    GreaterThan(String),
166    GreaterThanOrEqual(String),
167    LessThan(String),
168    LessThanOrEqual(String),
169    Contains(String),
170    StartsWith(String),
171    EndsWith(String),
172    Regex(String),
173}
174
175/// Transform operation for data operations
176#[derive(Debug, Clone)]
177pub enum TransformOperation {
178    RenameColumn {
179        from: usize,
180        to: String,
181    },
182    DropColumn(usize),
183    AddColumn {
184        name: String,
185        formula: Option<String>,
186    },
187    FillNa {
188        column: usize,
189        value: String,
190    },
191}