xdiff_live/cli.rs
1//! Command-line interface definitions for XDiff-NG tools.
2//!
3//! This module provides the command-line argument parsing structures and utilities
4//! for both `xdiff` and `xreq` tools using the `clap` crate. It defines the main
5//! command structure, subcommands, and parameter parsing logic.
6
7// clap是一个简单易用,功能强大的命令行参数解析库。
8// clap允许多种方式指定我们的命令行。支持常规的Rust方法调用、宏或者YAML配置。
9use anyhow::{anyhow, Result};
10use clap::{Parser, Subcommand};
11
12use crate::ExtraArgs;
13
14/// Main command-line arguments structure for XDiff-NG tools.
15///
16/// This structure defines the top-level CLI interface that accepts subcommands
17/// for different operations like running comparisons or parsing URLs.
18///
19/// # Examples
20///
21/// ```
22/// use clap::Parser;
23/// use xdiff_live::cli::Args;
24///
25/// // Parse arguments from command line
26/// let args = Args::parse();
27/// ```
28#[derive(Parser, Debug, Clone)]
29#[clap(version, author, about, long_about = None)]
30pub struct Args {
31 /// The subcommand to execute
32 #[clap(subcommand)]
33 pub action: Action,
34 // 属性可以与实现子命令的结构字段或枚举变体一起使用
35 // 使用 #[clap(subcommand)] 属性宏可以将一个结构体或枚举类型标记为一个 CLI 的子命令。
36}
37
38/// Available subcommands for the CLI application.
39///
40/// This enum defines all the possible actions that can be performed by the
41/// XDiff-NG tools. Currently supports running comparisons and parsing URLs.
42#[derive(Subcommand, Debug, Clone)]
43#[non_exhaustive]
44// non_exhaustive属性表示类型或变体将来可能会添加更多字段或变体。
45// 它可以应用在结构体(struct)上、枚举(enum)上和枚举变体上
46pub enum Action {
47 // #[clap(about = "Diff two http requests and compare the responses.")]
48 /// Diff two API responses based on given profile
49 Run(RunArgs),
50 /// Parse URls to generate a profile
51 Parse,
52}
53
54/// Arguments for the 'run' subcommand.
55///
56/// This structure contains all the parameters needed to execute a comparison
57/// or request using predefined profiles, including profile selection,
58/// configuration file path, and extra parameters for customization.
59///
60/// # Examples
61///
62/// ```bash
63/// # Using the run command with various options
64/// xdiff run -p myprofile -c config.yml -e param1=value1 -e @header1=value1
65/// ```
66#[derive(Parser, Debug, Clone)]
67pub struct RunArgs {
68 /// Profile Name
69 #[clap(short, long, value_parser)]
70 pub profile: String,
71
72 /// Overrides Args. Could be used to override the query, headers, and body of the request
73 /// for query params, use `-e key=value`
74 /// for headers, use `-e %key=value`
75 /// for body, use `-e @key=value`
76 #[clap(short, long, value_parser = parse_key_val, number_of_values = 1)]
77 pub extra_params: Vec<KeyVal>,
78
79 /// Configuration to use
80 #[clap(short, long, value_parser)]
81 pub config: Option<String>,
82}
83
84/// Types of key-value parameters that can be specified via command line.
85///
86/// This enum categorizes the different types of parameters that can be
87/// overridden or added through the `-e` command-line option.
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub enum KeyValType {
90 /// Query parameters (URL parameters)
91 Query,
92 /// HTTP headers
93 Header,
94 /// Request body parameters
95 Body,
96}
97
98/// A key-value pair with a specific type designation.
99///
100/// This structure represents a single parameter override that can be
101/// applied to HTTP requests. The type determines where the parameter
102/// will be applied (query, header, or body).
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct KeyVal {
105 /// The type of parameter (query, header, or body)
106 key_type: KeyValType,
107 /// The parameter name/key
108 key: String,
109 /// The parameter value
110 value: String,
111}
112
113/// Parses a key-value string into a `KeyVal` structure.
114///
115/// This function takes a string in the format `[prefix]key=value` and parses it
116/// into a structured `KeyVal` object. The prefix determines the parameter type:
117///
118/// - No prefix or alphabetic start: Query parameter
119/// - `%` prefix: Header parameter
120/// - `@` prefix: Body parameter
121///
122/// # Arguments
123///
124/// * `s` - The input string to parse (e.g., "key=value", "%header=value", "@body=value")
125///
126/// # Returns
127///
128/// A `Result<KeyVal>` containing the parsed key-value pair, or an error if
129/// the input format is invalid.
130///
131/// # Examples
132///
133/// ```
134/// use xdiff_live::cli::{parse_key_val, KeyValType};
135///
136/// // Parse a query parameter
137/// let kv = parse_key_val("search=rust").unwrap();
138/// assert_eq!(kv.key_type, KeyValType::Query);
139///
140/// // Parse a header parameter
141/// let kv = parse_key_val("%Authorization=Bearer token").unwrap();
142/// assert_eq!(kv.key_type, KeyValType::Header);
143///
144/// // Parse a body parameter
145/// let kv = parse_key_val("@username=john").unwrap();
146/// assert_eq!(kv.key_type, KeyValType::Body);
147/// ```
148///
149/// # Errors
150///
151/// Returns an error if:
152/// - The input doesn't contain an `=` sign
153/// - The key starts with an invalid character
154/// - Either key or value is missing
155pub fn parse_key_val(s: &str) -> Result<KeyVal> {
156 let mut parts = s.splitn(2, "=");
157
158 let key = parts
159 .next()
160 .ok_or_else(|| anyhow!("Invalid key value pair: {}", s))?
161 .trim();
162 let value = parts
163 .next()
164 .ok_or_else(|| anyhow!("Invalid key value pair: {}", s))?
165 .trim();
166
167 let (key_type, key) = match key.chars().next() {
168 Some('%') => (KeyValType::Header, &key[1..]),
169 Some('@') => (KeyValType::Body, &key[1..]),
170 Some(v) if v.is_ascii_alphabetic() => (KeyValType::Query, key),
171 _ => return Err(anyhow!("Invalid key value pair")),
172 };
173
174 Ok(KeyVal {
175 key_type,
176 key: key.to_string(),
177 value: value.to_string(),
178 })
179}
180
181impl From<Vec<KeyVal>> for ExtraArgs {
182 /// Converts a vector of `KeyVal` items into an `ExtraArgs` structure.
183 ///
184 /// This implementation groups the key-value pairs by their type and creates
185 /// separate vectors for headers, query parameters, and body parameters.
186 ///
187 /// # Arguments
188 ///
189 /// * `args` - A vector of `KeyVal` items to be converted
190 ///
191 /// # Returns
192 ///
193 /// An `ExtraArgs` structure with the key-value pairs organized by type.
194 ///
195 /// # Examples
196 ///
197 /// ```
198 /// use xdiff_live::cli::{KeyVal, KeyValType};
199 /// use xdiff_live::ExtraArgs;
200 ///
201 /// let kv_pairs = vec![
202 /// KeyVal { /* query param */ },
203 /// KeyVal { /* header param */ },
204 /// KeyVal { /* body param */ },
205 /// ];
206 ///
207 /// let extra_args: ExtraArgs = kv_pairs.into();
208 /// ```
209 fn from(args: Vec<KeyVal>) -> Self {
210 let mut headers = vec![];
211 let mut query = vec![];
212 let mut body = vec![];
213
214 for arg in args {
215 match arg.key_type {
216 KeyValType::Header => headers.push((arg.key, arg.value)),
217 KeyValType::Query => query.push((arg.key, arg.value)),
218 KeyValType::Body => body.push((arg.key, arg.value)),
219 }
220 }
221
222 Self {
223 headers,
224 query,
225 body,
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use std::vec;
233
234 use super::*;
235
236 /// Tests the conversion from `Vec<KeyVal>` to `ExtraArgs`.
237 ///
238 /// This test verifies that key-value pairs are correctly grouped by type
239 /// when converting to the `ExtraArgs` structure.
240 #[test]
241 fn from_vec_key_val_for_extra_args_should_work() {
242 let args = vec![
243 KeyVal {
244 key_type: KeyValType::Header,
245 key: "key1".to_string(),
246 value: "value1".to_string(),
247 },
248 KeyVal {
249 key_type: KeyValType::Query,
250 key: "key2".to_string(),
251 value: "value2".to_string(),
252 },
253 KeyVal {
254 key_type: KeyValType::Body,
255 key: "key3".to_string(),
256 value: "value3".to_string(),
257 },
258 ];
259
260 let extra_args = ExtraArgs::from(args);
261
262 assert_eq!(
263 extra_args,
264 ExtraArgs {
265 headers: vec![("key1".to_string(), "value1".to_string())],
266 query: vec![("key2".to_string(), "value2".to_string())],
267 body: vec![("key3".to_string(), "value3".to_string())],
268 }
269 );
270 }
271
272 /// Tests the `parse_key_val` function with various input formats.
273 ///
274 /// This test verifies that the parser correctly identifies and categorizes
275 /// different types of key-value pairs based on their prefix characters.
276 #[test]
277 fn parse_key_val_should_work() {
278 let args = vec![
279 "%key1=value1",
280 "key2=value2",
281 "@key3=value3",
282 "key4=value4",
283 ];
284
285 let key_vals = args
286 .into_iter()
287 .map(|arg| parse_key_val(arg))
288 .collect::<Result<Vec<_>>>()
289 .unwrap();
290
291 assert_eq!(
292 key_vals,
293 vec![
294 KeyVal {
295 key_type: KeyValType::Header,
296 key: "key1".to_string(),
297 value: "value1".to_string(),
298 },
299 KeyVal {
300 key_type: KeyValType::Query,
301 key: "key2".to_string(),
302 value: "value2".to_string(),
303 },
304 KeyVal {
305 key_type: KeyValType::Body,
306 key: "key3".to_string(),
307 value: "value3".to_string(),
308 },
309 KeyVal {
310 key_type: KeyValType::Query,
311 key: "key4".to_string(),
312 value: "value4".to_string(),
313 },
314 ]
315
316 );
317 }
318}