war-pigeon 0.1.0

An ergonomic Rust client for War Pigeon, the runtime message delivery system.
Documentation
use crate::{Error, Message};

/// Client for War Pigeon.
///
/// This allows users easy interaction with the API,
/// as well as the ability to persist state (mainly context).
///
/// TODO: We can probably store a base message here.
#[derive(Clone, Debug)]
pub struct Client {
    /// The webhook url.
    pub webhook_url: String,

    /// A baseline message template.
    ///
    // TODO: Multiple message templates / baselines ?
    // for example baseline messages for Error, Warning, Info, etc.
    base_message: Option<Message>,

    /// The HTTP client used for sending out messages.
    http_client: reqwest::Client,
}
impl Client {
    /// Send a [`Message`] with the
    pub async fn deliver(&self, message: Message) -> Result<(), Error> {
        message.send(&self.http_client).await?;

        Ok(())
    }

    /// Create a new [`Message`] based on the base message.
    ///
    /// Only usable when [`Client::base_message`] is [`Some`].
    pub async fn new_message<F>(&self, f: F) -> Option<Message>
    where
        F: FnOnce(&mut Message) -> Message,
    {
        if let Some(base_message) = &self.base_message {
            let mut message = base_message.clone();

            let new_message = f(&mut message);
            Some(new_message)
        } else {
            None
        }
    }
}
impl PartialEq for Client {
    fn eq(&self, other: &Self) -> bool {
        self.webhook_url == other.webhook_url
    }
}
impl Eq for Client {}

#[derive(Default)]
/// Builder for a War Pigeon [`Client`].
pub struct ClientBuilder {
    /// The webhook url.
    webhook_url: Option<String>,

    baseline_message: Option<Message>,
}
impl ClientBuilder {
    /// Instantiate a new instance of [`ClientBuilder`].
    pub fn new() -> ClientBuilder {
        ClientBuilder::default()
    }

    /// Set the slack webhook URL to send messages to.
    pub fn set_webhook_url<S: Into<String>>(mut self, url: S) -> ClientBuilder {
        self.webhook_url = Some(url.into());
        self
    }

    pub fn set_baseline_message(mut self, message: Message) -> ClientBuilder {
        self.baseline_message = Some(message);
        self
    }

    /// Finish building into a [`Client`].
    pub fn build(self) -> Result<Client, Error> {
        if let Some(webhook_url) = self.webhook_url {
            Ok(Client {
                webhook_url,
                http_client: reqwest::Client::new(),
                base_message: self.baseline_message,
            })
        } else {
            Err(Error::MissingWebhookURL)
        }
    }
}

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

    #[test]
    fn new_returns_default_builder() {
        let builder = ClientBuilder::new();
        assert!(builder.webhook_url.is_none());
    }

    #[test]
    fn set_webhook_url_sets_url() {
        let builder = ClientBuilder::new().set_webhook_url("https://hooks.slack.com/abc");
        assert_eq!(
            builder.webhook_url,
            Some("https://hooks.slack.com/abc".to_string())
        );
    }

    #[test]
    fn build_returns_error_if_missing_webhook() {
        let builder = ClientBuilder::new();
        let result = builder.build();
        assert!(matches!(result, Err(Error::MissingWebhookURL)));
    }

    #[test]
    fn build_successfully_creates_client() {
        let builder = ClientBuilder::new()
            .set_webhook_url("https://hooks.slack.com/xyz")
            .set_baseline_message(Message::Custom(serde_json::json!({
                "title": "Warning: detected a tampered token detected.",
                "description": "A request with a tampered token was used by IP: xxx.xx.xx.xxx",
            })));
        let client = builder.build().expect("Expected successful build");
        let base_message = Message::Custom(serde_json::json!({
            "title": "Warning: detected a tampered token detected.",
            "description": "A request with a tampered token was used by IP: xxx.xx.xx.xxx",
        }));

        assert_eq!(
            client,
            Client {
                webhook_url: "https://hooks.slack.com/xyz".into(),
                http_client: reqwest::Client::new(),
                base_message: Some(base_message)
            }
        );
    }
}