pushit 0.1.1

A small, cross-platform command-line tool for sending push notifications.
use serde::{Deserialize, Serialize};

use crate::error::{Error, Result};
use crate::service::{Message, Service};

const ENDPOINT: &str = "https://api.pushover.net/1/messages.json";

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PushoverConfig {
    pub token: String,
    pub user_key: String,
}

impl Service for PushoverConfig {
    fn send(&self, msg: &Message) -> Result<()> {
        let agent: ureq::Agent = ureq::Agent::config_builder()
            .http_status_as_error(false)
            .build()
            .into();

        let priority_str = msg.priority.map(|p| p.to_string());

        let mut form: Vec<(&str, &str)> = vec![
            ("token", self.token.as_str()),
            ("user", self.user_key.as_str()),
            ("message", msg.body.as_str()),
        ];
        if let Some(v) = msg.title.as_deref() {
            form.push(("title", v));
        }
        if let Some(v) = priority_str.as_deref() {
            form.push(("priority", v));
        }
        if let Some(v) = msg.sound.as_deref() {
            form.push(("sound", v));
        }
        if let Some(v) = msg.device.as_deref() {
            form.push(("device", v));
        }
        if let Some(v) = msg.url.as_deref() {
            form.push(("url", v));
        }
        if let Some(v) = msg.url_title.as_deref() {
            form.push(("url_title", v));
        }

        let mut response = agent.post(ENDPOINT).send_form(form)?;
        let status = response.status().as_u16();
        let body = response.body_mut().read_to_string()?;

        if (200..300).contains(&status) {
            return Ok(());
        }

        let msg = parse_pushover_error(&body)
            .unwrap_or_else(|| format!("pushover returned HTTP {status}: {body}"));
        Err(Error::Service(msg))
    }
}

fn parse_pushover_error(body: &str) -> Option<String> {
    #[derive(Deserialize)]
    struct PushoverError {
        errors: Option<Vec<String>>,
    }
    let parsed: PushoverError = serde_json::from_str(body).ok()?;
    let errors = parsed.errors?;
    if errors.is_empty() {
        None
    } else {
        Some(errors.join("; "))
    }
}