Skip to main content

war_pigeon/
client.rs

1use crate::{Error, Message};
2
3/// Client for War Pigeon.
4///
5/// This allows users easy interaction with the API,
6/// as well as the ability to persist state (mainly context).
7///
8/// TODO: We can probably store a base message here.
9#[derive(Clone, Debug)]
10pub struct Client {
11    /// The webhook url.
12    pub webhook_url: String,
13
14    /// A baseline message template.
15    ///
16    // TODO: Multiple message templates / baselines ?
17    // for example baseline messages for Error, Warning, Info, etc.
18    base_message: Option<Message>,
19
20    /// The HTTP client used for sending out messages.
21    http_client: reqwest::Client,
22}
23impl Client {
24    /// Send a [`Message`] with the
25    pub async fn deliver(&self, message: Message) -> Result<(), Error> {
26        message.send(&self.http_client).await?;
27
28        Ok(())
29    }
30
31    /// Create a new [`Message`] based on the base message.
32    ///
33    /// Only usable when [`Client::base_message`] is [`Some`].
34    pub async fn new_message<F>(&self, f: F) -> Option<Message>
35    where
36        F: FnOnce(&mut Message) -> Message,
37    {
38        if let Some(base_message) = &self.base_message {
39            let mut message = base_message.clone();
40
41            let new_message = f(&mut message);
42            Some(new_message)
43        } else {
44            None
45        }
46    }
47}
48impl PartialEq for Client {
49    fn eq(&self, other: &Self) -> bool {
50        self.webhook_url == other.webhook_url
51    }
52}
53impl Eq for Client {}
54
55#[derive(Default)]
56/// Builder for a War Pigeon [`Client`].
57pub struct ClientBuilder {
58    /// The webhook url.
59    webhook_url: Option<String>,
60
61    baseline_message: Option<Message>,
62}
63impl ClientBuilder {
64    /// Instantiate a new instance of [`ClientBuilder`].
65    pub fn new() -> ClientBuilder {
66        ClientBuilder::default()
67    }
68
69    /// Set the slack webhook URL to send messages to.
70    pub fn set_webhook_url<S: Into<String>>(mut self, url: S) -> ClientBuilder {
71        self.webhook_url = Some(url.into());
72        self
73    }
74
75    pub fn set_baseline_message(mut self, message: Message) -> ClientBuilder {
76        self.baseline_message = Some(message);
77        self
78    }
79
80    /// Finish building into a [`Client`].
81    pub fn build(self) -> Result<Client, Error> {
82        if let Some(webhook_url) = self.webhook_url {
83            Ok(Client {
84                webhook_url,
85                http_client: reqwest::Client::new(),
86                base_message: self.baseline_message,
87            })
88        } else {
89            Err(Error::MissingWebhookURL)
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn new_returns_default_builder() {
100        let builder = ClientBuilder::new();
101        assert!(builder.webhook_url.is_none());
102    }
103
104    #[test]
105    fn set_webhook_url_sets_url() {
106        let builder = ClientBuilder::new().set_webhook_url("https://hooks.slack.com/abc");
107        assert_eq!(
108            builder.webhook_url,
109            Some("https://hooks.slack.com/abc".to_string())
110        );
111    }
112
113    #[test]
114    fn build_returns_error_if_missing_webhook() {
115        let builder = ClientBuilder::new();
116        let result = builder.build();
117        assert!(matches!(result, Err(Error::MissingWebhookURL)));
118    }
119
120    #[test]
121    fn build_successfully_creates_client() {
122        let builder = ClientBuilder::new()
123            .set_webhook_url("https://hooks.slack.com/xyz")
124            .set_baseline_message(Message::Custom(serde_json::json!({
125                "title": "Warning: detected a tampered token detected.",
126                "description": "A request with a tampered token was used by IP: xxx.xx.xx.xxx",
127            })));
128        let client = builder.build().expect("Expected successful build");
129        let base_message = Message::Custom(serde_json::json!({
130            "title": "Warning: detected a tampered token detected.",
131            "description": "A request with a tampered token was used by IP: xxx.xx.xx.xxx",
132        }));
133
134        assert_eq!(
135            client,
136            Client {
137                webhook_url: "https://hooks.slack.com/xyz".into(),
138                http_client: reqwest::Client::new(),
139                base_message: Some(base_message)
140            }
141        );
142    }
143}