1use std::{
2 fmt::Display,
3 ops::{Deref, DerefMut},
4};
5
6use chrono::{Datelike, Timelike};
7use indexmap::IndexMap;
8
9#[derive(Debug)]
10pub struct SerializeError {
11 pub message: String,
12 pub offender: String,
13}
14
15impl Display for SerializeError {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 write!(f, "{}. Offending value: {}", self.message, self.offender)
18 }
19}
20
21#[derive(Debug, Clone, PartialEq)]
23pub enum AdifType {
24 Str(String),
26
27 Boolean(bool),
29
30 Number(f64),
32
33 Date(chrono::NaiveDate),
38
39 Time(chrono::NaiveTime),
45}
46
47impl AdifType {
48 pub fn get_data_type_indicator(&self) -> Option<char> {
50 match self {
51 AdifType::Str(_) => None,
52 AdifType::Boolean(_) => Some('B'),
53 AdifType::Number(_) => Some('N'),
54 AdifType::Date(_) => Some('D'),
55 AdifType::Time(_) => Some('T'),
56 }
57 }
58
59 pub fn serialize(&self, key_name: &str) -> Result<String, SerializeError> {
61 let value: Result<String, SerializeError> = match self {
63 AdifType::Str(val) => {
64 if val.contains('\n') {
66 return Err(SerializeError {
67 message: "String cannot contain linebreaks".to_string(),
68 offender: val.to_string(),
69 });
70 }
71 if !val.is_ascii() {
72 return Err(SerializeError {
73 message: "String must be ASCII".to_string(),
74 offender: val.to_string(),
75 });
76 }
77
78 Ok(val.to_string())
79 }
80 AdifType::Boolean(val) => Ok(match val {
81 true => "Y",
82 false => "N",
83 }
84 .to_string()),
85 AdifType::Number(val) => Ok(val.to_string()),
86 AdifType::Date(val) => {
87 if val.year() < 1930 {
89 return Err(SerializeError {
90 message: "Date must be >= 1930".to_string(),
91 offender: val.to_string(),
92 });
93 }
94
95 Ok(format!("{}{:02}{:02}", val.year(), val.month(), val.day()))
96 }
97 AdifType::Time(val) => Ok(format!(
98 "{:02}{:02}{:02}",
99 val.hour(),
100 val.minute(),
101 val.second()
102 )),
103 };
104 let value: &str = &(value?);
105
106 Ok(format!(
108 "<{}:{}{}>{}",
109 key_name.to_uppercase().replace(' ', "_"),
110 value.len(),
111 match self.get_data_type_indicator() {
112 Some(id) => format!(":{}", id),
113 None => String::new(),
114 },
115 value
116 ))
117 }
118}
119
120impl Display for AdifType {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 write!(f, "{:?}", self)
123 }
124}
125
126#[derive(Debug, Clone, PartialEq)]
128pub struct AdifRecord(IndexMap<String, AdifType>);
129
130impl AdifRecord {
131 pub fn serialize(&self) -> Result<String, SerializeError> {
133 let mut output = self
134 .0
135 .iter()
136 .map(|(key, value)| value.serialize(key))
137 .collect::<Result<Vec<String>, SerializeError>>()?
138 .join("");
139 output.push_str("<eor>");
140 Ok(output)
141 }
142}
143
144impl From<IndexMap<String, AdifType>> for AdifRecord {
145 fn from(map: IndexMap<String, AdifType>) -> Self {
146 Self(map)
147 }
148}
149
150impl<'a> From<IndexMap<&'a str, AdifType>> for AdifRecord {
151 fn from(map: IndexMap<&'a str, AdifType>) -> Self {
152 Self(
153 map.iter()
154 .map(|(key, value)| (key.to_string(), value.clone()))
155 .collect(),
156 )
157 }
158}
159
160impl Display for AdifRecord {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 write!(f, "{:?}", self.serialize())
163 }
164}
165
166impl Deref for AdifRecord {
167 type Target = IndexMap<String, AdifType>;
168
169 fn deref(&self) -> &Self::Target {
170 &self.0
171 }
172}
173
174impl DerefMut for AdifRecord {
175 fn deref_mut(&mut self) -> &mut Self::Target {
176 &mut self.0
177 }
178}
179
180#[derive(Debug, Clone, PartialEq)]
182pub struct AdifHeader(IndexMap<String, AdifType>);
183
184impl AdifHeader {
185 pub fn serialize(&self) -> Result<String, SerializeError> {
187 let mut output = String::new();
188 output.push_str(&format!(
189 "Generated {} (UTC)\n\n",
190 chrono::Utc::now().format("%Y-%m-%d %H:%M:%S")
191 ));
192
193 let header_tags = self
194 .0
195 .iter()
196 .map(|(key, value)| value.serialize(key))
197 .collect::<Result<Vec<String>, SerializeError>>()?
198 .join("\n");
199 output.push_str(&header_tags);
200
201 output.push('\n');
202 output.push_str("<EOH>");
203
204 Ok(output)
205 }
206}
207
208impl From<IndexMap<String, AdifType>> for AdifHeader {
209 fn from(map: IndexMap<String, AdifType>) -> Self {
210 Self(map)
211 }
212}
213
214impl<'a> From<IndexMap<&'a str, AdifType>> for AdifHeader {
215 fn from(map: IndexMap<&'a str, AdifType>) -> Self {
216 Self(
217 map.iter()
218 .map(|(key, value)| (key.to_string(), value.clone()))
219 .collect(),
220 )
221 }
222}
223
224impl Display for AdifHeader {
225 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226 write!(f, "{:?}", self.serialize())
227 }
228}
229
230impl Deref for AdifHeader {
231 type Target = IndexMap<String, AdifType>;
232
233 fn deref(&self) -> &Self::Target {
234 &self.0
235 }
236}
237
238impl DerefMut for AdifHeader {
239 fn deref_mut(&mut self) -> &mut Self::Target {
240 &mut self.0
241 }
242}
243
244#[derive(Debug, Clone, PartialEq)]
246pub struct AdifFile {
247 pub header: AdifHeader,
248 pub body: Vec<AdifRecord>,
249}
250
251impl AdifFile {
252 pub fn serialize(&self) -> Result<String, SerializeError> {
254 Ok(format!(
255 "{}\n{}",
256 self.header.serialize()?,
257 self.body
258 .iter()
259 .map(|record| record.serialize())
260 .collect::<Result<Vec<String>, SerializeError>>()?
261 .join("\n")
262 ))
263 }
264}
265
266#[cfg(test)]
267mod types_tests {
268 use chrono::{NaiveDate, NaiveTime};
269
270 use super::*;
271
272 #[test]
273 pub fn test_ser_string() {
274 assert_eq!(
275 AdifType::Str("Hello, world!".to_string())
276 .serialize("test")
277 .unwrap(),
278 "<TEST:13>Hello, world!"
279 );
280 }
281
282 #[test]
283 pub fn test_ser_bool() {
284 assert_eq!(
285 AdifType::Boolean(true).serialize("test").unwrap(),
286 "<TEST:1:B>Y"
287 );
288 assert_eq!(
289 AdifType::Boolean(false).serialize("test").unwrap(),
290 "<TEST:1:B>N"
291 );
292 }
293
294 #[test]
295 pub fn test_ser_num() {
296 assert_eq!(
297 AdifType::Number(3.5).serialize("test").unwrap(),
298 "<TEST:3:N>3.5"
299 );
300 assert_eq!(
301 AdifType::Number(-3.5).serialize("test").unwrap(),
302 "<TEST:4:N>-3.5"
303 );
304 assert_eq!(
305 AdifType::Number(-12.0).serialize("test").unwrap(),
306 "<TEST:3:N>-12"
307 );
308 }
309
310 #[test]
311 pub fn test_ser_date() {
312 assert_eq!(
313 AdifType::Date(NaiveDate::from_ymd_opt(2020, 2, 24).unwrap())
314 .serialize("test")
315 .unwrap(),
316 "<TEST:8:D>20200224"
317 );
318 assert!(AdifType::Date(NaiveDate::from_ymd_opt(1910, 2, 2).unwrap())
319 .serialize("test")
320 .is_err());
321 }
322
323 #[test]
324 pub fn test_ser_time() {
325 assert_eq!(
326 AdifType::Time(NaiveTime::from_hms_opt(23, 2, 5).unwrap())
327 .serialize("test")
328 .unwrap(),
329 "<TEST:6:T>230205"
330 );
331 }
332}
333
334#[cfg(test)]
335mod record_tests {
336
337 use indexmap::indexmap;
338
339 use super::*;
340
341 #[test]
342 pub fn test_ser_record() {
343 let test_record: AdifRecord = indexmap! {
344 "a number" => AdifType::Number(15.5),
345 "test string" => AdifType::Str("Heyo rusty friends!".to_string()),
346 }
347 .into();
348
349 assert_eq!(
350 test_record.serialize().unwrap(),
351 "<A_NUMBER:4:N>15.5<TEST_STRING:19>Heyo rusty friends!<eor>"
352 );
353 }
354
355 #[test]
356 pub fn test_ser_header() {
357 let test_header: AdifHeader = indexmap! {
358 "a number" => AdifType::Number(15.5),
359 "test string" => AdifType::Str("Heyo rusty friends!".to_string()),
360 }
361 .into();
362
363 let serialized_lines = test_header.serialize().unwrap();
364 let mut serialized_lines = serialized_lines.lines();
365
366 serialized_lines.next();
368 serialized_lines.next();
369
370 assert_eq!(serialized_lines.next(), Some("<A_NUMBER:4:N>15.5"));
372 assert_eq!(
373 serialized_lines.next(),
374 Some("<TEST_STRING:19>Heyo rusty friends!")
375 );
376 assert_eq!(serialized_lines.next(), Some("<EOH>"));
377 }
378}