1use chrono::{DateTime, SecondsFormat, Utc};
2use quick_xml::Reader;
3use serde::de::Error as DeError;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6use crate::Error;
7
8pub const COT_BASE_EXAMPLE: &str = r#"
12<?xml version='1.0' standalone='yes'?>
13<event version="2.0"
14 uid="J-01334"
15 type="a-h-A-M-F-U-M"
16 time="2005-04-05T11:43:38.07Z"
17 start="2005-04-05T11:43:38.07Z"
18 stale="2005-04-05T11:45:38.07Z" >
19 <detail>
20 </detail>
21 <point lat="30.0090027" lon="-85.9578735" ce="45.3"
22 hae="-42.6" le="99.5" />
23</event>
24"#;
25
26#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
27#[serde(rename = "event")]
28pub struct Cot<D> {
29 #[serde(rename = "@version")]
30 pub version: String,
31 #[serde(rename = "@uid")]
32 pub uid: String,
33 #[serde(rename = "@type")]
34 pub cot_type: String,
35 #[serde(
36 rename = "@time",
37 serialize_with = "serialize_date",
38 deserialize_with = "deserialize_date"
39 )]
40 pub time: DateTime<Utc>,
41 #[serde(
42 rename = "@start",
43 serialize_with = "serialize_date",
44 deserialize_with = "deserialize_date"
45 )]
46 pub start: DateTime<Utc>,
47 #[serde(
48 rename = "@stale",
49 serialize_with = "serialize_date",
50 deserialize_with = "deserialize_date"
51 )]
52 pub stale: DateTime<Utc>,
53 #[serde(rename = "@how", skip_serializing_if = "Option::is_none")]
54 pub how: Option<String>,
55 #[serde(rename = "detail")]
56 pub detail: D,
57 #[serde(rename = "point")]
58 pub point: Point,
59}
60
61pub fn parse_cot_msg_type(text: &str) -> Result<String, Error> {
63 match xml_first_element_w_attr(text, "event", "type") {
64 Ok(Some(val)) => Ok(val),
65 _ => Err(Error::BadField("No element 'event' with attribute 'type'")),
66 }
67}
68
69pub fn xml_first_element_w_attr(
71 text: &str,
72 elt_name: &str,
73 attr_name: &str,
74) -> Result<Option<String>, Error> {
75 let mut reader = Reader::from_str(text);
76 reader.config_mut().trim_text(true);
77 loop {
78 match reader.read_event()? {
79 quick_xml::events::Event::Start(ref e) => {
81 if e.name().into_inner() == elt_name.as_bytes() {
82 for attr in e.attributes() {
83 let attr = attr?;
84 if attr.key.into_inner() == attr_name.as_bytes() {
85 return Ok(Some(String::from_utf8_lossy(&attr.value).to_string()));
86 }
87 }
88 }
89 }
90 quick_xml::events::Event::Eof => break,
91 _ => {}
92 }
93 }
94 Ok(None)
95}
96
97pub(crate) fn serialize_date<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
98where
99 S: Serializer,
100{
101 let s = date.to_rfc3339_opts(SecondsFormat::Millis, true );
102 serializer.serialize_str(&s)
103}
104
105pub(crate) fn deserialize_date<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
106where
107 D: Deserializer<'de>,
108{
109 let s = String::deserialize(deserializer)?;
110 DateTime::parse_from_rfc3339(&s)
111 .map_err(DeError::custom)
112 .map(|dt| dt.with_timezone(&Utc))
113}
114
115pub type CotBase = Cot<NoDetail>;
116
117#[derive(Serialize, Deserialize, Debug, PartialEq)]
118pub struct NoDetail {}
119
120#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
121pub struct Point {
122 #[serde(rename = "@lat")]
123 pub lat: f64,
124 #[serde(rename = "@lon")]
125 pub lon: f64,
126 #[serde(rename = "@ce")]
127 pub ce: f32,
128 #[serde(rename = "@hae")]
129 pub hae: f32,
130 #[serde(rename = "@le")]
131 pub le: f32,
132}
133
134impl Point {
135 pub fn north_pole() -> Self {
136 Self {
137 lat: 90.0,
138 lon: 0.0,
139 ce: 0.0,
140 hae: 0.0,
141 le: 0.0,
142 }
143 }
144}
145
146#[cfg(test)]
147mod test {
148 use super::*;
149 #[test]
150 fn test_serde_roundtrip() {
151 let cot0: CotBase = quick_xml::de::from_str(COT_BASE_EXAMPLE).unwrap();
154 let cot_str = quick_xml::se::to_string(&cot0).unwrap();
155 let cot1: CotBase = quick_xml::de::from_str(&cot_str).unwrap();
156 assert_eq!(cot0, cot1);
157 }
158}