sota 0.7.0

API crate for Summits on the Air
Documentation
//! Statements of planned activations.

use std::hash::{Hash, Hasher};

use serde::{Deserialize, Serialize};
use serde_with::{serde_as, NoneAsEmptyString};
use time::{format_description::parse, OffsetDateTime};

use crate::{callsign::Callsign, HasSummit};

use super::Notice;

/// A SOTA alert.
///
/// An alert is something like a future spot: it states that the named operator
/// plans to activate a summit at the given time.
#[serde_as]
#[derive(Debug, Clone, Deserialize, Serialize, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Alert {
    /// Activator's callsign.
    pub activating_callsign: Callsign,
    /// Activator's name.
    pub activator_name: String,
    /// Association code of the summit, e.g. "W6".
    pub association_code: String,
    /// Short code of the summit, e.g. "CC-063".
    pub summit_code: String,
    /// A description of the summit, e.g., "Mount Tamalpais, 786m, 2 pts".
    pub summit_details: String,
    /// Freeform description of planned bands/modes.
    pub frequency: String,
    /// Time when the alert was posted.
    #[serde(with = "time::serde::rfc3339")]
    pub time_stamp: OffsetDateTime,
    /// Time for which the activation is planned.
    #[serde(with = "time::serde::rfc3339")]
    pub date_activated: OffsetDateTime,
    /// Poster's callsign.
    pub poster_callsign: Callsign,
    // A good reason Callsign doesn't enforce a specific format:
    // `RBNHOLE` is a common poster.
    #[serde_as(as = "NoneAsEmptyString")]
    /// Comments by the poster.
    pub comments: Option<String>,
    /// A UUID by which clients should cache responses.
    pub epoch: String,
}

impl Notice for Alert {
    fn callsign(&self) -> &Callsign {
        &self.activating_callsign
    }

    fn epoch(&self) -> &str {
        &self.epoch
    }

    fn timestamp(&self) -> String {
        let time = self
            .date_activated
            .format(&parse("[year]-[month]-[day] [hour]:[minute]").unwrap())
            .unwrap();
        format!("{}Z", time)
    }
}

impl HasSummit for Alert {
    fn summit_code(&self) -> String {
        format!("{}/{}", self.association_code, self.summit_code)
    }
}

impl Hash for Alert {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.activating_callsign.hash(state);
        self.association_code.hash(state);
        self.summit_code.hash(state);
        self.frequency.hash(state);
        self.date_activated.hash(state);
    }
}

impl PartialEq for Alert {
    fn eq(&self, other: &Self) -> bool {
        self.activating_callsign == other.activating_callsign
            && self.association_code == other.association_code
            && self.summit_code == other.summit_code
            && self.frequency == other.frequency
            && self.time_stamp == other.time_stamp
    }
}