Skip to main content

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}