switchbot_api/
command_request.rs

1use std::fmt::Display;
2
3/// A command request to send to the [SwitchBot API].
4///
5/// For more details of each field, please refer to the [SwitchBot
6/// documentation about device control commands][send-device-control-commands].
7///
8/// # Examples
9/// ```
10/// # use switchbot_api::CommandRequest;
11/// let command = CommandRequest {
12///     command: "turnOn".into(),
13///     ..Default::default()
14/// };
15/// ```
16///
17/// [SwitchBot API]: https://github.com/OpenWonderLabs/SwitchBotAPI
18/// [send-device-control-commands]: https://github.com/OpenWonderLabs/SwitchBotAPI/blob/main/README.md#send-device-control-commands
19#[derive(Clone, Debug, Default, PartialEq, serde::Serialize)]
20#[serde(rename_all = "camelCase")]
21pub struct CommandRequest {
22    /// The command.
23    pub command: String,
24
25    /// The command parameters.
26    #[serde(skip_serializing_if = "CommandRequest::can_omit_parameter")]
27    pub parameter: String,
28
29    /// The command type.
30    #[serde(skip_serializing_if = "CommandRequest::can_omit_command_type")]
31    pub command_type: String,
32}
33
34impl CommandRequest {
35    const DEFAULT_PARAMETER: &str = "default";
36    const DEFAULT_COMMAND_TYPE: &str = "command";
37
38    fn can_omit_parameter(str: &str) -> bool {
39        str.is_empty() || str == Self::DEFAULT_PARAMETER
40    }
41
42    fn can_omit_command_type(str: &str) -> bool {
43        str.is_empty() || str == Self::DEFAULT_COMMAND_TYPE
44    }
45}
46
47impl Display for CommandRequest {
48    /// Convert to a string by:
49    /// * Prepend `command_type` with a `/` (slash) if it's not empty nor default.
50    /// * Append `parameter` with a `:` (colon) if it's not empty nor default.
51    ///
52    /// This is the same form as what the [`from(&str)`][CommandRequest::from()] parses.
53    ///
54    /// [cli-command]: https://github.com/kojiishi/switchbot-rs/tree/main/cli#command
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        if !Self::can_omit_command_type(&self.command_type) {
57            write!(f, "{}/", self.command_type)?;
58        }
59        write!(f, "{}", self.command)?;
60        if !Self::can_omit_parameter(&self.parameter) {
61            write!(f, ":{}", self.parameter)?;
62        }
63        Ok(())
64    }
65}
66
67impl From<&str> for CommandRequest {
68    /// Parse a string into a [`CommandRequest`].
69    /// Please see the [`switchbot-cli` document][cli-command] for the syntax.
70    ///
71    /// [cli-command]: https://github.com/kojiishi/switchbot-rs/tree/main/cli#command
72    /// ```
73    /// # use switchbot_api::CommandRequest;
74    /// assert_eq!(
75    ///     CommandRequest::from("turnOn"),
76    ///     CommandRequest {
77    ///         command: "turnOn".into(),
78    ///         ..Default::default()
79    ///     }
80    /// );
81    /// assert_eq!(
82    ///     CommandRequest::from("turnOn:parameter:colon/slash"),
83    ///     CommandRequest {
84    ///         command: "turnOn".into(),
85    ///         parameter: "parameter:colon/slash".into(),
86    ///         ..Default::default()
87    ///     }
88    /// );
89    /// assert_eq!(
90    ///     CommandRequest::from("customize/turnOn"),
91    ///     CommandRequest {
92    ///         command: "turnOn".into(),
93    ///         command_type: "customize".into(),
94    ///         ..Default::default()
95    ///     }
96    /// );
97    /// assert_eq!(
98    ///     CommandRequest::from("customize/turnOn:parameter:colon/slash"),
99    ///     CommandRequest {
100    ///         command: "turnOn".into(),
101    ///         command_type: "customize".into(),
102    ///         parameter: "parameter:colon/slash".into(),
103    ///     }
104    /// );
105    /// ```
106    fn from(mut text: &str) -> Self {
107        let mut command = CommandRequest::default();
108        if let Some((name, parameter)) = text.split_once(':') {
109            command.parameter = parameter.into();
110            text = name;
111        }
112        if let Some((command_type, name)) = text.split_once('/') {
113            command.command_type = command_type.into();
114            text = name;
115        }
116        command.command = text.into();
117        command
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn serialize_all() {
127        let all = CommandRequest {
128            command: "test_command".into(),
129            parameter: "param".into(),
130            command_type: "type".into(),
131        };
132        assert_eq!(
133            serde_json::to_string(&all).unwrap(),
134            r#"{"command":"test_command","parameter":"param","commandType":"type"}"#
135        );
136    }
137
138    #[test]
139    fn serialize_default() {
140        let param_type_default = CommandRequest {
141            command: "test_command".into(),
142            ..Default::default()
143        };
144        assert_eq!(
145            serde_json::to_string(&param_type_default).unwrap(),
146            r#"{"command":"test_command"}"#
147        );
148    }
149
150    #[test]
151    fn serialize_default_str() {
152        let param_type_default = CommandRequest {
153            command: "test_command".into(),
154            parameter: CommandRequest::DEFAULT_PARAMETER.into(),
155            command_type: CommandRequest::DEFAULT_COMMAND_TYPE.into(),
156        };
157        assert_eq!(
158            serde_json::to_string(&param_type_default).unwrap(),
159            r#"{"command":"test_command"}"#
160        );
161    }
162
163    #[test]
164    fn serialize_empty() {
165        let param_type_empty = CommandRequest {
166            command: "test_command".into(),
167            parameter: String::default(),
168            command_type: String::default(),
169        };
170        assert_eq!(
171            serde_json::to_string(&param_type_empty).unwrap(),
172            r#"{"command":"test_command"}"#
173        );
174    }
175
176    #[test]
177    fn serialize_param() {
178        let with_param = CommandRequest {
179            command: "test_command".into(),
180            parameter: "param".into(),
181            ..Default::default()
182        };
183        assert_eq!(
184            serde_json::to_string(&with_param).unwrap(),
185            r#"{"command":"test_command","parameter":"param"}"#
186        );
187    }
188
189    #[test]
190    fn serialize_type() {
191        let with_type = CommandRequest {
192            command: "test_command".into(),
193            command_type: "type".into(),
194            ..Default::default()
195        };
196        assert_eq!(
197            serde_json::to_string(&with_type).unwrap(),
198            r#"{"command":"test_command","commandType":"type"}"#
199        );
200    }
201}