use crate::clublogquery::{is_in_time_window, ClubLogQuery};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer};
use std::vec::Vec;
pub type Adif = u16;
pub type CqZone = u8;
pub type RecordId = u32;
pub const CALLSIGN_EXCEPTION_INVALID: &str = "INVALID";
pub const CALLSIGN_EXCEPTION_MARITIME_MOBILE: &str = "MARITIME MOBILE";
pub const CALLSIGN_EXCEPTION_AERONAUTICAL_MOBILE: &str = "AERONAUTICAL MOBILE";
pub const CALLSIGN_EXCEPTION_SATELLITE: &str = "SATELLITE, INTERNET OR REPEATER";
pub const ADIF_ID_NO_DXCC: Adif = 0;
#[derive(Debug)]
pub struct Error;
impl ClubLogQuery for ClubLog {
fn get_entity(&self, adif: Adif, timestamp: &DateTime<Utc>) -> Option<&Entity> {
self.entities
.list
.iter()
.find(|e| e.adif == adif && is_in_time_window(timestamp, e.start, e.end))
}
fn get_prefix(&self, prefix: &str, timestamp: &DateTime<Utc>) -> Option<&Prefix> {
self.prefixes
.list
.iter()
.find(|p| p.call == prefix && is_in_time_window(timestamp, p.start, p.end))
}
fn get_callsign_exception(
&self,
callsign: &str,
timestamp: &DateTime<Utc>,
) -> Option<&CallsignException> {
self.exceptions
.list
.iter()
.find(|e| e.call == callsign && is_in_time_window(timestamp, e.start, e.end))
}
fn get_zone_exception(&self, callsign: &str, timestamp: &DateTime<Utc>) -> Option<CqZone> {
let exc = self
.zone_exceptions
.list
.iter()
.find(|o| o.call == callsign && is_in_time_window(timestamp, o.start, o.end))?;
Some(exc.zone)
}
fn is_invalid_operation(&self, callsign: &str, timestamp: &DateTime<Utc>) -> bool {
self.invalid_operations
.list
.iter()
.any(|o| o.call == callsign && is_in_time_window(timestamp, o.start, o.end))
}
}
impl ClubLog {
pub fn parse(content: &str) -> Result<Self, Error> {
quick_xml::de::from_str(content).map_err(|_| Error)
}
}
fn parse_datetime<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
DateTime::parse_from_rfc3339(&s)
.map(|d| d.into())
.map_err(serde::de::Error::custom)
}
fn parse_datetime_opt<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(Some(
DateTime::parse_from_rfc3339(&s)
.map(|d| d.into())
.map_err(serde::de::Error::custom)?,
))
}
#[derive(Debug, Deserialize, Clone)]
#[serde(rename = "clublog")]
pub struct ClubLog {
#[serde(default)]
#[serde(deserialize_with = "parse_datetime")]
#[serde(rename = "@date")]
pub date: DateTime<Utc>,
pub entities: Entities,
pub exceptions: CallsignExceptions,
pub prefixes: Prefixes,
pub invalid_operations: InvalidOperations,
pub zone_exceptions: ZoneExceptions,
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct Entities {
#[serde(rename = "entity")]
pub list: Vec<Entity>,
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct Entity {
pub adif: Adif,
pub name: String,
pub prefix: String,
pub deleted: bool,
pub cqz: Option<CqZone>,
pub cont: Option<String>,
pub long: Option<f32>,
pub lat: Option<f32>,
#[serde(default)]
#[serde(deserialize_with = "parse_datetime_opt")]
pub start: Option<DateTime<Utc>>,
#[serde(default)]
#[serde(deserialize_with = "parse_datetime_opt")]
pub end: Option<DateTime<Utc>>,
pub whitelist: Option<bool>,
#[serde(default)]
#[serde(deserialize_with = "parse_datetime_opt")]
pub whitelist_start: Option<DateTime<Utc>>,
#[serde(default)]
#[serde(deserialize_with = "parse_datetime_opt")]
pub whitelist_end: Option<DateTime<Utc>>,
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
#[serde(rename = "Exceptions")]
pub struct CallsignExceptions {
#[serde(rename = "exception")]
pub list: Vec<CallsignException>,
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
#[serde(rename = "Exception")]
pub struct CallsignException {
#[serde(rename = "@record")]
pub record: RecordId,
pub call: String,
pub entity: String,
pub adif: Adif,
pub cqz: Option<CqZone>,
pub cont: Option<String>,
pub long: Option<f32>,
pub lat: Option<f32>,
#[serde(default)]
#[serde(deserialize_with = "parse_datetime_opt")]
pub start: Option<DateTime<Utc>>,
#[serde(default)]
#[serde(deserialize_with = "parse_datetime_opt")]
pub end: Option<DateTime<Utc>>,
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct Prefixes {
#[serde(rename = "prefix")]
pub list: Vec<Prefix>,
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct Prefix {
#[serde(rename = "@record")]
pub record: RecordId,
pub call: String,
pub entity: String,
pub adif: Adif,
pub cqz: Option<CqZone>,
pub cont: Option<String>,
pub long: Option<f32>,
pub lat: Option<f32>,
#[serde(default)]
#[serde(deserialize_with = "parse_datetime_opt")]
pub start: Option<DateTime<Utc>>,
#[serde(default)]
#[serde(deserialize_with = "parse_datetime_opt")]
pub end: Option<DateTime<Utc>>,
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct InvalidOperations {
#[serde(rename = "invalid")]
pub list: Vec<InvalidOperation>,
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
#[serde(rename = "Invalid")]
pub struct InvalidOperation {
#[serde(rename = "@record")]
pub record: RecordId,
pub call: String,
#[serde(default)]
#[serde(deserialize_with = "parse_datetime_opt")]
pub start: Option<DateTime<Utc>>,
#[serde(default)]
#[serde(deserialize_with = "parse_datetime_opt")]
pub end: Option<DateTime<Utc>>,
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct ZoneExceptions {
#[serde(rename = "zone_exception")]
pub list: Vec<ZoneException>,
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct ZoneException {
#[serde(rename = "@record")]
pub record: RecordId,
pub call: String,
pub zone: CqZone,
#[serde(default)]
#[serde(deserialize_with = "parse_datetime_opt")]
pub start: Option<DateTime<Utc>>,
#[serde(default)]
#[serde(deserialize_with = "parse_datetime_opt")]
pub end: Option<DateTime<Utc>>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_parser() {
let clublog =
ClubLog::parse(&std::fs::read_to_string("data/clublog/cty.xml").unwrap()).unwrap();
assert!(clublog.entities.list.len() > 0);
assert!(clublog.exceptions.list.len() > 0);
assert!(clublog.prefixes.list.len() > 0);
assert!(clublog.invalid_operations.list.len() > 0);
assert!(clublog.zone_exceptions.list.len() > 0);
}
}