use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DriveMode {
Performance,
#[default]
Balanced,
Economy,
}
impl fmt::Display for DriveMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DriveMode::Performance => write!(f, "performance"),
DriveMode::Balanced => write!(f, "balanced"),
DriveMode::Economy => write!(f, "economy"),
}
}
}
impl FromStr for DriveMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"performance" | "perf" => Ok(DriveMode::Performance),
"balanced" | "bal" => Ok(DriveMode::Balanced),
"economy" | "eco" => Ok(DriveMode::Economy),
_ => Err(format!(
"unknown drive mode '{}': expected performance, balanced, or economy",
s
)),
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SvgMode {
#[default]
Raster,
Source,
Hybrid,
}
impl fmt::Display for SvgMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SvgMode::Raster => write!(f, "raster"),
SvgMode::Source => write!(f, "source"),
SvgMode::Hybrid => write!(f, "hybrid"),
}
}
}
impl FromStr for SvgMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"raster" => Ok(SvgMode::Raster),
"source" | "src" => Ok(SvgMode::Source),
"hybrid" => Ok(SvgMode::Hybrid),
_ => Err(format!(
"unknown svg mode '{}': expected raster, source, or hybrid",
s
)),
}
}
}
#[derive(Debug, Clone)]
pub struct SafetyLimits {
pub max_pixels: u64,
pub max_base64_bytes: usize,
pub max_download_bytes: usize,
pub max_images_extract: usize,
pub max_stdin_bytes: u64,
}
impl Default for SafetyLimits {
fn default() -> Self {
SafetyLimits {
max_pixels: 100_000_000,
max_base64_bytes: 30_000_000,
max_download_bytes: 25_000_000,
max_images_extract: 50,
max_stdin_bytes: 500_000_000,
}
}
}
#[derive(Debug, Clone)]
pub struct ShiftConfig {
pub mode: DriveMode,
pub svg_mode: SvgMode,
pub provider: String,
pub model: Option<String>,
pub dry_run: bool,
pub verbose: bool,
pub profile_path: Option<String>,
pub limits: SafetyLimits,
}
impl Default for ShiftConfig {
fn default() -> Self {
ShiftConfig {
mode: DriveMode::default(),
svg_mode: SvgMode::default(),
provider: "openai".to_string(),
model: None,
dry_run: false,
verbose: false,
profile_path: None,
limits: SafetyLimits::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_drive_mode_from_str() {
assert_eq!(
"performance".parse::<DriveMode>().unwrap(),
DriveMode::Performance
);
assert_eq!("perf".parse::<DriveMode>().unwrap(), DriveMode::Performance);
assert_eq!(
"balanced".parse::<DriveMode>().unwrap(),
DriveMode::Balanced
);
assert_eq!("bal".parse::<DriveMode>().unwrap(), DriveMode::Balanced);
assert_eq!("economy".parse::<DriveMode>().unwrap(), DriveMode::Economy);
assert_eq!("eco".parse::<DriveMode>().unwrap(), DriveMode::Economy);
assert!("invalid".parse::<DriveMode>().is_err());
}
#[test]
fn test_drive_mode_display() {
assert_eq!(DriveMode::Performance.to_string(), "performance");
assert_eq!(DriveMode::Balanced.to_string(), "balanced");
assert_eq!(DriveMode::Economy.to_string(), "economy");
}
#[test]
fn test_svg_mode_from_str() {
assert_eq!("raster".parse::<SvgMode>().unwrap(), SvgMode::Raster);
assert_eq!("source".parse::<SvgMode>().unwrap(), SvgMode::Source);
assert_eq!("src".parse::<SvgMode>().unwrap(), SvgMode::Source);
assert_eq!("hybrid".parse::<SvgMode>().unwrap(), SvgMode::Hybrid);
assert!("invalid".parse::<SvgMode>().is_err());
}
#[test]
fn test_default_config() {
let cfg = ShiftConfig::default();
assert_eq!(cfg.mode, DriveMode::Balanced);
assert_eq!(cfg.svg_mode, SvgMode::Raster);
assert_eq!(cfg.provider, "openai");
assert!(cfg.model.is_none());
assert!(!cfg.dry_run);
assert!(!cfg.verbose);
assert!(cfg.profile_path.is_none());
assert_eq!(cfg.limits.max_pixels, 100_000_000);
}
#[test]
fn test_default_safety_limits() {
let limits = SafetyLimits::default();
assert_eq!(limits.max_pixels, 100_000_000);
assert_eq!(limits.max_base64_bytes, 30_000_000);
assert_eq!(limits.max_download_bytes, 25_000_000);
assert_eq!(limits.max_images_extract, 50);
}
}