sota 0.9.1

API crate for Summits on the Air
Documentation
//! Federated units of the program.

use maidenhead::{longlat_to_grid, MHError};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use time::OffsetDateTime;

use crate::{Callsign, Summit};

/// A SOTA association.
#[serde_as]
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Association {
    /// Association's code (e.g.: W6).
    pub association_code: String,
    /// Association's name (e.g.: "USA").
    pub association_name: String,
    /// Association's manager's name.
    pub manager: String,
    /// Association's manager's callsign.
    pub association_manager_callsign: Callsign,
    /// Date the association became active.
    #[serde(with = "time::serde::rfc3339")]
    pub active_from: OffsetDateTime,
    /// DXCC entity number of the association's country (e.g.: 291).
    pub dxcc: String,
    /// Northernmost boundary of association's area.
    pub max_lat: f32,
    /// Easternmost boundary of association's area.
    pub max_long: f32,
    /// Southernmost boundary of association's area.
    pub min_lat: f32,
    /// Westernmost boundary of association's area.
    pub min_long: f32,
    /// Number of regions belonging to association.
    pub regions_count: usize,
    /// Number of summits belonging to association.
    pub summits_count: usize,
    /// Assocation's regions' data.
    pub regions: Vec<Region>,
    /// Assocation's rulebooks' metadata.
    pub arm: Vec<ARM>,
}

/// A region of a SOTA association.
#[serde_as]
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Region {
    /// Association's code (e.g.: W6).
    pub association_code: String,
    /// Region's code (e.g.: CC).
    pub region_code: String,
    /// Region's manager's callsign.
    pub region_manager_callsign: String,
    /// Region's name (e.g.: Coastal Ranges).
    pub region_name: String,
    /// Region's manager's name.
    pub manager: String,
    /// Number of summits belonging to region.
    pub summits: usize,
    /// Notes about the region.
    pub notes: String,
    /// Northernmost boundary of region's area.
    pub max_lat: f32,
    /// Easternmost boundary of region's area.
    pub max_long: f32,
    /// Southernmost boundary of region's area.
    pub min_lat: f32,
    /// Westernmost boundary of region's area.
    pub min_long: f32,
}

/// An Association Reference Manual's metadata.
#[serde_as]
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ARM {
    /// Association's code, e.g., "W6".
    pub association_code: String,
    /// ARM's filename.
    pub file_name: String,
    /// Language in which ARM is written.
    pub language: String,
    /// ARM's version.
    pub version_number: f32,
    /// Date ARM was last updated.
    #[serde(with = "time::serde::rfc3339")]
    pub last_updated: OffsetDateTime,
}

#[serde_as]
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct RegionResponse {
    pub region: Region,
    pub summits: Vec<Summit>,
}

/// Describes an entity with a bounding box of coordinates.
pub(crate) trait HasBounds {
    /// Returns the boundaries in the order "S, W, N, E":
    /// - minimum latitude (southernmost),
    /// - minimum longitude (westernmost),
    /// - maximum latitude (northernmost), and
    /// - maximum latitude (easternmost).
    fn bounds(&self) -> (f32, f32, f32, f32);

    /// Returns the southwestern and northeastern corners of
    /// the bounding box.
    fn maidenhead(&self, precision: usize) -> Result<(String, String), MHError> {
        let (s, w, n, e) = self.bounds();
        Ok((
            longlat_to_grid(w.into(), s.into(), precision)?,
            longlat_to_grid(e.into(), n.into(), precision)?,
        ))
    }
}

impl HasBounds for Region {
    fn bounds(&self) -> (f32, f32, f32, f32) {
        (self.min_lat, self.min_long, self.max_lat, self.max_long)
    }
}

impl HasBounds for Association {
    fn bounds(&self) -> (f32, f32, f32, f32) {
        (self.min_lat, self.min_long, self.max_lat, self.max_long)
    }
}