Skip to main content

sakurs_core/api/
config.rs

1//! Configuration API for sentence processing
2
3use crate::api::{Error, Language};
4use std::str::FromStr;
5
6/// Default configuration constants
7pub mod defaults {
8    /// Default chunk size in bytes (256KB)
9    pub const CHUNK_SIZE: usize = 256 * 1024;
10}
11
12/// Processing configuration
13#[derive(Debug, Clone)]
14pub struct Config {
15    pub(crate) language: Language,
16    pub(crate) chunk_size: usize,      // in bytes
17    pub(crate) threads: Option<usize>, // None = all available threads
18}
19
20impl Default for Config {
21    fn default() -> Self {
22        Self {
23            language: Language::default(),
24            chunk_size: defaults::CHUNK_SIZE,
25            threads: None,
26        }
27    }
28}
29
30impl Config {
31    /// Create a configuration builder
32    pub fn builder() -> ConfigBuilder {
33        ConfigBuilder::default()
34    }
35
36    /// Create a configuration optimized for small texts
37    pub fn small_text() -> Self {
38        Self {
39            language: Language::default(),
40            chunk_size: 8 * 1024, // 8KB chunks
41            threads: None,
42        }
43    }
44
45    /// Create a configuration optimized for large texts
46    pub fn large_text() -> Self {
47        Self {
48            language: Language::default(),
49            chunk_size: 512 * 1024, // 512KB chunks
50            threads: None,          // Use all available cores
51        }
52    }
53
54    /// Create a configuration optimized for streaming
55    pub fn streaming() -> Self {
56        Self {
57            language: Language::default(),
58            chunk_size: 32 * 1024, // 32KB chunks
59            threads: Some(2),      // Limited parallelism
60        }
61    }
62
63    /// Validate the configuration
64    pub(crate) fn validate(&self) -> Result<(), Error> {
65        if self.chunk_size == 0 {
66            return Err(Error::Configuration(
67                "chunk_size must be greater than 0".into(),
68            ));
69        }
70
71        if let Some(threads) = self.threads {
72            if threads == 0 {
73                return Err(Error::Configuration(
74                    "threads must be greater than 0".into(),
75                ));
76            }
77        }
78
79        Ok(())
80    }
81}
82
83/// Fluent builder for configuration
84#[derive(Debug, Default)]
85pub struct ConfigBuilder {
86    language: Option<String>,
87    chunk_size: Option<usize>,
88    threads: Option<usize>,
89}
90
91impl ConfigBuilder {
92    /// Create a new configuration builder
93    pub fn new() -> Self {
94        Self::default()
95    }
96
97    /// Set the language by code
98    pub fn language(mut self, code: impl Into<String>) -> Result<Self, Error> {
99        self.language = Some(code.into());
100        Ok(self)
101    }
102
103    /// Set the chunk size in bytes
104    pub fn chunk_size(mut self, bytes: usize) -> Self {
105        self.chunk_size = Some(bytes);
106        self
107    }
108
109    /// Set the number of threads (None = all available)
110    pub fn threads(mut self, count: Option<usize>) -> Self {
111        self.threads = count;
112        self
113    }
114
115    /// Build the configuration
116    pub fn build(self) -> Result<Config, Error> {
117        let mut config = Config::default();
118
119        if let Some(lang_code) = self.language {
120            config.language = Language::from_str(&lang_code)?;
121        }
122
123        if let Some(size) = self.chunk_size {
124            config.chunk_size = size;
125        }
126
127        if self.threads.is_some() {
128            config.threads = self.threads;
129        }
130
131        config.validate()?;
132        Ok(config)
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_default_config() {
142        let config = Config::default();
143        assert_eq!(config.chunk_size, defaults::CHUNK_SIZE);
144        assert!(config.threads.is_none());
145        assert!(config.validate().is_ok());
146    }
147
148    #[test]
149    fn test_config_validation() {
150        // Invalid chunk size
151        let config = Config {
152            chunk_size: 0,
153            ..Default::default()
154        };
155        assert!(config.validate().is_err());
156
157        // Invalid thread count
158        let config = Config {
159            threads: Some(0),
160            ..Default::default()
161        };
162        assert!(config.validate().is_err());
163    }
164
165    #[test]
166    fn test_preset_configs() {
167        // Small text preset
168        let small = Config::small_text();
169        assert_eq!(small.chunk_size, 8 * 1024);
170        assert!(small.validate().is_ok());
171
172        // Large text preset
173        let large = Config::large_text();
174        assert_eq!(large.chunk_size, 512 * 1024);
175        assert!(large.validate().is_ok());
176
177        // Streaming preset
178        let streaming = Config::streaming();
179        assert_eq!(streaming.chunk_size, 32 * 1024);
180        assert_eq!(streaming.threads, Some(2));
181        assert!(streaming.validate().is_ok());
182    }
183
184    #[test]
185    fn test_config_builder_with_new_fields() {
186        let config = Config::builder()
187            .chunk_size(128 * 1024)
188            .threads(Some(4))
189            .build()
190            .unwrap();
191
192        assert_eq!(config.chunk_size, 128 * 1024);
193        assert_eq!(config.threads, Some(4));
194    }
195
196    #[test]
197    fn test_config_validation_boundary_values() {
198        // Test chunk_size at minimum valid value
199        let config = Config {
200            chunk_size: 1,
201            ..Default::default()
202        };
203        assert!(config.validate().is_ok());
204
205        // Test threads at minimum valid value
206        let config = Config {
207            threads: Some(1),
208            ..Default::default()
209        };
210        assert!(config.validate().is_ok());
211    }
212
213    #[test]
214    fn test_config_builder_invalid_configurations() {
215        // Test building with invalid chunk_size
216        let result = Config::builder().chunk_size(0).build();
217        assert!(result.is_err());
218
219        // Test building with invalid thread count
220        let result = Config::builder().threads(Some(0)).build();
221        assert!(result.is_err());
222    }
223
224    #[test]
225    fn test_config_builder_with_invalid_language() {
226        let result = Config::builder()
227            .language("invalid_lang")
228            .unwrap() // language() returns Ok
229            .build(); // Error occurs here during validation
230
231        assert!(result.is_err());
232        match result {
233            Err(Error::InvalidLanguage(msg)) => {
234                assert!(msg.contains("invalid_lang"));
235            }
236            _ => panic!("Expected InvalidLanguage error"),
237        }
238    }
239
240    #[test]
241    fn test_config_validation_error_messages() {
242        // Test chunk_size error message
243        let config = Config {
244            chunk_size: 0,
245            ..Default::default()
246        };
247        match config.validate() {
248            Err(Error::Configuration(msg)) => {
249                assert_eq!(msg, "chunk_size must be greater than 0");
250            }
251            _ => panic!("Expected specific error message"),
252        }
253
254        // Test threads error message
255        let config = Config {
256            threads: Some(0),
257            ..Default::default()
258        };
259        match config.validate() {
260            Err(Error::Configuration(msg)) => {
261                assert_eq!(msg, "threads must be greater than 0");
262            }
263            _ => panic!("Expected specific error message"),
264        }
265    }
266
267    #[test]
268    fn test_config_builder_partial_configuration() {
269        // Test building with only some fields set
270        let config = Config::builder().chunk_size(64 * 1024).build().unwrap();
271
272        assert_eq!(config.chunk_size, 64 * 1024);
273        assert!(config.threads.is_none());
274    }
275
276    #[test]
277    fn test_config_builder_language_setting() {
278        // Test setting valid language
279        let config = Config::builder().language("en").unwrap().build().unwrap();
280        assert_eq!(config.language, Language::English);
281
282        let config = Config::builder().language("ja").unwrap().build().unwrap();
283        assert_eq!(config.language, Language::Japanese);
284    }
285
286    #[test]
287    fn test_large_configuration_values() {
288        // Test with very large valid values
289        let config = Config {
290            chunk_size: usize::MAX / 2,
291            threads: Some(1024),
292            ..Default::default()
293        };
294        assert!(config.validate().is_ok());
295    }
296}