1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::str::FromStr;
4
5#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum DriveMode {
13 Performance,
14 #[default]
15 Balanced,
16 Economy,
17}
18
19impl fmt::Display for DriveMode {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 DriveMode::Performance => write!(f, "performance"),
23 DriveMode::Balanced => write!(f, "balanced"),
24 DriveMode::Economy => write!(f, "economy"),
25 }
26 }
27}
28
29impl FromStr for DriveMode {
30 type Err = String;
31
32 fn from_str(s: &str) -> Result<Self, Self::Err> {
33 match s.to_lowercase().as_str() {
34 "performance" | "perf" => Ok(DriveMode::Performance),
35 "balanced" | "bal" => Ok(DriveMode::Balanced),
36 "economy" | "eco" => Ok(DriveMode::Economy),
37 _ => Err(format!(
38 "unknown drive mode '{}': expected performance, balanced, or economy",
39 s
40 )),
41 }
42 }
43}
44
45#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51#[serde(rename_all = "lowercase")]
52pub enum SvgMode {
53 #[default]
54 Raster,
55 Source,
56 Hybrid,
57}
58
59impl fmt::Display for SvgMode {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 SvgMode::Raster => write!(f, "raster"),
63 SvgMode::Source => write!(f, "source"),
64 SvgMode::Hybrid => write!(f, "hybrid"),
65 }
66 }
67}
68
69impl FromStr for SvgMode {
70 type Err = String;
71
72 fn from_str(s: &str) -> Result<Self, Self::Err> {
73 match s.to_lowercase().as_str() {
74 "raster" => Ok(SvgMode::Raster),
75 "source" | "src" => Ok(SvgMode::Source),
76 "hybrid" => Ok(SvgMode::Hybrid),
77 _ => Err(format!(
78 "unknown svg mode '{}': expected raster, source, or hybrid",
79 s
80 )),
81 }
82 }
83}
84
85#[derive(Debug, Clone)]
87pub struct SafetyLimits {
88 pub max_pixels: u64,
91 pub max_base64_bytes: usize,
94 pub max_download_bytes: usize,
97 pub max_images_extract: usize,
100 pub max_stdin_bytes: u64,
103}
104
105impl Default for SafetyLimits {
106 fn default() -> Self {
107 SafetyLimits {
108 max_pixels: 100_000_000,
109 max_base64_bytes: 30_000_000,
110 max_download_bytes: 25_000_000,
111 max_images_extract: 50,
112 max_stdin_bytes: 500_000_000,
113 }
114 }
115}
116
117#[derive(Debug, Clone)]
119pub struct ShiftConfig {
120 pub mode: DriveMode,
121 pub svg_mode: SvgMode,
122 pub provider: String,
123 pub model: Option<String>,
124 pub dry_run: bool,
125 pub verbose: bool,
126 pub profile_path: Option<String>,
128 pub limits: SafetyLimits,
131}
132
133impl Default for ShiftConfig {
134 fn default() -> Self {
135 ShiftConfig {
136 mode: DriveMode::default(),
137 svg_mode: SvgMode::default(),
138 provider: "openai".to_string(),
139 model: None,
140 dry_run: false,
141 verbose: false,
142 profile_path: None,
143 limits: SafetyLimits::default(),
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_drive_mode_from_str() {
154 assert_eq!(
155 "performance".parse::<DriveMode>().unwrap(),
156 DriveMode::Performance
157 );
158 assert_eq!("perf".parse::<DriveMode>().unwrap(), DriveMode::Performance);
159 assert_eq!(
160 "balanced".parse::<DriveMode>().unwrap(),
161 DriveMode::Balanced
162 );
163 assert_eq!("bal".parse::<DriveMode>().unwrap(), DriveMode::Balanced);
164 assert_eq!("economy".parse::<DriveMode>().unwrap(), DriveMode::Economy);
165 assert_eq!("eco".parse::<DriveMode>().unwrap(), DriveMode::Economy);
166 assert!("invalid".parse::<DriveMode>().is_err());
167 }
168
169 #[test]
170 fn test_drive_mode_display() {
171 assert_eq!(DriveMode::Performance.to_string(), "performance");
172 assert_eq!(DriveMode::Balanced.to_string(), "balanced");
173 assert_eq!(DriveMode::Economy.to_string(), "economy");
174 }
175
176 #[test]
177 fn test_svg_mode_from_str() {
178 assert_eq!("raster".parse::<SvgMode>().unwrap(), SvgMode::Raster);
179 assert_eq!("source".parse::<SvgMode>().unwrap(), SvgMode::Source);
180 assert_eq!("src".parse::<SvgMode>().unwrap(), SvgMode::Source);
181 assert_eq!("hybrid".parse::<SvgMode>().unwrap(), SvgMode::Hybrid);
182 assert!("invalid".parse::<SvgMode>().is_err());
183 }
184
185 #[test]
186 fn test_default_config() {
187 let cfg = ShiftConfig::default();
188 assert_eq!(cfg.mode, DriveMode::Balanced);
189 assert_eq!(cfg.svg_mode, SvgMode::Raster);
190 assert_eq!(cfg.provider, "openai");
191 assert!(cfg.model.is_none());
192 assert!(!cfg.dry_run);
193 assert!(!cfg.verbose);
194 assert!(cfg.profile_path.is_none());
195 assert_eq!(cfg.limits.max_pixels, 100_000_000);
196 }
197
198 #[test]
199 fn test_default_safety_limits() {
200 let limits = SafetyLimits::default();
201 assert_eq!(limits.max_pixels, 100_000_000);
202 assert_eq!(limits.max_base64_bytes, 30_000_000);
203 assert_eq!(limits.max_download_bytes, 25_000_000);
204 assert_eq!(limits.max_images_extract, 50);
205 }
206}