summaryrefslogtreecommitdiffstats
path: root/crates/core/tedge/src/command.rs
blob: 759afa4fe9c6b5859dcd74572293f6789e581a50 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/// A trait to be implemented by all tedge sub-commands.
///
/// A command encapsulates all the required parameters and provides an `execute()` method
/// to trigger the execution, plus a `description` of what is done by that command.
///
/// ```
/// struct SayHello {
///     name: String,
/// };
///
/// impl Command for SayHello {
///     fn description(&self) -> String {
///        format!("say hello to '{}'", name),
///     }
///
///     fn execute(&self) -> anyhow::Result<()> {
///        println!("Hello {}!", name};
///        Ok(())
///     }
/// }
/// ```
///
/// If a command needs some context, say the tedge configuration,
/// this context can be provided to the command struct.
///
/// ```
/// struct GetConfigKey {
///     config: TEdgeConfig,
///     key: String,
/// };
///
/// impl Command for GetConfigKey {
///     fn description(&self) -> String {
///        format!("get the value of the configuration key '{}'", self.key),
///     }
///
///     fn execute(&self) -> anyhow::Result<()> {
///        match self.config.get_config_value(self.key)? {
///             Some(value) => println!("{}", value),
///             None => eprintln!("The configuration key `{}` is not set", self.key),
///        }
///        Ok(())
///     }
/// }
/// ```
///
/// No specific support is provided to a command that needs to update the configuration.
/// The simplest is to acquire a mutable config using `TEdgeConfig::from_default_config()`.
///
/// ```
/// struct SetConfigKey {
///     key: String,
///     value: String,
/// };
///
/// impl Command for SetConfigKey {
///     fn description(&self) -> String {
///        format!("set the value of the configuration key '{}' to '{}'", self.key, self.value),
///     }
///
///     fn execute(&self) -> anyhow::Result<()> {
///        let mut config = TEdgeConfig::from_default_config()?;
///        config.set_config_value(self.key, self.value)?;
///        let _ = config.write_to_default_config()?;
///        Ok(())
///     }
/// }
/// ```
pub trait Command {
    /// Display that command to the user, telling what will be done.
    ///
    /// This description is displayed to the end user in case of an error, to give the context of that error.
    fn description(&self) -> String;

    /// Execute this command.
    ///
    /// The errors returned by this method must be concrete `anyhow::Error` values.
    /// The simplest way to implement a specific `anyhow::Error` type is to derive the `thiserror::Error`.
    /// Doing so, the command specific error type implements `Into<anyhow::Error>`
    /// and such errors can then be returned with no explicit conversion from the `run()` method.
    ///
    /// ```
    /// #[derive(thiserror::Error, Debug)]
    /// pub enum ConfigError {
    ///     #[error("Not a Thin Edge property: {key:?}")]
    ///     UnknownKey{key: String},
    /// }
    /// ```
    fn execute(&self) -> anyhow::Result<()>;

    /// Helper method to be used in the `BuildCommand` trait.
    ///
    /// The `BuildCommand::build_command()` method has to return a box around a new command.
    ///
    /// ```
    /// fn build_command(self, config: TEdgeConfig) -> Result<Box<dyn Command>, ConfigError> {
    ///     let cmd = GetConfigKey { config, key };
    ///     Ok(cmd.into_boxed())
    /// }
    /// ```
    fn into_boxed(self) -> Box<dyn Command>
    where
        Self: Sized + 'static,
    {
        Box::new(self)
    }
}

/// A trait implemented by the tedge subcommands to build the actual command
/// using a combination of the parameters provided on the command line
/// and those from the configuration.
///
/// In practice, an implementation will also derives the `Structopt` trait.
///
/// ```
/// #[derive(clap::Parser, Debug)]
/// enum ConfigCmd {
///     /// Add new value (overwrite the value if the key exists).
///     Set { key: String, value: String },
///
///     /// Get value.
///     Get { key: String },
/// }
///
/// impl BuildCommand for ConfigCmd {
///     fn build_command(self, context: BuildContext) -> Result<Box<dyn Command>, ConfigError> {
///        let cmd = match self {
///            ConfigCmd::Set { key, value } => SetConfigKey {
///                config: context.config,
///                key,
///                value,
///            },
///            ConfigCmd::Get { key } => GetConfigKey {
///                config: context.config,
///                key,
///            },
///        }
///        Ok(cmd.into_boxed())
///     }
/// }
/// ```
pub trait BuildCommand {
    fn build_command(self, context: BuildContext) -> Result<Box<dyn Command>, crate::ConfigError>;
}

/// The context for `BuildCommand`
///
pub struct BuildContext {
    pub config_repository: tedge_config::TEdgeConfigRepository,
    pub config_location: tedge_config::TEdgeConfigLocation,
}