use crate::{resource, util};
use chrono::NaiveDateTime;
use derive_setters::Setters;
use serde::{Deserialize, Serialize};
use serde_json::{Error as JsonError, Value as JsonValue};
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
pub struct Rule {
#[serde(skip)]
pub id: String,
pub name: String,
#[serde(deserialize_with = "util::deserialize_option_string")]
pub owner: Option<String>,
#[serde(
rename = "lasttriggered",
deserialize_with = "util::deserialize_option_date_time"
)]
pub last_triggered: Option<NaiveDateTime>,
#[serde(rename = "timestriggered")]
pub times_triggered: usize,
pub created: NaiveDateTime,
pub status: Status,
pub conditions: Vec<Condition>,
pub actions: Vec<Action>,
}
impl Rule {
pub(crate) fn with_id(self, id: String) -> Self {
Self { id, ..self }
}
}
impl resource::Resource for Rule {}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Status {
Enabled,
Disabled,
ResourceDeleted,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
pub struct Condition {
pub address: String,
pub operator: ConditionOperator,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
pub enum ConditionOperator {
#[serde(rename = "lt")]
LessThan,
#[serde(rename = "gt")]
GreaterThan,
#[serde(rename = "eq")]
Equals,
#[serde(rename = "dx")]
Dx,
#[serde(rename = "ddx")]
Ddx,
#[serde(rename = "stable")]
Stable,
#[serde(rename = "not stable")]
NotStable,
#[serde(rename = "in")]
In,
#[serde(rename = "not in")]
NotIn,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct Action {
pub address: String,
#[serde(rename = "method")]
pub request_method: ActionRequestMethod,
pub body: JsonValue,
}
impl Action {
pub fn from_creator<C>(creator: &C) -> Result<Self, JsonError>
where
C: resource::Creator,
{
Ok(Self {
address: format!("/{}", C::url_suffix()),
request_method: ActionRequestMethod::Post,
body: serde_json::to_value(creator)?,
})
}
pub fn from_modifier<M>(modifier: &M, id: M::Id) -> Result<Self, JsonError>
where
M: resource::Modifier,
{
Ok(Self {
address: format!("/{}", M::url_suffix(id)),
request_method: ActionRequestMethod::Put,
body: serde_json::to_value(modifier)?,
})
}
pub fn from_scanner<S>(scanner: &S) -> Result<Self, JsonError>
where
S: resource::Scanner,
{
Ok(Self {
address: format!("/{}", S::url_suffix()),
request_method: ActionRequestMethod::Post,
body: serde_json::to_value(scanner)?,
})
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum ActionRequestMethod {
Put,
Post,
Delete,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Setters)]
#[setters(strip_option, prefix = "with_")]
pub struct Creator {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<Status>,
#[setters(skip)]
pub conditions: Vec<Condition>,
#[setters(skip)]
pub actions: Vec<Action>,
}
impl Creator {
pub fn new(conditions: Vec<Condition>, actions: Vec<Action>) -> Self {
Self {
name: None,
status: None,
conditions,
actions,
}
}
}
impl resource::Creator for Creator {
fn url_suffix() -> String {
"rules".to_owned()
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Setters)]
#[setters(strip_option, prefix = "with_")]
pub struct Modifier {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<Status>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conditions: Option<Vec<Condition>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub actions: Option<Vec<Action>>,
}
impl Modifier {
pub fn new() -> Self {
Self::default()
}
}
impl resource::Modifier for Modifier {
type Id = String;
fn url_suffix(id: Self::Id) -> String {
format!("rules/{}", id)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn serialize_action() {
let action = Action {
address: "/lights/1/state".into(),
request_method: ActionRequestMethod::Put,
body: json!({"on": true}),
};
let action_json = serde_json::to_value(action).unwrap();
let expected_json = json!({
"address": "/lights/1/state",
"method": "PUT",
"body": {
"on": true
}
});
assert_eq!(action_json, expected_json);
let creator = resource::group::Creator::new("test".into(), vec!["1".into()]);
let action = Action::from_creator(&creator).unwrap();
let action_json = serde_json::to_value(action).unwrap();
let expected_json = json!({
"address": "/groups",
"method": "POST",
"body": {
"name": "test",
"lights": ["1"]
}
});
assert_eq!(action_json, expected_json);
let modifier = resource::light::StateModifier::new().with_on(true);
let action = Action::from_modifier(&modifier, "1".into()).unwrap();
let action_json = serde_json::to_value(action).unwrap();
let expected_json = json!({
"address": "/lights/1/state",
"method": "PUT",
"body": {
"on": true
}
});
assert_eq!(action_json, expected_json);
let scanner = resource::light::Scanner::new();
let action = Action::from_scanner(&scanner).unwrap();
let action_json = serde_json::to_value(action).unwrap();
let expected_json = json!({
"address": "/lights",
"method": "POST",
"body": {}
});
assert_eq!(action_json, expected_json);
}
#[test]
fn serialize_creator() {
let conditions = vec![Condition {
address: "/sensors/2/state/lastupdated".into(),
operator: ConditionOperator::Dx,
value: None,
}];
let actions = vec![Action {
address: "/lights/1/state".into(),
request_method: ActionRequestMethod::Put,
body: json!({}),
}];
let creator = Creator::new(conditions.clone(), actions.clone());
let creator_json = serde_json::to_value(creator).unwrap();
let expected_json = json!({
"conditions": [
{
"address": "/sensors/2/state/lastupdated",
"operator": "dx"
}
],
"actions": [
{
"address": "/lights/1/state",
"method": "PUT",
"body": {}
}
],
});
assert_eq!(creator_json, expected_json);
let creator = Creator {
name: Some("test".into()),
status: Some(Status::Enabled),
conditions,
actions,
};
let creator_json = serde_json::to_value(creator).unwrap();
let expected_json = json!({
"name": "test",
"status": "enabled",
"conditions": [
{
"address": "/sensors/2/state/lastupdated",
"operator": "dx"
}
],
"actions": [
{
"address": "/lights/1/state",
"method": "PUT",
"body": {}
}
],
});
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()),
status: Some(Status::Disabled),
conditions: Some(vec![]),
actions: Some(vec![]),
};
let modifier_json = serde_json::to_value(modifier).unwrap();
let expected_json = json!({
"name": "test",
"status": "disabled",
"conditions": [],
"actions": []
});
assert_eq!(modifier_json, expected_json);
}
}