Added config parsing and refactored parsing

This commit is contained in:
Joey Hines 2026-04-05 14:50:35 -06:00
parent a65fdc64f8
commit 0ee4621fa3
Signed by: joeyahines
GPG Key ID: E99D8FB14855100E
6 changed files with 538 additions and 121 deletions

View File

@ -1,9 +1,10 @@
[package]
name = "rust-dsn-parser"
version = "0.3.0"
version = "1.0.0"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
xml-rs = "1.0.0"
thiserror = "2.0.18"
xml-rs = "1.0.0"

195
data/config.xml Normal file
View File

@ -0,0 +1,195 @@
<?xml version="1.0" encoding="UTF-8"?>
<config>
<sites>
<site name="mdscc" friendlyName="Madrid" longitude="-4.2480085" latitude="40.2413554">
<dish name="DSS63" friendlyName="DSS 63" type="70M"></dish>
<dish name="DSS65" friendlyName="DSS 65" type="34MHEF"></dish>
<dish name="DSS53" friendlyName="DSS 53" type="34M"></dish>
<dish name="DSS54" friendlyName="DSS 54" type="34M"></dish>
<dish name="DSS55" friendlyName="DSS 55" type="34M"></dish>
<dish name="DSS56" friendlyName="DSS 56" type="34M"></dish>
</site>
<site name="gdscc" friendlyName="Goldstone" longitude="-116.8895382" latitude="35.2443523">
<dish name="DSS14" friendlyName="DSS 14" type="70M"></dish>
<dish name="DSS24" friendlyName="DSS 24" type="34M"></dish>
<dish name="DSS25" friendlyName="DSS 25" type="34M"></dish>
<dish name="DSS26" friendlyName="DSS 26" type="34M"></dish>
</site>
<site name="cdscc" friendlyName="Canberra" longitude="148.9812673" latitude="-35.2209189">
<dish name="DSS43" friendlyName="DSS 43" type="70M"></dish>
<dish name="DSS34" friendlyName="DSS 34" type="34M"></dish>
<dish name="DSS35" friendlyName="DSS 35" type="34M"></dish>
<dish name="DSS36" friendlyName="DSS 36" type="34M"></dish>
</site>
</sites>
<spacecraftMap>
<spacecraft name="ace" explorerName="sc_ace" friendlyName="Advanced Composition Explorer" thumbnail="true"></spacecraft>
<spacecraft name="apm1" explorerName="" friendlyName="Astrobotic Peregrine 1 Mission" thumbnail="true"></spacecraft>
<spacecraft name="agm1" explorerName="" friendlyName="Astrobotic Griffin Lander" thumbnail="true"></spacecraft>
<spacecraft name="argo" explorerName="" friendlyName="ArgoMoon" thumbnail="true"></spacecraft>
<spacecraft name="atot" explorerName="" friendlyName="Advanced Tracking and Observational Techniques"></spacecraft>
<spacecraft name="bepi" explorerName="" friendlyName="BepiColombo" thumbnail="true"></spacecraft>
<spacecraft name="bios" explorerName="" friendlyName="BioSentinel" thumbnail="true"></spacecraft>
<spacecraft name="caps" explorerName="" friendlyName="CAPSTONE" thumbnail="true"></spacecraft>
<spacecraft name="cas" explorerName="sc_cassini" friendlyName="Cassini" thumbnail="true"></spacecraft>
<spacecraft name="cass" explorerName="sc_cassini" friendlyName="Testing" thumbnail="true"></spacecraft>
<spacecraft name="ch1" explorerName="sc_chandrayaan" friendlyName="Chandrayaan"></spacecraft>
<spacecraft name="ch2" explorerName="" friendlyName="Chandrayaan-2"></spacecraft>
<spacecraft name="ch2l" explorerName="" friendlyName="Chandrayaan-2 Lander" thumbnail="true"></spacecraft>
<spacecraft name="ch2o" explorerName="" friendlyName="Chandrayaan-2 Orbiter" thumbnail="true"></spacecraft>
<spacecraft name="ch3" explorerName="" friendlyName="Chandrayaan-3" thumbnail="true"></spacecraft>
<spacecraft name="cgo" explorerName="sc_cgo" friendlyName="Carruthers Geocorona Observatory" thumbnail="true"></spacecraft>
<spacecraft name="chdr" explorerName="sc_chandra" friendlyName="Chandra" thumbnail="true"></spacecraft>
<spacecraft name="clu1" explorerName="sc_cluster_1" friendlyName="Cluster 1" thumbnail="true"></spacecraft>
<spacecraft name="clu2" explorerName="sc_cluster_2" friendlyName="Cluster 2" thumbnail="true"></spacecraft>
<spacecraft name="clu3" explorerName="sc_cluster_3" friendlyName="Cluster 3" thumbnail="true"></spacecraft>
<spacecraft name="clu4" explorerName="sc_cluster_4" friendlyName="Cluster 4" thumbnail="true"></spacecraft>
<spacecraft name="cue3" explorerName="" friendlyName="CU Earth Escape Explorer" thumbnail="true"></spacecraft>
<spacecraft name="cusp" explorerName="" friendlyName="CubeSat for Solar Particles" thumbnail="true"></spacecraft>
<spacecraft name="dart" explorerName="" friendlyName="Double Asteroid Redirection Test" thumbnail="true"></spacecraft>
<spacecraft name="dawn" explorerName="sc_dawn" friendlyName="Dawn" thumbnail="true"></spacecraft>
<spacecraft name="dif" explorerName="sc_deep_impact" friendlyName="Deep Impact" thumbnail="true"></spacecraft>
<spacecraft name="dsco" explorerName="" friendlyName="Deep Space Climate Observatory (DSCOVR)" thumbnail="true"></spacecraft>
<spacecraft name="dsn" explorerName="" friendlyName="Testing"></spacecraft>
<spacecraft name="egs" explorerName="" friendlyName="EVN and Global Services"></spacecraft>
<spacecraft name="em1" explorerName="" friendlyAcronym="art1" friendlyName="Human Space Flight: Artemis I" thumbnail="true"></spacecraft>
<spacecraft name="em2" explorerName="" friendlyAcronym="art2" friendlyName="Human Space Flight: Artemis II" thumbnail="true"></spacecraft>
<spacecraft name="em3" explorerName="" friendlyName="Human Space Flight: Artemis III"></spacecraft>
<spacecraft name="emm" explorerName="" friendlyName="Emirates Mars Mission (Hope)" thumbnail="true"></spacecraft>
<spacecraft name="em-1" explorerName="" friendlyName="Exploration Mission-1" thumbnail="true"></spacecraft>
<spacecraft name="em-2" explorerName="" friendlyName="Exploration Mission-2"></spacecraft>
<spacecraft name="em-3" explorerName="" friendlyName="Exploration Mission-3"></spacecraft>
<spacecraft name="em-4" explorerName="" friendlyName="Exploration Mission-4"></spacecraft>
<spacecraft name="em-5" explorerName="" friendlyName="Exploration Mission-5"></spacecraft>
<spacecraft name="em-6" explorerName="" friendlyName="Exploration Mission-6"></spacecraft>
<spacecraft name="em-7" explorerName="" friendlyName="Exploration Mission-7"></spacecraft>
<spacecraft name="em-8" explorerName="" friendlyName="Exploration Mission-8"></spacecraft>
<spacecraft name="em-9" explorerName="" friendlyName="Exploration Mission-9"></spacecraft>
<spacecraft name="em-10" explorerName="" friendlyName="Exploration Mission-10"></spacecraft>
<spacecraft name="equl" explorerName="" friendlyName="EQUULEUS" thumbnail="true"></spacecraft>
<spacecraft name="escb" explorerName="" friendlyName="ESCAPADE Blue" thumbnail="true"></spacecraft>
<spacecraft name="escg" explorerName="" friendlyName="ESCAPADE Gold" thumbnail="true"></spacecraft>
<spacecraft name="eurc" explorerName="" friendlyName="Europa Clipper" thumbnail="true"></spacecraft>
<spacecraft name="eus" explorerName="" friendlyName="Exploration Upper Stage"></spacecraft>
<spacecraft name="gaia" explorerName="" friendlyName="Gaia" thumbnail="true"></spacecraft>
<spacecraft name="gbra" explorerName="" friendlyName="Ground Based Radio Astronomy"></spacecraft>
<spacecraft name="go10" explorerName="sc_goes_10" friendlyName="GOES 10"></spacecraft>
<spacecraft name="go11" explorerName="sc_goes_11" friendlyName="GOES 11"></spacecraft>
<spacecraft name="go12" explorerName="sc_goes_12" friendlyName="GOES 12"></spacecraft>
<spacecraft name="go13" explorerName="sc_goes_13" friendlyName="GOES 13"></spacecraft>
<spacecraft name="go14" explorerName="sc_goes_14" friendlyName="GOES 14"></spacecraft>
<spacecraft name="go15" explorerName="sc_goes_15" friendlyName="GOES 15"></spacecraft>
<spacecraft name="go16" explorerName="" friendlyName="GOES 16"></spacecraft>
<spacecraft name="go17" explorerName="" friendlyName="GOES 17"></spacecraft>
<spacecraft name="grla" explorerName="sc_grail_a" friendlyName="GRAIL A"></spacecraft>
<spacecraft name="grlb" explorerName="sc_grail_b" friendlyName="GRAIL B"></spacecraft>
<spacecraft name="gssr" explorerName="" friendlyName="Goldstone Solar System Radar"></spacecraft>
<spacecraft name="gtl" explorerName="sc_geotail" friendlyName="Geotail" thumbnail="true"></spacecraft>
<spacecraft name="gvrt" explorerName="" friendlyName="Goldstone Apple Valley Radio Telescope"></spacecraft>
<spacecraft name="hcra" explorerName="" friendlyName="Host Country Radio Astronomy"></spacecraft>
<spacecraft name="hmap" explorerName="" friendlyName="LunaH-Map" thumbnail="true"></spacecraft>
<spacecraft name="hst" explorerName="sc_hubble_space_telescope" friendlyName="Hubble Space Telescope" thumbnail="true"></spacecraft>
<spacecraft name="hyb2" explorerName="" friendlyName="Hayabusa-2" thumbnail="true"></spacecraft>
<spacecraft name="ice" explorerName="sc_ice" friendlyName="ISEE-3 Reboot" thumbnail="true"></spacecraft>
<spacecraft name="icps" explorerName="" friendlyName="Interim Cryogenic Propulsion Stage" thumbnail="true"></spacecraft>
<spacecraft name="imag" explorerName="sc_image" friendlyName="IMAGE" thumbnail="true"></spacecraft>
<spacecraft name="imap" explorerName="sc_imap" friendlyName="IMAP" thumbnail="true"></spacecraft>
<spacecraft name="intg" explorerName="sc_integral" friendlyName="INTEGRAL" thumbnail="true"></spacecraft>
<spacecraft name="jno" explorerName="sc_juno" friendlyName="Juno" thumbnail="true"></spacecraft>
<spacecraft name="jnsa" explorerName="" friendlyName="Janus A" thumbnail="true"></spacecraft>
<spacecraft name="jnsb" explorerName="" friendlyName="Janus B" thumbnail="true"></spacecraft>
<spacecraft name="jwst" explorerName="" friendlyName="James Webb Space Telescope" thumbnail="true"></spacecraft>
<spacecraft name="kepl" explorerName="sc_kepler" friendlyName="Kepler" thumbnail="true"></spacecraft>
<spacecraft name="kplo" explorerName="" friendlyName="Korea Pathfinder Lunar Orbiter" thumbnail="true"></spacecraft>
<spacecraft name="lade" explorerName="sc_ladee" friendlyName="LADEE" thumbnail="true"></spacecraft>
<spacecraft name="ltb" explorerName="" friendlyName="Lunar Trailblazer" thumbnail="true"></spacecraft>
<spacecraft name="lcro" explorerName="sc_lcross" friendlyName="LCROSS"></spacecraft>
<spacecraft name="lfl" explorerName="" friendlyName="Lunar Flashlight" thumbnail="true"></spacecraft>
<spacecraft name="lici" explorerName="" friendlyName="LICIACube" thumbnail="true"></spacecraft>
<spacecraft name="lro" explorerName="sc_lunar_reconnaissance_orbiter" friendlyName="Lunar Reconnaissance Orbiter" thumbnail="true"></spacecraft>
<spacecraft name="lnd1" explorerName="" friendlyName="Lunar Node 1"></spacecraft>
<spacecraft name="ltst" explorerName="" friendlyName="Interim Cryogenic Propulsion Stage" thumbnail="true"></spacecraft>
<spacecraft name="lucy" explorerName="" friendlyName="Lucy" thumbnail="true"></spacecraft>
<spacecraft name="lunah-map" explorerName="" friendlyName="Lunar Hydrogen MAPper"></spacecraft>
<spacecraft name="m01o" explorerName="sc_mars_odyssey" friendlyName="Mars Odyssey" thumbnail="true"></spacecraft>
<spacecraft name="m01s" explorerName="sc_mars_odyssey" friendlyName="Mars Odyssey" thumbnail="true"></spacecraft>
<spacecraft name="m20" explorerName="" friendlyName="Mars 2020 (Perseverance)" thumbnail="true"></spacecraft>
<spacecraft name="map" explorerName="sc_wmap" friendlyName="WMAP"></spacecraft>
<spacecraft name="mcoa" explorerName="sc_marco_a" friendlyName="MarCO A" thumbnail="true"></spacecraft>
<spacecraft name="mcob" explorerName="sc_marco_b" friendlyName="MarCO B" thumbnail="true"></spacecraft>
<spacecraft name="mer1" explorerName="sc_mars_exploration_rover_b" friendlyName="Opportunity" thumbnail="true"></spacecraft>
<spacecraft name="mer2" explorerName="sc_mars_exploration_rover_a" friendlyName="Spirit" thumbnail="true"></spacecraft>
<spacecraft name="mex" explorerName="sc_mars_express" friendlyName="Mars Express" thumbnail="true"></spacecraft>
<spacecraft name="mgs" explorerName="sc_mars_global_surveyor" friendlyName="Mars Global Surveyor"></spacecraft>
<spacecraft name="mlic" explorerName="" friendlyName="Lunar IceCube" thumbnail="true"></spacecraft>
<spacecraft name="mms1" explorerName="sc_mms1" friendlyName="MMS 1" thumbnail="true"></spacecraft>
<spacecraft name="mms2" explorerName="sc_mms2" friendlyName="MMS 2" thumbnail="true"></spacecraft>
<spacecraft name="mms3" explorerName="sc_mms3" friendlyName="MMS 3" thumbnail="true"></spacecraft>
<spacecraft name="mms4" explorerName="sc_mms4" friendlyName="MMS 4" thumbnail="true"></spacecraft>
<spacecraft name="mom" explorerName="sc_mars_orbiter_mission" friendlyName="Mars Orbiter Mission" thumbnail="true"></spacecraft>
<spacecraft name="mro" explorerName="sc_mars_reconnaissance_orbiter" friendlyName="Mars Reconnaissance Orbiter" thumbnail="true"></spacecraft>
<spacecraft name="mros" explorerName="sc_mars_reconnaissance_orbiter" friendlyName="Mars Reconnaissance Orbiter" thumbnail="true"></spacecraft>
<spacecraft name="msgr" explorerName="sc_messenger" friendlyName="MESSENGER" thumbnail="true"></spacecraft>
<spacecraft name="msl" explorerName="sc_mars_science_laboratory" friendlyName="Mars Science Laboratory (Curiosity)" thumbnail="true"></spacecraft>
<spacecraft name="musc" explorerName="sc_hayabusa" friendlyName="Hayabusa"></spacecraft>
<spacecraft name="mvn" explorerName="sc_maven" friendlyName="MAVEN" thumbnail="true"></spacecraft>
<spacecraft name="neas" explorerName="" friendlyName="Near Earth Asteroid Scout" thumbnail="true"></spacecraft>
<spacecraft name="nhpc" explorerName="sc_new_horizons" friendlyName="New Horizons" thumbnail="true"></spacecraft>
<spacecraft name="no15" explorerName="sc_noaa_15" friendlyName="NOAA 15"></spacecraft>
<spacecraft name="no16" explorerName="sc_noaa_16" friendlyName="NOAA 16"></spacecraft>
<spacecraft name="no17" explorerName="sc_noaa_17" friendlyName="NOAA 17"></spacecraft>
<spacecraft name="no18" explorerName="sc_noaa_18" friendlyName="NOAA 18"></spacecraft>
<spacecraft name="nsyt" explorerName="sc_insight" friendlyName="InSight" thumbnail="true"></spacecraft>
<spacecraft name="olin" explorerName="" friendlyName="SWARM-EX" thumbnail="true"></spacecraft>
<spacecraft name="omot" explorerName="" friendlyName="OMOTENASHI" thumbnail="true"></spacecraft>
<spacecraft name="orx" explorerName="sc_osiris_rex" friendlyName="OSIRIS-REx" thumbnail="true"></spacecraft>
<spacecraft name="phx" explorerName="sc_phoenix" friendlyName="Phoenix"></spacecraft>
<spacecraft name="plc" explorerName="sc_akatsuki" friendlyName="Akatsuki" thumbnail="true"></spacecraft>
<spacecraft name="polr" explorerName="sc_polar" friendlyName="POLAR"></spacecraft>
<spacecraft name="psyc" explorerName="" friendlyName="Psyche" thumbnail="true"></spacecraft>
<spacecraft name="rd1" explorerName="" friendlyName="Red Dragon 1"></spacecraft>
<spacecraft name="rfc" explorerName="" friendlyName="Reference Frame Calibration"></spacecraft>
<spacecraft name="rose" explorerName="sc_rosetta" friendlyName="Rosetta" thumbnail="true"></spacecraft>
<spacecraft name="rp" explorerName="" friendlyName="Resource Prospector" link="https://www.nasa.gov/resource-prospector"></spacecraft>
<spacecraft name="rsp" explorerName="" friendlyName="ExoMars Rover and Surface Platform"></spacecraft>
<spacecraft name="rsts" explorerName="" friendlyName="Resource System Time Sharing"></spacecraft>
<spacecraft name="sdu" explorerName="sc_stardust" friendlyName="Stardust"></spacecraft>
<spacecraft name="sele" explorerName="sc_kaguya" friendlyName="Kaguya"></spacecraft>
<spacecraft name="sgp" explorerName="" friendlyName="Space Geodesy Program"></spacecraft>
<spacecraft name="slim" explorerName="" friendlyName="Smart Lander for Investigating Moon" thumbnail="true"></spacecraft>
<spacecraft name="soho" explorerName="sc_soho" friendlyName="SOHO" thumbnail="true"></spacecraft>
<spacecraft name="spil" explorerName="" friendlyName="Beresheet"></spacecraft>
<spacecraft name="spp" explorerName="sc_parker_solar_probe" friendlyName="Parker Solar Probe" thumbnail="true"></spacecraft>
<spacecraft name="sta" explorerName="sc_stereo_ahead" friendlyName="STEREO A" thumbnail="true"></spacecraft>
<spacecraft name="stab" explorerName="sc_stereo_behind" friendlyName="STEREO B" thumbnail="true"></spacecraft>
<spacecraft name="stb" explorerName="sc_stereo_behind" friendlyName="STEREO B" thumbnail="true"></spacecraft>
<spacecraft name="stf" explorerName="sc_sirtf" friendlyName="Spitzer Space Telescope" thumbnail="true"></spacecraft>
<spacecraft name="swfo" explorerName="sc_swfo_l_1" friendlyName="Space Weather Follow On-Lagrange 1" thumbnail="true"></spacecraft>
<spacecraft name="td10" explorerName="sc_tdrs_10" friendlyName="TDRS 10" thumbnail="true"></spacecraft>
<spacecraft name="td11" explorerName="" friendlyName="TDRS 11" thumbnail="true"></spacecraft>
<spacecraft name="td12" explorerName="" friendlyName="TDRS 12" thumbnail="true"></spacecraft>
<spacecraft name="td13" explorerName="" friendlyName="TDRS 13" thumbnail="true"></spacecraft>
<spacecraft name="tdr1" explorerName="sc_tdrs_1" friendlyName="TDRS 1" thumbnail="true"></spacecraft>
<spacecraft name="tdr3" explorerName="sc_tdrs_3" friendlyName="TDRS 3" thumbnail="true"></spacecraft>
<spacecraft name="tdr4" explorerName="sc_tdrs_4" friendlyName="TDRS 4" thumbnail="true"></spacecraft>
<spacecraft name="tdr5" explorerName="sc_tdrs_5" friendlyName="TDRS 5" thumbnail="true"></spacecraft>
<spacecraft name="tdr6" explorerName="sc_tdrs_6" friendlyName="TDRS 6" thumbnail="true"></spacecraft>
<spacecraft name="tdr7" explorerName="sc_tdrs_7" friendlyName="TDRS 7" thumbnail="true"></spacecraft>
<spacecraft name="tdr8" explorerName="sc_tdrs_8" friendlyName="TDRS 8" thumbnail="true"></spacecraft>
<spacecraft name="tdr9" explorerName="sc_tdrs_9" friendlyName="TDRS 9" thumbnail="true"></spacecraft>
<spacecraft name="terr" explorerName="sc_terra" friendlyName="TERRA" thumbnail="true"></spacecraft>
<spacecraft name="tess" explorerName="" friendlyName="Transiting Exoplanet Survey Satellite" thumbnail="true"></spacecraft>
<spacecraft name="tgo" explorerName="" friendlyName="Trace Gas Orbiter" thumbnail="true"></spacecraft>
<spacecraft name="thb" explorerName="sc_themis_b" friendlyName="THEMIS B" thumbnail="true"></spacecraft>
<spacecraft name="thc" explorerName="sc_themis_c" friendlyName="THEMIS C" thumbnail="true"></spacecraft>
<spacecraft name="tm" explorerName="" friendlyName="Team Miles" thumbnail="true"></spacecraft>
<spacecraft name="tmm" explorerName="" friendlyName="Team Miles" thumbnail="true"></spacecraft>
<spacecraft name="ulys" explorerName="sc_ulysses" friendlyName="Ulysses"></spacecraft>
<spacecraft name="vex" explorerName="sc_venus_express" friendlyName="Venus Express" thumbnail="true"></spacecraft>
<spacecraft name="vgr1" explorerName="sc_voyager_1" friendlyName="Voyager 1" thumbnail="true"></spacecraft>
<spacecraft name="vgr2" explorerName="sc_voyager_2" friendlyName="Voyager 2" thumbnail="true"></spacecraft>
<spacecraft name="vlbi" explorerName="" friendlyName="V.L.B. Interferometry"></spacecraft>
<spacecraft name="wind" explorerName="sc_wind" friendlyName="WIND" thumbnail="true"></spacecraft>
<spacecraft name="xmm" explorerName="" friendlyName="X-ray Multi-Mirror Mission (XMM-Newton)" thumbnail="true"></spacecraft>
</spacecraftMap>
</config>

View File

@ -3,75 +3,56 @@
pub mod model;
pub mod prelude;
use std::num::{ParseFloatError, ParseIntError};
use std::str::ParseBoolError;
use thiserror::Error;
#[derive(Debug)]
#[derive(Debug, Error)]
pub enum DsnRespParseError {
AttrMissing(String),
ParseIntErr(ParseIntError),
ParseFloatErr(ParseFloatError),
ParseBoolErr(ParseBoolError),
#[error("Failed to read XML: {0}")]
XMLReaderError(xml::reader::Error),
#[error("Failed to parse element '{element}': {err}")]
ElementParsingError { element: String, err: DsnFieldError },
}
impl From<ParseIntError> for DsnRespParseError {
fn from(error: ParseIntError) -> Self {
DsnRespParseError::ParseIntErr(error)
}
}
impl From<ParseFloatError> for DsnRespParseError {
fn from(error: ParseFloatError) -> Self {
DsnRespParseError::ParseFloatErr(error)
}
}
impl From<ParseBoolError> for DsnRespParseError {
fn from(error: ParseBoolError) -> Self {
DsnRespParseError::ParseBoolErr(error)
}
}
impl From<xml::reader::Error> for DsnRespParseError {
fn from(error: xml::reader::Error) -> Self {
DsnRespParseError::XMLReaderError(error)
}
}
impl std::fmt::Display for DsnRespParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DsnRespParseError::AttrMissing(attr) => write!(f, "Attr missing: {}", attr),
DsnRespParseError::ParseIntErr(e) => write!(f, "Int parse error: {}", e),
DsnRespParseError::ParseFloatErr(e) => write!(f, "Int parse error: {}", e),
DsnRespParseError::ParseBoolErr(e) => write!(f, "Bool parse error: {}", e),
DsnRespParseError::XMLReaderError(e) => write!(f, "XML Parse error: {}", e),
}
}
#[derive(Error, Debug)]
pub enum DsnFieldError {
#[error("Attribute '{0}' is missing")]
AttrMissing(String),
#[error("Failed to parse attribute '{attribute}': {err}")]
AttributeParseFailed { err: String, attribute: String },
}
#[cfg(test)]
mod tests {
use crate::model::DsnResponse;
use crate::DsnRespParseError;
use crate::model::config::DsnConfig;
use crate::model::status::DsnStatus;
use crate::prelude::DsnModel;
use std::fs::File;
use std::io::BufReader;
fn parse_test_file() -> DsnResponse {
fn parse_test_file() -> Result<DsnStatus, DsnRespParseError> {
let example_file = File::open("data/dsn.xml").unwrap();
let buf_reader = BufReader::new(example_file);
DsnResponse::from_xml_response(buf_reader).unwrap()
DsnStatus::from_xml_response(buf_reader)
}
fn parse_cfg_file() -> Result<DsnConfig, DsnRespParseError> {
let example_file = File::open("data/config.xml").unwrap();
let buf_reader = BufReader::new(example_file);
DsnConfig::from_xml_response(buf_reader)
}
#[test]
fn test_parse() {
let response = parse_test_file();
println!("{:?}", response.stations)
let response = parse_test_file().unwrap();
assert_eq!(response.stations.len(), 3);
}
#[test]
fn test_get_active_targets() {
let response = parse_test_file();
println!("{:?}", response.get_active_targets());
fn test_parse_config() {
let config = parse_cfg_file().unwrap();
assert_eq!(config.sites.len(), 3);
assert_eq!(config.spacecraft_map.len(), 168)
}
}

162
src/model/config.rs Normal file
View File

@ -0,0 +1,162 @@
use crate::model::{DsnField, DsnModel};
use crate::{DsnFieldError, DsnRespParseError};
use std::collections::HashMap;
use std::io::{BufReader, Read};
use xml::EventReader;
use xml::attribute::OwnedAttribute;
use xml::reader::XmlEvent;
#[derive(Debug)]
pub struct DsnConfig {
pub sites: Vec<SiteCfg>,
pub spacecraft_map: HashMap<String, SpacecraftMap>,
}
impl DsnModel for DsnConfig {
fn from_xml_response<T>(reader: BufReader<T>) -> Result<Self, DsnRespParseError>
where
T: Read,
{
let mut sites = Vec::new();
let mut site_cfg: Option<SiteCfg> = None;
let mut spacecraft_map = HashMap::new();
let mut spacecraft_cfg: Option<SpacecraftMap> = None;
let parser = EventReader::new(reader).into_iter();
for e in parser {
match e {
Ok(XmlEvent::StartElement {
name, attributes, ..
}) => {
if name.local_name == "site" {
site_cfg = Some(SiteCfg::parse_field(&attributes).map_err(|err| {
DsnRespParseError::ElementParsingError {
element: "site".to_string(),
err,
}
})?)
} else if let Some(site_cfg) = &mut site_cfg
&& name.local_name == "dish"
{
site_cfg.add_dish(DishCfg::parse_field(&attributes).map_err(|err| {
DsnRespParseError::ElementParsingError {
err,
element: "dish".to_string(),
}
})?)
} else if name.local_name == "spacecraft" {
spacecraft_cfg =
Some(SpacecraftMap::parse_field(&attributes).map_err(|err| {
DsnRespParseError::ElementParsingError {
err,
element: "spacecraft".to_string(),
}
})?);
}
}
Ok(XmlEvent::EndElement { name }) => {
if name.local_name == "site" {
if let Some(site_cfg) = &site_cfg {
sites.push(site_cfg.clone());
}
site_cfg = None;
} else if name.local_name == "spacecraft" {
if let Some(spacecraft_cfg) = &spacecraft_cfg {
spacecraft_map
.insert(spacecraft_cfg.name.clone(), spacecraft_cfg.clone());
}
spacecraft_cfg = None;
}
}
Ok(XmlEvent::EndDocument) => {}
Err(e) => {
return Err(DsnRespParseError::XMLReaderError(e));
}
_ => {}
}
}
Ok(Self {
sites,
spacecraft_map,
})
}
}
#[derive(Debug, Clone)]
pub struct SiteCfg {
pub name: String,
pub friendly_name: String,
pub longitude: f32,
pub latitude: f32,
pub dishes: Vec<DishCfg>,
}
impl SiteCfg {
pub fn add_dish(&mut self, dish: DishCfg) {
self.dishes.push(dish);
}
}
impl DsnField for SiteCfg {
fn parse_field(attrs: &[OwnedAttribute]) -> Result<Self, DsnFieldError> {
let name = Self::get_attr(attrs, "name")?;
let friendly_name = Self::get_attr(attrs, "friendlyName")?;
let longitude = Self::parse_attribute::<f32>(attrs, "longitude")?;
let latitude = Self::parse_attribute::<f32>(attrs, "latitude")?;
Ok(Self {
name,
friendly_name,
longitude,
latitude,
dishes: vec![],
})
}
}
#[derive(Debug, Clone)]
pub struct DishCfg {
pub name: String,
pub friendly_name: String,
pub dish_type: String,
}
impl DsnField for DishCfg {
fn parse_field(attrs: &[OwnedAttribute]) -> Result<Self, DsnFieldError> {
let name = Self::get_attr(attrs, "name")?;
let friendly_name = Self::get_attr(attrs, "friendlyName")?;
let dish_type = Self::get_attr(attrs, "type")?;
Ok(Self {
name,
friendly_name,
dish_type,
})
}
}
#[derive(Debug, Clone)]
pub struct SpacecraftMap {
pub name: String,
pub explorer_name: String,
pub friendly_name: String,
pub thumbnail: Option<bool>,
}
impl DsnField for SpacecraftMap {
fn parse_field(attrs: &[OwnedAttribute]) -> Result<Self, DsnFieldError> {
let name = Self::get_attr(attrs, "name")?;
let explorer_name = Self::get_attr(attrs, "explorerName")?;
let friendly_name = Self::get_attr(attrs, "friendlyName")?;
let thumbnail = Self::parse_optional_attribute::<bool>(attrs, "thumbnail")?;
Ok(Self {
name,
explorer_name,
friendly_name,
thumbnail,
})
}
}

71
src/model/mod.rs Normal file
View File

@ -0,0 +1,71 @@
pub mod config;
pub mod status;
use crate::{DsnFieldError, DsnRespParseError};
use std::fmt::Debug;
use std::io::BufReader;
use std::str::FromStr;
use xml::attribute::OwnedAttribute;
pub trait DsnModel: Sized {
fn from_xml_string(resp_str: &str) -> Result<Self, DsnRespParseError> {
Self::from_xml_response(BufReader::new(resp_str.as_bytes()))
}
fn from_xml_response<T>(reader: BufReader<T>) -> Result<Self, DsnRespParseError>
where
T: std::io::Read;
}
pub trait DsnField: Sized + Clone {
fn get_attr(attrs: &[OwnedAttribute], name: &str) -> Result<String, DsnFieldError> {
attrs
.iter()
.find_map(|o| {
if o.name.local_name.eq(name) {
Some(o.value.clone())
} else {
None
}
})
.ok_or_else(|| DsnFieldError::AttrMissing(name.to_string()))
}
fn parse<T: FromStr>(name: &str, value: &str) -> Result<T, DsnFieldError>
where
<T as FromStr>::Err: Debug,
{
value
.parse::<T>()
.map_err(|err| DsnFieldError::AttributeParseFailed {
err: format!("{err:?}"),
attribute: name.to_string(),
})
}
fn parse_attribute<T: FromStr>(attrs: &[OwnedAttribute], name: &str) -> Result<T, DsnFieldError>
where
<T as FromStr>::Err: Debug,
{
Self::parse::<T>(name, &Self::get_attr(attrs, name)?)
}
fn parse_optional_attribute<T: FromStr>(
attrs: &[OwnedAttribute],
name: &str,
) -> Result<Option<T>, DsnFieldError>
where
<T as FromStr>::Err: Debug,
{
let attr = if let Ok(attr) = Self::get_attr(attrs, name)
&& !attr.is_empty()
{
Some(Self::parse::<T>(name, &attr)?)
} else {
None
};
Ok(attr)
}
fn parse_field(attrs: &[OwnedAttribute]) -> Result<Self, DsnFieldError>;
}

View File

@ -1,30 +1,16 @@
use std::convert::TryFrom;
use crate::model::{DsnField, DsnModel};
use crate::{DsnFieldError, DsnRespParseError};
use std::io::BufReader;
use crate::DsnRespParseError;
use xml::EventReader;
use xml::attribute::OwnedAttribute;
use xml::reader::XmlEvent;
fn get_attr(attrs: &[OwnedAttribute], name: &str) -> Result<String, DsnRespParseError> {
attrs
.iter()
.find_map(|o| {
if o.name.local_name.eq(name) {
Some(o.value.clone())
} else {
None
}
})
.ok_or_else(|| DsnRespParseError::AttrMissing(name.to_string()))
}
#[derive(Clone, Debug)]
pub struct DsnResponse {
pub struct DsnStatus {
pub stations: Vec<Station>,
}
impl DsnResponse {
impl DsnStatus {
pub fn get_active_targets(&self) -> Vec<Target> {
let mut targets = Vec::new();
@ -36,12 +22,10 @@ impl DsnResponse {
targets
}
}
pub fn from_xml_string(resp_str: &str) -> Result<DsnResponse, DsnRespParseError> {
Self::from_xml_response(BufReader::new(resp_str.as_bytes()))
}
pub fn from_xml_response<T>(reader: BufReader<T>) -> Result<DsnResponse, DsnRespParseError>
impl DsnModel for DsnStatus {
fn from_xml_response<T>(reader: BufReader<T>) -> Result<DsnStatus, DsnRespParseError>
where
T: std::io::Read,
{
@ -60,15 +44,46 @@ impl DsnResponse {
if let Some(station) = station {
stations.push(station);
}
station = Some(Station::try_from(attributes)?);
station = Some(Station::parse_field(&attributes).map_err(|err| {
DsnRespParseError::ElementParsingError {
err,
element: "station".to_string(),
}
})?);
} else if name.local_name.contains("dish") {
dish = Some(Dish::try_from(attributes)?);
dish = Some(Dish::parse_field(&attributes).map_err(|err| {
DsnRespParseError::ElementParsingError {
err,
element: "dish".to_string(),
}
})?);
} else if name.local_name.contains("downSignal") {
dish = Some(dish.unwrap().add_down_signal(Signal::try_from(attributes)?));
dish = Some(dish.unwrap().add_down_signal(
Signal::parse_field(&attributes).map_err(|err| {
DsnRespParseError::ElementParsingError {
err,
element: "downSignal".to_string(),
}
})?,
));
} else if name.local_name.contains("upSignal") {
dish = Some(dish.unwrap().add_up_signal(Signal::try_from(attributes)?));
dish = Some(dish.unwrap().add_up_signal(
Signal::parse_field(&attributes).map_err(|err| {
DsnRespParseError::ElementParsingError {
err,
element: "upSignal".to_string(),
}
})?,
));
} else if name.local_name.contains("target") {
dish = Some(dish.unwrap().add_target(Target::try_from(attributes)?));
dish = Some(dish.unwrap().add_target(
Target::parse_field(&attributes).map_err(|err| {
DsnRespParseError::ElementParsingError {
err,
element: "target".to_string(),
}
})?,
));
}
}
Ok(XmlEvent::EndElement { name }) => {
@ -84,13 +99,13 @@ impl DsnResponse {
}
}
Err(e) => {
return Err(DsnRespParseError::from(e));
return Err(DsnRespParseError::XMLReaderError(e));
}
_ => {}
}
}
Ok(DsnResponse { stations })
Ok(DsnStatus { stations })
}
}
@ -110,14 +125,12 @@ impl Station {
}
}
impl TryFrom<Vec<OwnedAttribute>> for Station {
type Error = DsnRespParseError;
fn try_from(attrs: Vec<OwnedAttribute>) -> Result<Self, Self::Error> {
let name = get_attr(&attrs, "name")?;
let friendly_name = get_attr(&attrs, "friendlyName")?;
let time_utc = get_attr(&attrs, "timeUTC")?.parse::<u64>()?;
let tz_offset = get_attr(&attrs, "timeZoneOffset")?.parse::<f64>()?;
impl DsnField for Station {
fn parse_field(attrs: &[OwnedAttribute]) -> Result<Self, DsnFieldError> {
let name = Self::get_attr(attrs, "name")?;
let friendly_name = Self::get_attr(attrs, "friendlyName")?;
let time_utc = Self::parse_attribute::<u64>(attrs, "timeUTC")?;
let tz_offset = Self::parse_attribute::<f64>(attrs, "timeZoneOffset")?;
Ok(Self {
name,
@ -135,7 +148,7 @@ pub enum SignalType {
Carrier,
#[default]
None,
Unkown(String),
Unknown(String),
}
impl From<String> for SignalType {
@ -144,7 +157,7 @@ impl From<String> for SignalType {
"data" => SignalType::Data,
"carrier" => SignalType::Carrier,
"none" => SignalType::None,
_ => SignalType::Unkown(s),
_ => SignalType::Unknown(s),
}
}
}
@ -159,16 +172,14 @@ pub struct Signal {
pub spacecraft_id: Option<i32>,
}
impl TryFrom<Vec<OwnedAttribute>> for Signal {
type Error = DsnRespParseError;
fn try_from(attrs: Vec<OwnedAttribute>) -> Result<Self, Self::Error> {
let signal_type = SignalType::from(get_attr(&attrs, "signalType")?);
let data_rate = get_attr(&attrs, "dataRate")?.parse::<f64>().ok();
let frequency = get_attr(&attrs, "frequency")?.parse::<f64>().ok();
let power = get_attr(&attrs, "power")?.parse::<f64>().ok();
let spacecraft = get_attr(&attrs, "spacecraft")?;
let spacecraft_id = get_attr(&attrs, "spacecraftID")?.parse::<i32>().ok();
impl DsnField for Signal {
fn parse_field(attrs: &[OwnedAttribute]) -> Result<Self, DsnFieldError> {
let signal_type = SignalType::from(Self::get_attr(attrs, "signalType")?);
let data_rate = Self::parse_optional_attribute::<f64>(attrs, "dataRate")?;
let frequency = Self::parse_optional_attribute::<f64>(attrs, "frequency")?;
let power = Self::parse_optional_attribute::<f64>(attrs, "power")?;
let spacecraft = Self::get_attr(attrs, "spacecraft")?;
let spacecraft_id = Self::parse_optional_attribute::<i32>(attrs, "spacecraftID")?;
Ok(Self {
signal_type,
@ -193,15 +204,13 @@ pub struct Target {
pub rtlt: f64,
}
impl TryFrom<Vec<OwnedAttribute>> for Target {
type Error = DsnRespParseError;
fn try_from(attrs: Vec<OwnedAttribute>) -> Result<Self, Self::Error> {
let name = get_attr(&attrs, "name")?;
let id = get_attr(&attrs, "id")?.parse::<u32>()?;
let upleg_range = get_attr(&attrs, "uplegRange")?.parse::<f64>()?;
let downleg_range = get_attr(&attrs, "downlegRange")?.parse::<f64>()?;
let rtlt = get_attr(&attrs, "rtlt")?.parse::<f64>()?;
impl DsnField for Target {
fn parse_field(attrs: &[OwnedAttribute]) -> Result<Self, DsnFieldError> {
let name = Self::get_attr(attrs, "name")?;
let id = Self::parse_attribute::<u32>(attrs, "id")?;
let upleg_range = Self::parse_attribute::<f64>(attrs, "uplegRange")?;
let downleg_range = Self::parse_attribute::<f64>(attrs, "downlegRange")?;
let rtlt = Self::parse_attribute::<f64>(attrs, "rtlt")?;
Ok(Self {
name,
@ -244,17 +253,15 @@ impl Dish {
}
}
impl TryFrom<Vec<OwnedAttribute>> for Dish {
type Error = DsnRespParseError;
fn try_from(attrs: Vec<OwnedAttribute>) -> Result<Self, Self::Error> {
let name = get_attr(&attrs, "name")?;
let azimuth_angle = get_attr(&attrs, "azimuthAngle")?.parse::<f64>().ok();
let elevation_angle = get_attr(&attrs, "elevationAngle")?.parse::<f64>().ok();
let wind_speed = get_attr(&attrs, "windSpeed")?.parse::<f64>().ok();
let is_mspa = get_attr(&attrs, "isMSPA")?.parse::<bool>()?;
let is_array = get_attr(&attrs, "isArray")?.parse::<bool>()?;
let is_ddor = get_attr(&attrs, "isDDOR")?.parse::<bool>()?;
impl DsnField for Dish {
fn parse_field(attrs: &[OwnedAttribute]) -> Result<Self, DsnFieldError> {
let name = Self::get_attr(attrs, "name")?;
let azimuth_angle = Self::parse_optional_attribute::<f64>(attrs, "azimuthAngle")?;
let elevation_angle = Self::parse_optional_attribute::<f64>(attrs, "elevationAngle")?;
let wind_speed = Self::parse_optional_attribute::<f64>(attrs, "windSpeed")?;
let is_mspa = Self::parse_attribute::<bool>(attrs, "isMSPA")?;
let is_array = Self::parse_attribute::<bool>(attrs, "isArray")?;
let is_ddor = Self::parse_attribute::<bool>(attrs, "isDDOR")?;
Ok(Self {
name,