Skip to main content

homeassistant_cli/commands/
event.rs

1use owo_colors::OwoColorize;
2
3use crate::api::{self, HaClient, HaError};
4use crate::output::{self, OutputConfig};
5
6pub async fn fire(
7    out: &OutputConfig,
8    client: &HaClient,
9    event_type: &str,
10    data: Option<&str>,
11) -> Result<(), HaError> {
12    let body = if let Some(d) = data {
13        Some(
14            serde_json::from_str::<serde_json::Value>(d)
15                .map_err(|e| HaError::InvalidInput(format!("Invalid JSON data: {e}")))?,
16        )
17    } else {
18        None
19    };
20
21    let result = api::events::fire_event(client, event_type, body.as_ref()).await?;
22
23    if out.is_json() {
24        out.print_data(
25            &serde_json::to_string_pretty(&serde_json::json!({"ok": true, "data": result}))
26                .expect("serialize"),
27        );
28    } else {
29        out.print_data(&format!("✔ Fired event: {event_type}"));
30    }
31    Ok(())
32}
33
34pub async fn watch(
35    out: &OutputConfig,
36    client: &HaClient,
37    event_type: Option<&str>,
38) -> Result<(), HaError> {
39    out.print_message(&format!(
40        "Watching events{} (Ctrl+C to stop)...",
41        event_type.map(|t| format!(": {t}")).unwrap_or_default()
42    ));
43
44    api::events::watch_stream(client, event_type, |event| {
45        if out.is_json() {
46            if let Ok(s) =
47                serde_json::to_string_pretty(&serde_json::json!({"ok": true, "data": event}))
48            {
49                println!("{s}");
50            }
51        } else {
52            let time = event
53                .time_fired
54                .as_deref()
55                .map(output::relative_time)
56                .unwrap_or_else(|| "-".to_owned());
57            let data_str = if event.data.is_null() || event.data == serde_json::json!({}) {
58                String::new()
59            } else {
60                format!("  {}", event.data.to_string().dimmed())
61            };
62            println!("{}  {}{}", time.dimmed(), event.event_type.bold(), data_str);
63        }
64        true
65    })
66    .await
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use crate::api::HaClient;
73    use crate::output::{OutputConfig, OutputFormat};
74    use wiremock::matchers::{method, path};
75    use wiremock::{Mock, MockServer, ResponseTemplate};
76
77    fn json_out() -> OutputConfig {
78        OutputConfig::new(Some(OutputFormat::Json), false)
79    }
80
81    #[tokio::test]
82    async fn fire_succeeds_on_200() {
83        let server = MockServer::start().await;
84        Mock::given(method("POST"))
85            .and(path("/api/events/my_event"))
86            .respond_with(
87                ResponseTemplate::new(200)
88                    .set_body_json(serde_json::json!({"message": "Event my_event fired."})),
89            )
90            .mount(&server)
91            .await;
92
93        let client = HaClient::new(server.uri(), "tok");
94        let result = fire(&json_out(), &client, "my_event", None).await;
95        assert!(result.is_ok());
96    }
97
98    #[tokio::test]
99    async fn fire_with_invalid_json_returns_error() {
100        let server = MockServer::start().await;
101        let client = HaClient::new(server.uri(), "tok");
102        let result = fire(&json_out(), &client, "my_event", Some("{invalid}")).await;
103        assert!(matches!(result, Err(crate::api::HaError::InvalidInput(_))));
104    }
105}