use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DelimiterType {
Comma,
Tab,
Pipe,
}
impl DelimiterType {
pub fn as_str(&self) -> &'static str {
match self {
DelimiterType::Comma => ",",
DelimiterType::Tab => "\t",
DelimiterType::Pipe => "|",
}
}
}
impl std::str::FromStr for DelimiterType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"comma" | "," => Ok(DelimiterType::Comma),
"tab" | "\t" => Ok(DelimiterType::Tab),
"pipe" | "|" => Ok(DelimiterType::Pipe),
other => Err(format!(
"Invalid delimiter '{}'. Use 'comma', 'tab', or 'pipe'",
other
)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum QuoteStrategy {
Smart,
Always,
Never,
}
impl QuoteStrategy {
pub fn should_quote(&self, value: &str, delimiter: DelimiterType) -> bool {
match self {
QuoteStrategy::Always => true,
QuoteStrategy::Never => false,
QuoteStrategy::Smart => needs_quoting(value, delimiter),
}
}
}
fn needs_quoting(value: &str, delimiter: DelimiterType) -> bool {
if value.is_empty() {
return true;
}
if value == "null" || value == "true" || value == "false" {
return true;
}
if value.parse::<f64>().is_ok() {
return true;
}
if value.starts_with(' ') || value.ends_with(' ') {
return true;
}
let structural_chars = ":[]{}";
if value.chars().any(|c| structural_chars.contains(c)) {
return true;
}
if value.chars().any(|c| c.is_control()) {
return true;
}
if value.contains(delimiter.as_str()) {
return true;
}
false
}
#[derive(Debug, Clone)]
pub struct ConversionConfig {
pub indent_size: u8,
pub delimiter: DelimiterType,
pub length_marker: bool,
pub quote_strings: QuoteStrategy,
pub memory_limit: usize,
pub timeout: Duration,
pub enable_simd: bool,
pub pretty: bool,
pub validate_output: bool,
pub include_schema: bool,
pub max_depth: Option<usize>,
}
impl Default for ConversionConfig {
fn default() -> Self {
Self {
indent_size: 2,
delimiter: DelimiterType::Comma,
length_marker: true,
quote_strings: QuoteStrategy::Smart,
memory_limit: 100 * 1024 * 1024, timeout: Duration::from_secs(300), enable_simd: false,
pretty: true,
validate_output: true,
include_schema: true,
max_depth: Some(1000), }
}
}
impl ConversionConfig {
pub fn new() -> Self {
Self::default()
}
pub fn small_files() -> Self {
Self {
memory_limit: 10 * 1024 * 1024, timeout: Duration::from_secs(30), ..Default::default()
}
}
pub fn large_files() -> Self {
Self {
memory_limit: 1024 * 1024 * 1024, timeout: Duration::from_secs(1800), enable_simd: true,
validate_output: false, ..Default::default()
}
}
pub fn batch_processing() -> Self {
Self {
memory_limit: 512 * 1024 * 1024, timeout: Duration::from_secs(600), enable_simd: true,
pretty: false, validate_output: false,
..Default::default()
}
}
pub fn with_indent_size(mut self, size: u8) -> Result<Self, String> {
if size > 8 {
return Err("Indent size must be 0-8 spaces".to_string());
}
self.indent_size = size;
Ok(self)
}
pub fn with_delimiter(mut self, delimiter: DelimiterType) -> Self {
self.delimiter = delimiter;
self
}
pub fn with_length_marker(mut self, enabled: bool) -> Self {
self.length_marker = enabled;
self
}
pub fn with_quote_strategy(mut self, strategy: QuoteStrategy) -> Self {
self.quote_strings = strategy;
self
}
pub fn with_memory_limit(mut self, limit_bytes: usize) -> Self {
self.memory_limit = limit_bytes;
self
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn with_simd(mut self, enabled: bool) -> Self {
self.enable_simd = enabled;
self
}
pub fn with_pretty(mut self, pretty: bool) -> Self {
self.pretty = pretty;
self
}
pub fn with_validation(mut self, validate: bool) -> Self {
self.validate_output = validate;
self
}
pub fn with_max_depth(mut self, depth: Option<usize>) -> Self {
self.max_depth = depth;
self
}
pub fn validate(&self) -> Result<(), String> {
if self.indent_size > 8 {
return Err("Indent size must be 0-8 spaces".to_string());
}
if self.memory_limit < 1024 {
return Err("Memory limit must be at least 1KB".to_string());
}
if self.timeout.as_secs() == 0 {
return Err("Timeout must be greater than 0".to_string());
}
if let Some(depth) = self.max_depth {
if depth == 0 {
return Err("Max depth must be at least 1".to_string());
}
}
Ok(())
}
pub fn json_parser_type(&self) -> JsonParserType {
if self.enable_simd {
JsonParserType::SimdJson
} else {
JsonParserType::SerdeJson
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum JsonParserType {
SerdeJson,
SimdJson,
}
#[derive(Debug, Clone)]
pub enum ValidationStrategy {
None,
Basic,
Full,
}
#[derive(Debug, Clone)]
pub enum PerformanceProfile {
Speed,
Memory,
Balanced,
Custom(ConversionConfig),
}
impl PerformanceProfile {
pub fn to_config(&self) -> ConversionConfig {
match self {
PerformanceProfile::Speed => ConversionConfig::large_files(),
PerformanceProfile::Memory => ConversionConfig::small_files(),
PerformanceProfile::Balanced => ConversionConfig::default(),
PerformanceProfile::Custom(config) => config.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn test_default_config() {
let config = ConversionConfig::default();
assert_eq!(config.indent_size, 2);
assert_eq!(config.delimiter, DelimiterType::Comma);
assert!(config.length_marker);
assert_eq!(config.quote_strings, QuoteStrategy::Smart);
}
#[test]
fn test_config_validation() {
let mut config = ConversionConfig::default();
assert!(config.validate().is_ok());
config.indent_size = 10;
assert!(config.validate().is_err());
}
#[test]
fn test_quote_strategy() {
assert!(QuoteStrategy::Smart.should_quote("", DelimiterType::Comma));
assert!(QuoteStrategy::Smart.should_quote("null", DelimiterType::Comma));
assert!(!QuoteStrategy::Smart.should_quote("hello", DelimiterType::Comma));
assert!(QuoteStrategy::Smart.should_quote("hello,world", DelimiterType::Comma));
}
#[test]
fn test_delimiter_from_str() {
assert_eq!(
DelimiterType::from_str("comma").unwrap(),
DelimiterType::Comma
);
assert_eq!(DelimiterType::from_str("tab").unwrap(), DelimiterType::Tab);
assert_eq!(
DelimiterType::from_str("pipe").unwrap(),
DelimiterType::Pipe
);
assert!(DelimiterType::from_str("invalid").is_err());
}
#[test]
fn test_performance_profiles() {
let speed = PerformanceProfile::Speed;
let memory = PerformanceProfile::Memory;
let balanced = PerformanceProfile::Balanced;
assert!(speed.to_config().enable_simd);
assert!(memory.to_config().memory_limit < 100 * 1024 * 1024);
assert!(balanced.to_config().validate_output);
}
}