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#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
9pub struct Schedule {
10 #[serde(skip)]
12 pub id: String,
13 pub name: String,
15 pub description: String,
17 pub command: Command,
19 #[serde(rename = "localtime")]
21 pub local_time: String,
22 #[serde(rename = "starttime")]
24 pub start_time: Option<NaiveDateTime>,
25 pub status: Status,
27 #[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#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
42pub struct Command {
43 pub address: String,
45 #[serde(rename = "method")]
47 pub request_method: CommandRequestMethod,
48 pub body: JsonValue,
50}
51
52impl Command {
53 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 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 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#[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#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
111#[serde(rename_all = "lowercase")]
112pub enum Status {
113 Enabled,
115 Disabled,
117}
118
119#[derive(Clone, Debug, Eq, PartialEq, Serialize, Setters)]
121#[setters(strip_option, prefix = "with_")]
122pub struct Creator {
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub name: Option<String>,
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub description: Option<String>,
129 #[setters(skip)]
131 pub command: Command,
132 #[serde(rename = "localtime")]
134 #[setters(skip)]
135 pub local_time: String,
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub status: Option<Status>,
139 #[serde(skip_serializing_if = "Option::is_none", rename = "autodelete")]
141 pub auto_delete: Option<bool>,
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub recycle: Option<bool>,
145}
146
147impl Creator {
148 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#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Setters)]
170#[setters(strip_option, prefix = "with_")]
171pub struct Modifier {
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub name: Option<String>,
175 #[serde(skip_serializing_if = "Option::is_none")]
177 pub description: Option<String>,
178 #[serde(skip_serializing_if = "Option::is_none")]
180 pub command: Option<Command>,
181 #[serde(skip_serializing_if = "Option::is_none", rename = "localtime")]
183 pub local_time: Option<String>,
184 #[serde(skip_serializing_if = "Option::is_none")]
186 pub status: Option<Status>,
187 #[serde(skip_serializing_if = "Option::is_none", rename = "autodelete")]
189 pub auto_delete: Option<bool>,
190}
191
192impl Modifier {
193 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}