pub mod capabilities;
pub mod config;
pub mod group;
pub mod light;
pub mod resourcelink;
pub mod rule;
pub mod scene;
pub mod schedule;
pub mod sensor;
pub use capabilities::Capabilities;
pub use config::Config;
pub use group::Group;
pub use light::Light;
pub use resourcelink::Resourcelink;
pub use rule::Rule;
pub use scene::Scene;
pub use schedule::Schedule;
pub use sensor::Sensor;
use crate::{response::Modified, Bridge, Error, Response};
use chrono::NaiveDateTime;
use serde::{de, de::Error as _, Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::fmt;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Alert {
Select,
LSelect,
None,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Effect {
Colorloop,
None,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize)]
pub enum ColorMode {
#[serde(rename = "ct")]
ColorTemperature,
#[serde(rename = "hs")]
HueAndSaturation,
#[serde(rename = "xy")]
ColorSpaceCoordinates,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Scan {
pub last_scan: LastScan,
pub resources: Vec<ScanResource>,
}
impl<'de> Deserialize<'de> for Scan {
fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
enum Field {
LastScan,
ResourceId(String),
}
impl<'de> Deserialize<'de> for Field {
fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value: String = Deserialize::deserialize(deserializer)?;
Ok(match value.as_ref() {
"lastscan" => Field::LastScan,
v => Field::ResourceId(v.to_owned()),
})
}
}
struct ScanVisitor;
impl<'de> de::Visitor<'de> for ScanVisitor {
type Value = Scan;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("struct Scan")
}
fn visit_map<V: de::MapAccess<'de>>(self, mut map: V) -> Result<Scan, V::Error> {
#[derive(Deserialize)]
struct ResourceInfo {
name: String,
};
let mut resources = Vec::new();
let mut last_scan = None;
while let Some(key) = map.next_key()? {
match key {
Field::LastScan => {
last_scan = serde_json::from_value(map.next_value()?)
.map_err(V::Error::custom)?
}
Field::ResourceId(v) => {
let info: ResourceInfo = map.next_value()?;
let resource = ScanResource {
id: v,
name: info.name,
};
resources.push(resource);
}
}
}
let last_scan = last_scan.ok_or_else(|| de::Error::missing_field("lastscan"))?;
Ok(Scan {
resources,
last_scan,
})
}
}
const FIELDS: &[&str] = &["lastscan", "resources"];
deserializer.deserialize_struct("Scan", FIELDS, ScanVisitor)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum LastScan {
DateTime(NaiveDateTime),
Active,
None,
}
impl<'de> Deserialize<'de> for LastScan {
fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value: String = Deserialize::deserialize(deserializer)?;
Ok(match value.as_ref() {
"active" => LastScan::Active,
"none" => LastScan::None,
v => LastScan::DateTime(
NaiveDateTime::parse_from_str(v, "%Y-%m-%dT%H:%M:%S").map_err(D::Error::custom)?,
),
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ScanResource {
pub id: String,
pub name: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Adjust<T> {
Override(T),
Increment(T),
Decrement(T),
}
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RequestMethod {
Put,
Post,
Get,
Delete,
}
pub trait Resource {}
pub trait Creator: Serialize {
fn url_suffix() -> String;
fn execute(&self, bridge: &Bridge) -> crate::Result<String> {
#[derive(Deserialize)]
struct CreationInfo {
id: String,
}
let mut response: Vec<Response<CreationInfo>> = bridge.api_request(
Self::url_suffix(),
RequestMethod::Post,
Some(serde_json::to_value(self)?),
)?;
match response.pop() {
Some(v) => Ok(v.into_result()?.id),
None => Err(Error::GetCreatedId),
}
}
}
pub trait Modifier: Serialize {
type Id;
fn url_suffix(id: Self::Id) -> String;
fn execute(&self, bridge: &Bridge, id: Self::Id) -> crate::Result<Vec<Response<Modified>>> {
bridge.api_request(
Self::url_suffix(id),
RequestMethod::Put,
Some(serde_json::to_value(self)?),
)
}
}
pub trait Scanner: Serialize {
fn url_suffix() -> String;
fn execute(&self, bridge: &Bridge) -> crate::Result<()> {
let responses: Vec<Response<JsonValue>> = bridge.api_request(
Self::url_suffix(),
RequestMethod::Post,
Some(serde_json::to_value(self)?),
)?;
for response in responses {
response.into_result()?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{NaiveDate, NaiveTime};
use serde_json::json;
#[test]
fn deserialize_last_scan() {
let json = json!("none");
let value: LastScan = serde_json::from_value(json).unwrap();
assert_eq!(value, LastScan::None);
let json = json!("active");
let value: LastScan = serde_json::from_value(json).unwrap();
assert_eq!(value, LastScan::Active);
let json = json!("2020-01-01T00:10:00");
let value: LastScan = serde_json::from_value(json).unwrap();
let date = NaiveDate::from_ymd(2020, 1, 1);
let time = NaiveTime::from_hms(0, 10, 0);
assert_eq!(value, LastScan::DateTime(NaiveDateTime::new(date, time)))
}
#[test]
fn deserialize_scan() {
let json = json!({
"lastscan": "active",
"1": {"name": "light one"},
"2": {"name": "light two"}
});
let value: Scan = serde_json::from_value(json).unwrap();
let scan = Scan {
last_scan: LastScan::Active,
resources: vec![
ScanResource {
id: "1".to_owned(),
name: "light one".to_owned(),
},
ScanResource {
id: "2".to_owned(),
name: "light two".to_owned(),
},
],
};
assert_eq!(value, scan);
}
}