use crate::api::{Error, Language};
use std::str::FromStr;
pub mod defaults {
pub const CHUNK_SIZE: usize = 256 * 1024;
pub const PARALLEL_THRESHOLD: usize = 1024 * 1024;
pub const OVERLAP_SIZE: usize = 256;
}
#[derive(Debug, Clone)]
pub struct Config {
pub(crate) language: Language,
pub(crate) chunk_size: usize, pub(crate) parallel_threshold: usize, pub(crate) threads: Option<usize>, pub(crate) overlap_size: usize, }
impl Default for Config {
fn default() -> Self {
Self {
language: Language::default(),
chunk_size: defaults::CHUNK_SIZE,
parallel_threshold: defaults::PARALLEL_THRESHOLD,
threads: None,
overlap_size: defaults::OVERLAP_SIZE,
}
}
}
impl Config {
pub fn builder() -> ConfigBuilder {
ConfigBuilder::default()
}
pub fn small_text() -> Self {
Self {
language: Language::default(),
chunk_size: 8 * 1024, parallel_threshold: usize::MAX, threads: None,
overlap_size: 64, }
}
pub fn large_text() -> Self {
Self {
language: Language::default(),
chunk_size: 512 * 1024, parallel_threshold: 512 * 1024, threads: None, overlap_size: 512, }
}
pub fn streaming() -> Self {
Self {
language: Language::default(),
chunk_size: 32 * 1024, parallel_threshold: 256 * 1024, threads: Some(2), overlap_size: 128, }
}
pub(crate) fn validate(&self) -> Result<(), Error> {
if self.chunk_size == 0 {
return Err(Error::Configuration(
"chunk_size must be greater than 0".into(),
));
}
if self.overlap_size >= self.chunk_size {
return Err(Error::Configuration(
"overlap_size must be less than chunk_size".into(),
));
}
if let Some(threads) = self.threads {
if threads == 0 {
return Err(Error::Configuration(
"threads must be greater than 0".into(),
));
}
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct ConfigBuilder {
language: Option<String>,
chunk_size: Option<usize>,
parallel_threshold: Option<usize>,
threads: Option<usize>,
overlap_size: Option<usize>,
}
impl ConfigBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn language(mut self, code: impl Into<String>) -> Result<Self, Error> {
self.language = Some(code.into());
Ok(self)
}
pub fn chunk_size(mut self, bytes: usize) -> Self {
self.chunk_size = Some(bytes);
self
}
pub fn threads(mut self, count: Option<usize>) -> Self {
self.threads = count;
self
}
pub fn parallel_threshold(mut self, bytes: usize) -> Self {
self.parallel_threshold = Some(bytes);
self
}
pub fn overlap_size(mut self, bytes: usize) -> Self {
self.overlap_size = Some(bytes);
self
}
pub fn build(self) -> Result<Config, Error> {
let mut config = Config::default();
if let Some(lang_code) = self.language {
config.language = Language::from_str(&lang_code)?;
}
if let Some(size) = self.chunk_size {
config.chunk_size = size;
}
if let Some(threshold) = self.parallel_threshold {
config.parallel_threshold = threshold;
}
if self.threads.is_some() {
config.threads = self.threads;
}
if let Some(overlap) = self.overlap_size {
config.overlap_size = overlap;
}
config.validate()?;
Ok(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = Config::default();
assert_eq!(config.chunk_size, defaults::CHUNK_SIZE);
assert_eq!(config.parallel_threshold, defaults::PARALLEL_THRESHOLD);
assert_eq!(config.overlap_size, defaults::OVERLAP_SIZE);
assert!(config.threads.is_none());
assert!(config.validate().is_ok());
}
#[test]
fn test_config_validation() {
let config = Config {
chunk_size: 0,
..Default::default()
};
assert!(config.validate().is_err());
let config = Config {
chunk_size: 100,
overlap_size: 200,
..Default::default()
};
assert!(config.validate().is_err());
let config = Config {
threads: Some(0),
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_preset_configs() {
let small = Config::small_text();
assert_eq!(small.chunk_size, 8 * 1024);
assert_eq!(small.parallel_threshold, usize::MAX);
assert_eq!(small.overlap_size, 64);
assert!(small.validate().is_ok());
let large = Config::large_text();
assert_eq!(large.chunk_size, 512 * 1024);
assert_eq!(large.parallel_threshold, 512 * 1024);
assert_eq!(large.overlap_size, 512);
assert!(large.validate().is_ok());
let streaming = Config::streaming();
assert_eq!(streaming.chunk_size, 32 * 1024);
assert_eq!(streaming.parallel_threshold, 256 * 1024);
assert_eq!(streaming.threads, Some(2));
assert_eq!(streaming.overlap_size, 128);
assert!(streaming.validate().is_ok());
}
#[test]
fn test_config_builder_with_new_fields() {
let config = Config::builder()
.chunk_size(128 * 1024)
.parallel_threshold(256 * 1024)
.overlap_size(512)
.threads(Some(4))
.build()
.unwrap();
assert_eq!(config.chunk_size, 128 * 1024);
assert_eq!(config.parallel_threshold, 256 * 1024);
assert_eq!(config.overlap_size, 512);
assert_eq!(config.threads, Some(4));
}
#[test]
fn test_config_validation_boundary_values() {
let config = Config {
chunk_size: 1,
overlap_size: 0,
..Default::default()
};
assert!(config.validate().is_ok());
let config = Config {
chunk_size: 100,
overlap_size: 99,
..Default::default()
};
assert!(config.validate().is_ok());
let config = Config {
chunk_size: 100,
overlap_size: 100,
..Default::default()
};
let result = config.validate();
assert!(result.is_err());
match result {
Err(Error::Configuration(msg)) => {
assert!(msg.contains("overlap_size must be less than chunk_size"));
}
_ => panic!("Expected Configuration error"),
}
let config = Config {
threads: Some(1),
..Default::default()
};
assert!(config.validate().is_ok());
}
#[test]
fn test_config_builder_invalid_configurations() {
let result = Config::builder().chunk_size(0).build();
assert!(result.is_err());
let result = Config::builder().chunk_size(100).overlap_size(100).build();
assert!(result.is_err());
let result = Config::builder().threads(Some(0)).build();
assert!(result.is_err());
}
#[test]
fn test_config_builder_with_invalid_language() {
let result = Config::builder()
.language("invalid_lang")
.unwrap() .build();
assert!(result.is_err());
match result {
Err(Error::InvalidLanguage(msg)) => {
assert!(msg.contains("invalid_lang"));
}
_ => panic!("Expected InvalidLanguage error"),
}
}
#[test]
fn test_config_validation_error_messages() {
let config = Config {
chunk_size: 0,
..Default::default()
};
match config.validate() {
Err(Error::Configuration(msg)) => {
assert_eq!(msg, "chunk_size must be greater than 0");
}
_ => panic!("Expected specific error message"),
}
let config = Config {
threads: Some(0),
..Default::default()
};
match config.validate() {
Err(Error::Configuration(msg)) => {
assert_eq!(msg, "threads must be greater than 0");
}
_ => panic!("Expected specific error message"),
}
}
#[test]
fn test_config_builder_partial_configuration() {
let config = Config::builder().chunk_size(64 * 1024).build().unwrap();
assert_eq!(config.chunk_size, 64 * 1024);
assert_eq!(config.parallel_threshold, defaults::PARALLEL_THRESHOLD);
assert_eq!(config.overlap_size, defaults::OVERLAP_SIZE);
assert!(config.threads.is_none());
}
#[test]
fn test_config_builder_language_setting() {
let config = Config::builder().language("en").unwrap().build().unwrap();
assert_eq!(config.language, Language::English);
let config = Config::builder().language("ja").unwrap().build().unwrap();
assert_eq!(config.language, Language::Japanese);
}
#[test]
fn test_large_configuration_values() {
let config = Config {
chunk_size: usize::MAX / 2,
overlap_size: 1024,
parallel_threshold: usize::MAX / 2,
threads: Some(1024),
..Default::default()
};
assert!(config.validate().is_ok());
}
}