adaptive_pipeline_bootstrap/
cli.rs1pub mod parser;
35pub mod validator;
36
37pub use parser::{parse_cli, Cli, Commands};
38pub use validator::{ParseError, SecureArgParser};
39
40use std::path::PathBuf;
41
42#[derive(Debug, Clone)]
47pub struct ValidatedCli {
48 pub command: ValidatedCommand,
49 pub verbose: bool,
50 pub config: Option<PathBuf>,
51 pub cpu_threads: Option<usize>,
52 pub io_threads: Option<usize>,
53 pub storage_type: Option<String>,
54 pub channel_depth: usize,
55}
56
57#[derive(Debug, Clone)]
59pub enum ValidatedCommand {
60 Process {
61 input: PathBuf,
62 output: PathBuf,
63 pipeline: String,
64 chunk_size_mb: Option<usize>,
65 workers: Option<usize>,
66 },
67 Create {
68 name: String,
69 stages: String,
70 output: Option<PathBuf>,
71 },
72 List,
73 Show {
74 pipeline: String,
75 },
76 Delete {
77 pipeline: String,
78 force: bool,
79 },
80 Benchmark {
81 file: Option<PathBuf>,
82 size_mb: usize,
83 iterations: usize,
84 },
85 Validate {
86 config: PathBuf,
87 },
88 ValidateFile {
89 file: PathBuf,
90 full: bool,
91 },
92 Restore {
93 input: PathBuf,
94 output_dir: Option<PathBuf>,
95 mkdir: bool,
96 overwrite: bool,
97 },
98 Compare {
99 original: PathBuf,
100 adapipe: PathBuf,
101 detailed: bool,
102 },
103}
104
105pub fn parse_and_validate() -> Result<ValidatedCli, ParseError> {
121 let cli = parse_cli();
122 validate_cli(cli)
123}
124
125fn validate_cli(cli: Cli) -> Result<ValidatedCli, ParseError> {
136 let config = if let Some(ref path) = cli.config {
138 SecureArgParser::validate_argument(&path.to_string_lossy())?;
140 Some(path.clone())
141 } else {
142 None
143 };
144
145 if cli.channel_depth == 0 {
147 return Err(ParseError::InvalidValue {
148 arg: "channel-depth".to_string(),
149 reason: "must be greater than 0".to_string(),
150 });
151 }
152
153 if let Some(threads) = cli.cpu_threads {
155 if threads == 0 || threads > 128 {
156 return Err(ParseError::InvalidValue {
157 arg: "cpu-threads".to_string(),
158 reason: "must be between 1 and 128".to_string(),
159 });
160 }
161 }
162
163 if let Some(threads) = cli.io_threads {
165 if threads == 0 || threads > 256 {
166 return Err(ParseError::InvalidValue {
167 arg: "io-threads".to_string(),
168 reason: "must be between 1 and 256".to_string(),
169 });
170 }
171 }
172
173 let command = match cli.command {
175 Commands::Process {
176 input,
177 output,
178 pipeline,
179 chunk_size_mb,
180 workers,
181 } => {
182 let validated_input = SecureArgParser::validate_path(&input.to_string_lossy())?;
184
185 SecureArgParser::validate_argument(&output.to_string_lossy())?;
187
188 SecureArgParser::validate_argument(&pipeline)?;
190
191 if let Some(size) = chunk_size_mb {
193 if size == 0 || size > 1024 {
194 return Err(ParseError::InvalidValue {
195 arg: "chunk-size-mb".to_string(),
196 reason: "must be between 1 and 1024 MB".to_string(),
197 });
198 }
199 }
200
201 if let Some(w) = workers {
203 if w == 0 || w > 128 {
204 return Err(ParseError::InvalidValue {
205 arg: "workers".to_string(),
206 reason: "must be between 1 and 128".to_string(),
207 });
208 }
209 }
210
211 ValidatedCommand::Process {
212 input: validated_input,
213 output,
214 pipeline,
215 chunk_size_mb,
216 workers,
217 }
218 }
219 Commands::Create { name, stages, output } => {
220 SecureArgParser::validate_argument(&name)?;
221 SecureArgParser::validate_argument(&stages)?;
222
223 if let Some(ref path) = output {
224 SecureArgParser::validate_argument(&path.to_string_lossy())?;
225 }
226
227 ValidatedCommand::Create { name, stages, output }
228 }
229 Commands::List => ValidatedCommand::List,
230 Commands::Show { pipeline } => {
231 SecureArgParser::validate_argument(&pipeline)?;
232 ValidatedCommand::Show { pipeline }
233 }
234 Commands::Delete { pipeline, force } => {
235 SecureArgParser::validate_argument(&pipeline)?;
236 ValidatedCommand::Delete { pipeline, force }
237 }
238 Commands::Benchmark {
239 file,
240 size_mb,
241 iterations,
242 } => {
243 let validated_file = if let Some(ref path) = file {
244 Some(SecureArgParser::validate_path(&path.to_string_lossy())?)
245 } else {
246 None
247 };
248
249 if size_mb == 0 || size_mb > 100_000 {
250 return Err(ParseError::InvalidValue {
251 arg: "size-mb".to_string(),
252 reason: "must be between 1 and 100000 MB".to_string(),
253 });
254 }
255
256 if iterations == 0 || iterations > 1000 {
257 return Err(ParseError::InvalidValue {
258 arg: "iterations".to_string(),
259 reason: "must be between 1 and 1000".to_string(),
260 });
261 }
262
263 ValidatedCommand::Benchmark {
264 file: validated_file,
265 size_mb,
266 iterations,
267 }
268 }
269 Commands::Validate { config } => {
270 let validated_config = SecureArgParser::validate_path(&config.to_string_lossy())?;
271 ValidatedCommand::Validate {
272 config: validated_config,
273 }
274 }
275 Commands::ValidateFile { file, full } => {
276 let validated_file = SecureArgParser::validate_path(&file.to_string_lossy())?;
277 ValidatedCommand::ValidateFile {
278 file: validated_file,
279 full,
280 }
281 }
282 Commands::Restore {
283 input,
284 output_dir,
285 mkdir,
286 overwrite,
287 } => {
288 let validated_input = SecureArgParser::validate_path(&input.to_string_lossy())?;
289
290 let validated_output_dir = if let Some(ref path) = output_dir {
291 SecureArgParser::validate_argument(&path.to_string_lossy())?;
293 Some(path.clone())
294 } else {
295 None
296 };
297
298 ValidatedCommand::Restore {
299 input: validated_input,
300 output_dir: validated_output_dir,
301 mkdir,
302 overwrite,
303 }
304 }
305 Commands::Compare {
306 original,
307 adapipe,
308 detailed,
309 } => {
310 let validated_original = SecureArgParser::validate_path(&original.to_string_lossy())?;
311 let validated_adapipe = SecureArgParser::validate_path(&adapipe.to_string_lossy())?;
312 ValidatedCommand::Compare {
313 original: validated_original,
314 adapipe: validated_adapipe,
315 detailed,
316 }
317 }
318 };
319
320 Ok(ValidatedCli {
321 command,
322 verbose: cli.verbose,
323 config,
324 cpu_threads: cli.cpu_threads,
325 io_threads: cli.io_threads,
326 storage_type: cli.storage_type,
327 channel_depth: cli.channel_depth,
328 })
329}