mithril_client_cli/commands/
deprecation.rs

1use clap::{
2    builder::{StyledStr, Styles},
3    error::{ContextKind, ContextValue},
4};
5
6use crate::ClapError;
7
8/// Stores the deprecated command name and the new command name to use.
9#[derive(Clone)]
10pub struct DeprecatedCommand {
11    command: String,
12    new_command: String,
13}
14
15impl DeprecatedCommand {
16    /// Create information about a deprecated command
17    pub fn new<S: ToString>(command: S, new_command: S) -> Self {
18        Self {
19            command: command.to_string(),
20            new_command: new_command.to_string(),
21        }
22    }
23}
24
25/// Tool to handle deprecated Clap commands.
26pub struct Deprecation;
27
28impl Deprecation {
29    fn find_deprecated_command(
30        error: &ClapError,
31        deprecated_commands: Vec<DeprecatedCommand>,
32    ) -> Option<DeprecatedCommand> {
33        if let Some(context_value) = error.get(ContextKind::InvalidSubcommand) {
34            let command_name = context_value.to_string();
35            deprecated_commands
36                .into_iter()
37                .find(|dc| command_name == dc.command)
38        } else {
39            None
40        }
41    }
42
43    /// Modify result to add information on deprecated commands.
44    pub fn handle_deprecated_commands<A>(
45        matches_result: Result<A, ClapError>,
46        styles: Styles,
47        deprecated_commands: Vec<DeprecatedCommand>,
48    ) -> Result<A, ClapError> {
49        matches_result.map_err(|mut e: ClapError| {
50            if let Some(deprecated_command) = Self::find_deprecated_command(&e, deprecated_commands)
51            {
52                let message = format!(
53                    "'{}{}{}' command is deprecated, use '{}{}{}' command instead",
54                    styles.get_error().render(),
55                    deprecated_command.command,
56                    styles.get_error().render_reset(),
57                    styles.get_valid().render(),
58                    deprecated_command.new_command,
59                    styles.get_valid().render_reset(),
60                );
61                e.insert(
62                    ContextKind::Suggested,
63                    ContextValue::StyledStrs(vec![StyledStr::from(&message)]),
64                );
65            }
66            e
67        })
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use clap::error::{ContextKind, ContextValue, ErrorKind};
75    use clap::{CommandFactory, Parser, Subcommand};
76
77    #[derive(Parser, Debug, Clone)]
78    pub struct MyCommand {
79        #[clap(subcommand)]
80        command: MySubCommands,
81    }
82
83    #[derive(Subcommand, Debug, Clone)]
84    enum MySubCommands {}
85
86    #[test]
87    fn invalid_sub_command_message_for_a_non_deprecated_command_is_not_modified() {
88        fn build_error() -> Result<MyCommand, ClapError> {
89            let mut e =
90                ClapError::new(ErrorKind::InvalidSubcommand).with_cmd(&MyCommand::command());
91
92            e.insert(
93                ContextKind::InvalidSubcommand,
94                ContextValue::String("invalid_command".to_string()),
95            );
96
97            Err(e)
98        }
99
100        let default_error_message = build_error().err().unwrap().to_string();
101
102        let result = Deprecation::handle_deprecated_commands(
103            build_error(),
104            Styles::plain(),
105            vec![DeprecatedCommand::new("old_command", "new_command")],
106        );
107        assert!(result.is_err());
108        let message = result.err().unwrap().to_string();
109        assert_eq!(default_error_message, message);
110    }
111
112    #[test]
113    fn replace_error_message_on_deprecated_commands_and_show_the_new_command() {
114        let mut e = ClapError::new(ErrorKind::InvalidSubcommand).with_cmd(&MyCommand::command());
115        e.insert(
116            ContextKind::InvalidSubcommand,
117            ContextValue::String("old_command".to_string()),
118        );
119
120        let result = Deprecation::handle_deprecated_commands(
121            Err(e) as Result<MyCommand, ClapError>,
122            Styles::plain(),
123            vec![DeprecatedCommand::new("old_command", "new_command")],
124        );
125        assert!(result.is_err());
126        let message = result.err().unwrap().to_string();
127        assert!(message.contains("'old_command'"));
128        assert!(message.contains("deprecated"));
129        assert!(message.contains("'new_command'"));
130    }
131}