Initial commit
+ Parsing working from a test XML file + All parsing is handled by the models + Clippy + fmtmain
commit
2dca331092
|
@ -0,0 +1,3 @@
|
||||||
|
/target
|
||||||
|
/Cargo.lock
|
||||||
|
/.idea
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "rust-dsn-parser"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = "0.9.15"
|
||||||
|
chrono = {version = "0.4.22", features=["serde"]}
|
||||||
|
xml-rs = "0.8.3"
|
|
@ -0,0 +1,50 @@
|
||||||
|
<dsn>
|
||||||
|
<station name="gdscc" friendlyName="Goldstone" timeUTC="1664054963000" timeZoneOffset="-25200000"/>
|
||||||
|
<dish name="DSS26" azimuthAngle="180.0" elevationAngle="89.80" windSpeed="1.852" isMSPA="false" isArray="false" isDDOR="false">
|
||||||
|
<downSignal signalType="none" dataRate="4250000" frequency="8475000000" power="-120.3788" spacecraft="KPLO" spacecraftID="-155"/>
|
||||||
|
<upSignal signalType="none" dataRate="1000" frequency="7188" power="0.0000" spacecraft="KPLO" spacecraftID="-155"/>
|
||||||
|
<downSignal signalType="none" dataRate="8192" frequency="2261000000" power="-161.6540" spacecraft="KPLO" spacecraftID="-155"/>
|
||||||
|
<target name="KPLO" id="155" uplegRange="-1.000e+00" downlegRange="-1.000e+00" rtlt="-1.0000"/>
|
||||||
|
</dish>
|
||||||
|
<dish name="DSS14" azimuthAngle="272.7" elevationAngle="25.50" windSpeed="15.43" isMSPA="false" isArray="false" isDDOR="false">
|
||||||
|
<downSignal signalType="carrier" dataRate="0.0000" frequency="8426000000" power="-147.3783" spacecraft="HYB2" spacecraftID="-37"/>
|
||||||
|
<target name="HYB2" id="37" uplegRange="1.950e+08" downlegRange="1.949e+08" rtlt="1301"/>
|
||||||
|
</dish>
|
||||||
|
<station name="mdscc" friendlyName="Madrid" timeUTC="1664054963000" timeZoneOffset="7200000"/>
|
||||||
|
<dish name="DSS65" azimuthAngle="" elevationAngle="" windSpeed="" isMSPA="false" isArray="false" isDDOR="false">
|
||||||
|
<downSignal signalType="data" dataRate="1250000" frequency="2282000000" power="-112.1945" spacecraft="MMS3" spacecraftID="-110"/>
|
||||||
|
<upSignal signalType="data" dataRate="2000" frequency="2101" power="0.2691" spacecraft="MMS3" spacecraftID="-110"/>
|
||||||
|
<target name="MMS3" id="110" uplegRange="1.817e+05" downlegRange="1.818e+05" rtlt="1.212"/>
|
||||||
|
</dish>
|
||||||
|
<dish name="DSS54" azimuthAngle="144.5" elevationAngle="51.96" windSpeed="18.52" isMSPA="false" isArray="false" isDDOR="false">
|
||||||
|
<downSignal signalType="data" dataRate="40000" frequency="2270000000" power="-120.4680" spacecraft="JWST" spacecraftID="-170"/>
|
||||||
|
<upSignal signalType="data" dataRate="16000" frequency="2090" power="5.086" spacecraft="JWST" spacecraftID="-170"/>
|
||||||
|
<downSignal signalType="data" dataRate="28000000" frequency="25900000000" power="-91.6436" spacecraft="JWST" spacecraftID="-170"/>
|
||||||
|
<target name="JWST" id="170" uplegRange="1.267e+06" downlegRange="1.267e+06" rtlt="8.455"/>
|
||||||
|
</dish>
|
||||||
|
<dish name="DSS63" azimuthAngle="125.7" elevationAngle="34.23" windSpeed="18.52" isMSPA="false" isArray="false" isDDOR="false">
|
||||||
|
<downSignal signalType="data" dataRate="200000" frequency="8404000000" power="-124.8344" spacecraft="JNO" spacecraftID="-61"/>
|
||||||
|
<upSignal signalType="data" dataRate="2000" frequency="7153" power="17.38" spacecraft="JNO" spacecraftID="-61"/>
|
||||||
|
<target name="JNO" id="61" uplegRange="5.922e+08" downlegRange="5.922e+08" rtlt="3951"/>
|
||||||
|
</dish>
|
||||||
|
<station name="cdscc" friendlyName="Canberra" timeUTC="1664054963000" timeZoneOffset="36000000"/>
|
||||||
|
<dish name="DSS34" azimuthAngle="" elevationAngle="" windSpeed="" isMSPA="false" isArray="false" isDDOR="false">
|
||||||
|
<downSignal signalType="none" dataRate="0.0000" frequency="2278000000" power="-478.9152" spacecraft="ACE" spacecraftID="-92"/>
|
||||||
|
<upSignal signalType="none" dataRate="1000" frequency="2098" power="0.0000" spacecraft="ACE" spacecraftID="-92"/>
|
||||||
|
<target name="ACE" id="92" uplegRange="1.566e+06" downlegRange="1.566e+06" rtlt="10.45"/>
|
||||||
|
</dish>
|
||||||
|
<dish name="DSS35" azimuthAngle="253.5" elevationAngle="37.87" windSpeed="0.0000" isMSPA="false" isArray="false" isDDOR="false">
|
||||||
|
<downSignal signalType="data" dataRate="312500" frequency="8422000000" power="-107.0920" spacecraft="DART" spacecraftID="-135"/>
|
||||||
|
<upSignal signalType="data" dataRate="2000" frequency="7168" power="18.22" spacecraft="DART" spacecraftID="-135"/>
|
||||||
|
<target name="DART" id="135" uplegRange="1.170e+07" downlegRange="1.170e+07" rtlt="78.08"/>
|
||||||
|
</dish>
|
||||||
|
<dish name="DSS43" azimuthAngle="27.84" elevationAngle="33.41" windSpeed="0.0000" isMSPA="false" isArray="false" isDDOR="false">
|
||||||
|
<downSignal signalType="carrier" dataRate="0.0000" frequency="8426000000" power="-146.7820" spacecraft="HYB2" spacecraftID="-37"/>
|
||||||
|
<target name="HYB2" id="37" uplegRange="1.950e+08" downlegRange="1.949e+08" rtlt="1301"/>
|
||||||
|
</dish>
|
||||||
|
<dish name="DSS36" azimuthAngle="155.8" elevationAngle="13.23" windSpeed="0.0000" isMSPA="false" isArray="false" isDDOR="false">
|
||||||
|
<downSignal signalType="none" dataRate="0.0000" frequency="26140000000" power="-166.2977" spacecraft="TEST" spacecraftID="-99"/>
|
||||||
|
<target name="TEST" id="99" uplegRange="-1.000e+00" downlegRange="-1.000e+00" rtlt="-1.0000"/>
|
||||||
|
</dish>
|
||||||
|
<timestamp>1664054963000</timestamp>
|
||||||
|
</dsn>
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
Loading…
Reference in New Issue