Initial commit

+ Parsing working from a test XML file
+ All parsing is handled by the models
+ Clippy + fmt
This commit is contained in:
joeyahines committed 2022-10-08 14:46:43 -06:00
commit 2dca331092
6 files changed
+437

No files matched your search

+85
View File
@@ -0,0 +1,85 @@
#![allow(dead_code)]
mod model;
mod prelude;
use std::num::{ParseFloatError, ParseIntError};
use std::str::ParseBoolError;
#[derive(Debug)]
pub enum DsnRespParseError {
AttrMissing(String),
ParseIntErr(ParseIntError),
ParseFloatErr(ParseFloatError),
ParseBoolErr(ParseBoolError),
ParseChronoErr(chrono::ParseError),
XMLReaderError(xml::reader::Error),
}
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<chrono::ParseError> for DsnRespParseError {
fn from(error: chrono::ParseError) -> Self {
DsnRespParseError::ParseChronoErr(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::ParseChronoErr(e) => write!(f, "Chrono parse error: {}", e),
DsnRespParseError::XMLReaderError(e) => write!(f, "XML Parse error: {}", e),
}
}
}
#[cfg(test)]
mod tests {
use crate::model::DsnResponse;
use std::fs::File;
use std::io::BufReader;
fn parse_test_file() -> DsnResponse {
let example_file = File::open("data/dsn.xml").unwrap();
let buf_reader = BufReader::new(example_file);
DsnResponse::from_xml_response(buf_reader).unwrap()
}
#[test]
fn test_parse() {
let response = parse_test_file();
println!("{:?}", response.stations)
}
#[test]
fn test_get_active_targets() {
let response = parse_test_file();
println!("{:?}", response.get_active_targets());
}
}
+286
View File
@@ -0,0 +1,286 @@
use std::convert::TryFrom;
use std::io::BufReader;
use crate::DsnRespParseError;
use xml::attribute::OwnedAttribute;
use xml::reader::XmlEvent;
use xml::EventReader;
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 stations: Vec<Station>,
}
impl DsnResponse {
pub fn get_active_targets(&self) -> Vec<Target> {
let mut targets = Vec::new();
for station in &self.stations {
for dish in &station.dishes {
targets.append(&mut dish.target.clone())
}
}
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>
where
T: std::io::Read,
{
let mut stations = Vec::new();
let mut station: Option<Station> = None;
let mut dish: Option<Dish> = None;
let parser = EventReader::new(reader).into_iter();
for e in parser {
match e {
Ok(XmlEvent::StartElement {
name, attributes, ..
}) => {
if name.local_name.contains("station") {
if let Some(station) = station {
stations.push(station);
}
station = Some(Station::try_from(attributes).unwrap());
} else if name.local_name.contains("dish") {
dish = Some(Dish::try_from(attributes).unwrap());
} else if name.local_name.contains("downSignal") {
dish = Some(
dish.unwrap()
.add_down_signal(Signal::try_from(attributes).unwrap()),
);
} else if name.local_name.contains("upSignal") {
dish = Some(
dish.unwrap()
.add_up_signal(Signal::try_from(attributes).unwrap()),
);
} else if name.local_name.contains("target") {
dish = Some(
dish.unwrap()
.add_target(Target::try_from(attributes).unwrap()),
);
}
}
Ok(XmlEvent::EndElement { name }) => {
if name.local_name.contains("dish") && station.is_some() && dish.is_some() {
station = Some(station.unwrap().add_dish(dish.unwrap()));
dish = None;
}
}
Ok(XmlEvent::EndDocument) => {
if let Some(station) = station {
stations.push(station);
break;
}
}
Err(e) => {
return Err(DsnRespParseError::from(e));
}
_ => {}
}
}
Ok(DsnResponse { stations })
}
}
#[derive(Clone, Debug)]
pub struct Station {
pub name: String,
pub friendly_name: String,
pub time_utc: u64,
pub tz_offset: i64,
pub dishes: Vec<Dish>,
}
impl Station {
pub fn add_dish(mut self, dish: Dish) -> Self {
self.dishes.push(dish);
self
}
}
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::<i64>()?;
Ok(Self {
name,
friendly_name,
time_utc,
tz_offset,
dishes: Vec::new(),
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SignalType {
Data,
Carrier,
None,
Unkown(String),
}
impl From<String> for SignalType {
fn from(s: String) -> Self {
match s.to_lowercase().as_str() {
"data" => SignalType::Data,
"carrier" => SignalType::Carrier,
"none" => SignalType::None,
_ => SignalType::Unkown(s),
}
}
}
impl Default for SignalType {
fn default() -> Self {
SignalType::None
}
}
#[derive(Clone, Debug, Default)]
pub struct Signal {
pub signal_type: SignalType,
pub data_rate: Option<f64>,
pub frequency: Option<f64>,
pub power: Option<f64>,
pub spacecraft: String,
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();
Ok(Self {
signal_type,
data_rate,
frequency,
power,
spacecraft,
spacecraft_id,
})
}
}
#[derive(Clone, Debug, Default)]
pub struct Target {
pub name: String,
pub id: u32,
/// Up Leg Range (km)
pub upleg_range: f64,
/// Down Leg Leg Range (km)
pub downleg_range: f64,
/// Round trip light time (s)
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>()?;
Ok(Self {
name,
id,
upleg_range,
downleg_range,
rtlt,
})
}
}
#[derive(Clone, Debug)]
pub struct Dish {
pub name: String,
pub azimuth_angle: Option<f64>,
pub elevation_angle: Option<f64>,
pub wind_speed: Option<f64>,
pub is_mspa: bool,
pub is_array: bool,
pub is_ddor: bool,
pub up_signal: Vec<Signal>,
pub down_signal: Vec<Signal>,
pub target: Vec<Target>,
}
impl Dish {
pub fn add_up_signal(mut self, signal: Signal) -> Self {
self.up_signal.push(signal);
self
}
pub fn add_down_signal(mut self, signal: Signal) -> Self {
self.down_signal.push(signal);
self
}
pub fn add_target(mut self, target: Target) -> Self {
self.target.push(target);
self
}
}
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>()?;
Ok(Self {
name,
azimuth_angle,
elevation_angle,
wind_speed,
is_mspa,
is_array,
is_ddor,
up_signal: Vec::new(),
down_signal: Vec::new(),
target: Vec::new(),
})
}
}
+2
View File
@@ -0,0 +1,2 @@
pub use crate::model::*;
pub use crate::DsnRespParseError;