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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
use crate::resource::{self, light};
use crate::util;
use derive_setters::Setters;
use serde::{Deserialize, Serialize};
use serde_repr::Deserialize_repr;
use std::collections::HashMap;

/// A scene.
#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
pub struct Scene {
    /// Identifier of the scene.
    #[serde(skip_deserializing)]
    pub id: String,
    /// Name of the scene.
    pub name: String,
    /// Kind of the scene.
    #[serde(rename = "type")]
    pub kind: Kind,
    /// Identifier of the group that the scene is linked to.
    pub group: Option<String>,
    /// Identifier of the lights that are in this scene.
    pub lights: Option<Vec<String>>,
    /// Whitelist user that created or modified the content of the scene.
    #[serde(deserialize_with = "util::deserialize_option_string")]
    pub owner: Option<String>,
    /// Whether the group is automatically deleted when not referenced anymore.
    pub recycle: bool,
    /// Whether the scene is locked by a rule or a schedule.
    ///
    /// If set to true, the scene cannot be deleted until all resources requiring or that reference
    /// the scene are deleted.
    pub locked: bool,
    /// App specific data linked to the scene.
    #[serde(rename = "appdata")]
    pub app_data: AppData,
    /// Only available with an individual scene resource.
    ///
    /// Reserved by the Philips Hue API for future use.
    pub picture: Option<String>,
    /// Time the scene has been created or updated.
    ///
    /// Not available for legacy scenes.
    #[serde(rename = "lastupdate")]
    pub last_update: Option<chrono::NaiveDateTime>,
    /// Version of the scene document.
    pub version: Version,
}

impl Scene {
    pub(crate) fn with_id(self, id: String) -> Self {
        Self { id, ..self }
    }
}

impl resource::Resource for Scene {}

/// Kind of a scene.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
pub enum Kind {
    /// Represents a scene with lights.
    LightScene,
    /// Represents a scene which links to a specific group.
    GroupScene,
}

/// Version of a scene document.
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
pub struct AppData {
    /// App specific version of the data field.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub version: Option<i8>,
    /// App specific data. Free format string.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<String>,
}

/// Version of a scene document.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Deserialize_repr)]
#[repr(i32)]
pub enum Version {
    /// Scene was created with a PUT request.
    Put = 1,
    /// Scene was created with a POST request.
    Post = 2,
}

/// Struct for creating a scene.
#[derive(Clone, Debug, PartialEq, Serialize, Setters)]
#[setters(strip_option, prefix = "with_")]
pub struct Creator {
    /// Sets the name of the scene.
    #[setters(skip)]
    pub name: String,
    /// Sets the light identifiers of the scene.
    #[setters(skip)]
    pub lights: Vec<String>,
    /// Sets the type of the scene.
    #[serde(skip_serializing_if = "Option::is_none", rename = "type")]
    pub kind: Option<Kind>,
    /// Sets the app data of the scene.
    #[serde(skip_serializing_if = "Option::is_none", rename = "appdata")]
    pub app_data: Option<AppData>,
    /// Sets the state of specific lights.
    #[serde(skip_serializing_if = "Option::is_none", rename = "lightstates")]
    pub light_states: Option<HashMap<String, light::StaticStateModifier>>,
}

impl Creator {
    /// Creates a new [`Creator`].
    pub fn new(name: String, lights: Vec<String>) -> Self {
        Self {
            name,
            lights,
            kind: None,
            app_data: None,
            light_states: None,
        }
    }
}

impl resource::Creator for Creator {
    fn url_suffix() -> String {
        "scenes".to_owned()
    }
}

/// Struct for modifying a scene.
#[derive(Clone, Debug, Default, PartialEq, Serialize, Setters)]
#[setters(strip_option, prefix = "with_")]
pub struct Modifier {
    /// Sets the name of the scene.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    /// Sets the indentifiers of the lights that are in this scene.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub lights: Option<Vec<String>>,
    /// Sets the state of specific lights.
    ///
    /// The keys of the HashMap are the light identifiers.
    #[serde(skip_serializing_if = "Option::is_none", rename = "lightstates")]
    pub light_states: Option<HashMap<String, light::StaticStateModifier>>,
    /// Sets whether the state of the lights will be overwritten by the current state of the lights.
    #[serde(skip_serializing_if = "Option::is_none", rename = "storelightstate")]
    pub store_light_state: Option<bool>,
}

impl Modifier {
    /// Creates a new [`Modifier`].
    pub fn new() -> Self {
        Self::default()
    }
}

impl resource::Modifier for Modifier {
    type Id = String;
    fn url_suffix(id: Self::Id) -> String {
        format!("scenes/{}", id)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn serialize_creator() {
        let creator = Creator::new("test".into(), vec!["1".into()]);
        let creator_json = serde_json::to_value(creator).unwrap();
        let expected_json = json!({
            "name": "test",
            "lights": ["1"]
        });
        assert_eq!(creator_json, expected_json);

        let creator = Creator {
            name: "test".into(),
            lights: vec!["1".into()],
            kind: Some(Kind::GroupScene),
            app_data: Some(AppData {
                version: Some(2),
                data: Some("data test".into()),
            }),
            light_states: Some(HashMap::new()),
        };
        let creator_json = serde_json::to_value(creator).unwrap();
        let expected_json = json!({
            "name": "test",
            "lights": ["1"],
            "type": "GroupScene",
            "appdata": {
                "version": 2,
                "data": "data test"
            },
            "lightstates": {}
        });
        assert_eq!(creator_json, expected_json);
    }

    #[test]
    fn serialize_modifier() {
        let modifier = Modifier::new();
        let modifier_json = serde_json::to_value(modifier).unwrap();
        let expected_json = json!({});
        assert_eq!(modifier_json, expected_json);

        let modifier = Modifier {
            name: Some("test".into()),
            lights: Some(vec!["1".into(), "2".into()]),
            light_states: Some(HashMap::new()),
            store_light_state: Some(true),
        };
        let modifier_json = serde_json::to_value(modifier).unwrap();
        let expected_json = json!({
            "name": "test",
            "lights": ["1", "2"],
            "lightstates": {},
            "storelightstate": true
        });
        assert_eq!(modifier_json, expected_json);
    }
}