Initial commit
+ Parsing working from a test XML file + All parsing is handled by the models + Clippy + fmt
This commit is contained in:
6 files changed
+437
No files matched your search
+85
@@ -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
@@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub use crate::model::*;
|
||||
pub use crate::DsnRespParseError;
|
||||
Reference in new issue
Block a user