Added config parsing and refactored parsing

This commit is contained in:
joeyahines committed 2026-04-05 14:50:35 -06:00
1 parent a65fdc64f8
commit 0ee4621fa3
6 files changed
+538 -121

No files matched your search

+30 -49
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
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
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>;
}
+77 -70
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,