mithril_client_cli/commands/
deprecation.rs1use clap::{
2 builder::{StyledStr, Styles},
3 error::{ContextKind, ContextValue},
4};
5
6use crate::ClapError;
7
8#[derive(Clone)]
10pub struct DeprecatedCommand {
11 command: String,
12 new_command: String,
13}
14
15impl DeprecatedCommand {
16 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
25pub 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 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}