huelib2/resource/
schedule.rs

1use crate::resource;
2use chrono::NaiveDateTime;
3use derive_setters::Setters;
4use serde::{Deserialize, Serialize};
5use serde_json::{Error as JsonError, Value as JsonValue};
6
7/// Schedule of a resource.
8#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
9pub struct Schedule {
10    /// Identifier of the schedule.
11    #[serde(skip)]
12    pub id: String,
13    /// Name of the schedule.
14    pub name: String,
15    /// Description of the schedule.
16    pub description: String,
17    /// Action to execute when the scheduled event occurs.
18    pub command: Command,
19    /// Time when the scheduled event will occur.
20    #[serde(rename = "localtime")]
21    pub local_time: String,
22    /// UTC time that the timer was started. Only provided for timers.
23    #[serde(rename = "starttime")]
24    pub start_time: Option<NaiveDateTime>,
25    /// Status of the schedule.
26    pub status: Status,
27    /// Whether the schedule will be removed after it expires.
28    #[serde(rename = "autodelete")]
29    pub auto_delete: Option<bool>,
30}
31
32impl Schedule {
33    pub(crate) fn with_id(self, id: String) -> Self {
34        Self { id, ..self }
35    }
36}
37
38impl resource::Resource for Schedule {}
39
40/// Command of a schedule.
41#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
42pub struct Command {
43    /// Address where the command will be executed.
44    pub address: String,
45    /// The HTTP method used to send the body to the given address.
46    #[serde(rename = "method")]
47    pub request_method: CommandRequestMethod,
48    /// Body of the request that the command sends.
49    pub body: JsonValue,
50}
51
52impl Command {
53    /// Creates a new command from a [`Creator`].
54    ///
55    /// [`Creator`]: resource::Creator
56    pub fn from_creator<C, S>(creator: &C, username: S) -> Result<Self, JsonError>
57    where
58        C: resource::Creator,
59        S: AsRef<str>,
60    {
61        Ok(Self {
62            address: format!("/api/{}/{}", username.as_ref(), C::url_suffix()),
63            request_method: CommandRequestMethod::Post,
64            body: serde_json::to_value(creator)?,
65        })
66    }
67
68    /// Creates a new command from a [`Modifier`].
69    ///
70    /// [`Modifier`]: resource::Modifier
71    pub fn from_modifier<M, S>(modifier: &M, id: M::Id, username: S) -> Result<Self, JsonError>
72    where
73        M: resource::Modifier,
74        S: AsRef<str>,
75    {
76        Ok(Self {
77            address: format!("/api/{}/{}", username.as_ref(), M::url_suffix(id)),
78            request_method: CommandRequestMethod::Put,
79            body: serde_json::to_value(modifier)?,
80        })
81    }
82
83    /// Creates a new command from a [`Scanner`].
84    ///
85    /// [`Scanner`]: resource::Scanner
86    pub fn from_scanner<T, S>(scanner: &T, username: S) -> Result<Self, JsonError>
87    where
88        T: resource::Scanner,
89        S: AsRef<str>,
90    {
91        Ok(Self {
92            address: format!("/api/{}/{}", username.as_ref(), T::url_suffix()),
93            request_method: CommandRequestMethod::Post,
94            body: serde_json::to_value(scanner)?,
95        })
96    }
97}
98
99/// Request method of an command.
100#[allow(missing_docs)]
101#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
102#[serde(rename_all = "UPPERCASE")]
103pub enum CommandRequestMethod {
104    Put,
105    Post,
106    Delete,
107}
108
109/// Status of a schedule.
110#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
111#[serde(rename_all = "lowercase")]
112pub enum Status {
113    /// The schedule is enabled.
114    Enabled,
115    /// The schedule is disabled.
116    Disabled,
117}
118
119/// Struct for creating a schedule.
120#[derive(Clone, Debug, Eq, PartialEq, Serialize, Setters)]
121#[setters(strip_option, prefix = "with_")]
122pub struct Creator {
123    /// Sets the name of the schedule.
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub name: Option<String>,
126    /// Sets the description of the schedule.
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub description: Option<String>,
129    /// Sets the command of the schedule.
130    #[setters(skip)]
131    pub command: Command,
132    /// Sets the local time of the schedule.
133    #[serde(rename = "localtime")]
134    #[setters(skip)]
135    pub local_time: String,
136    /// Sets the status of the schedule.
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub status: Option<Status>,
139    /// Sets whether the schedule will be removed after it expires.
140    #[serde(skip_serializing_if = "Option::is_none", rename = "autodelete")]
141    pub auto_delete: Option<bool>,
142    /// Sets whether resource is automatically deleted when not referenced anymore.
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub recycle: Option<bool>,
145}
146
147impl Creator {
148    /// Creates a new [`Creator`].
149    pub fn new(command: Command, local_time: String) -> Self {
150        Self {
151            name: None,
152            description: None,
153            command,
154            local_time,
155            status: None,
156            auto_delete: None,
157            recycle: None,
158        }
159    }
160}
161
162impl resource::Creator for Creator {
163    fn url_suffix() -> String {
164        "schedules".to_owned()
165    }
166}
167
168/// Struct for modifying attributes of a schedule.
169#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Setters)]
170#[setters(strip_option, prefix = "with_")]
171pub struct Modifier {
172    /// Sets the name of the schedule.
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub name: Option<String>,
175    /// Sets the description of the schedule.
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub description: Option<String>,
178    /// Sets the command of the schedule.
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub command: Option<Command>,
181    /// Sets the local time of the schedule.
182    #[serde(skip_serializing_if = "Option::is_none", rename = "localtime")]
183    pub local_time: Option<String>,
184    /// Sets the status of the schedule.
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub status: Option<Status>,
187    /// Sets whether the schedule is removed after it expires.
188    #[serde(skip_serializing_if = "Option::is_none", rename = "autodelete")]
189    pub auto_delete: Option<bool>,
190}
191
192impl Modifier {
193    /// Creates a new [`Modifier`].
194    pub fn new() -> Self {
195        Self::default()
196    }
197}
198
199impl resource::Modifier for Modifier {
200    type Id = String;
201    fn url_suffix(id: Self::Id) -> String {
202        format!("schedules/{}", id)
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use serde_json::json;
210
211    #[test]
212    fn serialize_command() {
213        let command = Command {
214            address: "/api/user/lights/1/state".into(),
215            request_method: CommandRequestMethod::Put,
216            body: json!({"on": true}),
217        };
218        let command_json = serde_json::to_value(command).unwrap();
219        let expected_json = json!({
220            "address": "/api/user/lights/1/state",
221            "method": "PUT",
222            "body": {
223                "on": true
224            }
225        });
226        assert_eq!(command_json, expected_json);
227
228        let creator = resource::group::Creator::new("test".into(), vec!["1".into()]);
229        let command = Command::from_creator(&creator, "user").unwrap();
230        let command_json = serde_json::to_value(command).unwrap();
231        let expected_json = json!({
232            "address": "/api/user/groups",
233            "method": "POST",
234            "body": {
235                "name": "test",
236                "lights": ["1"]
237            }
238        });
239        assert_eq!(command_json, expected_json);
240
241        let modifier = resource::light::StateModifier::new().with_on(true);
242        let command = Command::from_modifier(&modifier, "1".into(), "user").unwrap();
243        let command_json = serde_json::to_value(command).unwrap();
244        let expected_json = json!({
245            "address": "/api/user/lights/1/state",
246            "method": "PUT",
247            "body": {
248                "on": true
249            }
250        });
251        assert_eq!(command_json, expected_json);
252
253        let scanner = resource::light::Scanner::new();
254        let command = Command::from_scanner(&scanner, "user").unwrap();
255        let command_json = serde_json::to_value(command).unwrap();
256        let expected_json = json!({
257            "address": "/api/user/lights",
258            "method": "POST",
259            "body": {}
260        });
261        assert_eq!(command_json, expected_json);
262    }
263
264    #[test]
265    fn serialize_creator() {
266        let command = Command {
267            address: "/api/user/lights/1/state".into(),
268            request_method: CommandRequestMethod::Put,
269            body: json!({"on": true}),
270        };
271
272        let creator = Creator::new(command.clone(), "2020-01-01T00:00:00".into());
273        let creator_json = serde_json::to_value(creator).unwrap();
274        let expected_json = json!({
275            "command": {
276                "address": "/api/user/lights/1/state",
277                "method": "PUT",
278                "body": {
279                    "on": true
280                }
281            },
282            "localtime": "2020-01-01T00:00:00"
283        });
284        assert_eq!(creator_json, expected_json);
285
286        let creator = Creator {
287            name: Some("test".into()),
288            description: Some("description test".into()),
289            command,
290            local_time: "2020-01-01T00:00:00".into(),
291            status: Some(Status::Enabled),
292            auto_delete: Some(false),
293            recycle: Some(true),
294        };
295        let creator_json = serde_json::to_value(creator).unwrap();
296        let expected_json = json!({
297            "name": "test",
298            "description": "description test",
299            "command": {
300                "address": "/api/user/lights/1/state",
301                "method": "PUT",
302                "body": {
303                    "on": true
304                }
305            },
306            "localtime": "2020-01-01T00:00:00",
307            "status": "enabled",
308            "autodelete": false,
309            "recycle": true
310        });
311        assert_eq!(creator_json, expected_json);
312    }
313
314    #[test]
315    fn serialize_modifier() {
316        let modifier = Modifier::new();
317        let modifier_json = serde_json::to_value(modifier).unwrap();
318        let expected_json = json!({});
319        assert_eq!(modifier_json, expected_json);
320
321        let modifier = Modifier {
322            name: Some("test".into()),
323            description: Some("description test".into()),
324            command: Some(Command {
325                address: "/api/user/lights/1/state".into(),
326                request_method: CommandRequestMethod::Put,
327                body: json!({"on": true}),
328            }),
329            local_time: Some("2020-01-01T00:00:00".into()),
330            status: Some(Status::Disabled),
331            auto_delete: Some(true),
332        };
333        let modifier_json = serde_json::to_value(modifier).unwrap();
334        let expected_json = json!({
335            "name": "test",
336            "description": "description test",
337            "command": {
338                "address": "/api/user/lights/1/state",
339                "method": "PUT",
340                "body": {
341                    "on": true
342                }
343            },
344            "localtime": "2020-01-01T00:00:00",
345            "status": "disabled",
346            "autodelete": true
347        });
348        assert_eq!(modifier_json, expected_json);
349    }
350}