use std::fmt;
use crate::util::{Date, DisplayOption, ParseError, RawPosition, Time};
#[derive(Debug, PartialEq, Eq)]
pub struct CRecordDeclaration<'a> {
pub date: Date,
pub time: Time,
pub flight_date: Option<Date>,
pub task_id: u16,
pub turnpoint_count: i8,
pub task_name: Option<&'a str>,
}
impl<'a> CRecordDeclaration<'a> {
pub fn parse(line: &'a str) -> Result<Self, ParseError> {
if line.len() < 25 {
return Err(ParseError::SyntaxError);
}
if !line.bytes().take(25).all(|b| b.is_ascii()) {
return Err(ParseError::NonASCIICharacters);
}
assert!(line.as_bytes()[0] == b'C');
let date = line[1..7].parse()?;
let time = line[7..13].parse()?;
let flight_date = match &line[13..19] {
"000000" => None,
s => Some(s.parse()?),
};
let task_id = line[19..23].parse::<u16>()?;
let turnpoint_count = line[23..25].parse::<i8>()?;
let task_name = if line.len() > 25 {
Some(&line[25..])
} else {
None
};
Ok(CRecordDeclaration {
date,
time,
flight_date,
task_id,
turnpoint_count,
task_name,
})
}
}
impl<'a> fmt::Display for CRecordDeclaration<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let flight_date = match &self.flight_date {
None => "000000".to_string(),
Some(flight_date) => flight_date.to_string(),
};
write!(
f,
"C{date}{time}{flight_date}{task_id:04}{tp_count:02}{task_name}",
date = self.date,
time = self.time,
flight_date = flight_date,
task_id = self.task_id,
tp_count = self.turnpoint_count,
task_name = DisplayOption(self.task_name)
)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct CRecordTurnpoint<'a> {
pub position: RawPosition,
pub turnpoint_name: Option<&'a str>,
}
impl<'a> CRecordTurnpoint<'a> {
pub fn parse(line: &'a str) -> Result<Self, ParseError> {
if line.len() < 18 {
return Err(ParseError::SyntaxError);
}
if !line.bytes().take(18).all(|b| b.is_ascii()) {
return Err(ParseError::NonASCIICharacters);
}
assert!(line.as_bytes()[0] == b'C');
let position = line[1..18].parse()?;
let turnpoint_name = if line.len() > 18 {
Some(&line[18..])
} else {
None
};
Ok(CRecordTurnpoint {
position,
turnpoint_name,
})
}
}
impl<'a> fmt::Display for CRecordTurnpoint<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"C{}{}",
self.position,
DisplayOption(self.turnpoint_name)
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::{Compass, RawLatitude, RawLongitude, RawPosition};
#[test]
fn c_record_declaration_parse() {
let sample_string = "C230718092044000000000204Foo task";
let parsed_declaration = CRecordDeclaration::parse(sample_string).unwrap();
let mut expected = CRecordDeclaration {
date: Date::from_dmy(23, 07, 18),
time: Time::from_hms(09, 20, 44),
flight_date: None,
task_id: 2,
turnpoint_count: 4,
task_name: Some("Foo task"),
};
assert_eq!(parsed_declaration, expected);
let sample_string = "C230718092044000000000204";
let parsed_declaration = CRecordDeclaration::parse(sample_string).unwrap();
expected.task_name = None;
assert_eq!(parsed_declaration, expected);
}
#[test]
fn c_record_declaration_parse_with_missing_content() {
assert!(CRecordDeclaration::parse("C").is_err());
}
#[test]
fn c_record_declaration_parse_with_invalid_char_boundary() {
assert!(CRecordDeclaration::parse("C𑠀𖭽ₐ𞸧\u{1daa1}").is_err());
}
#[test]
fn c_record_declaration_format() {
let expected_string = "C230718092044000000000204Foo task";
let mut declaration = CRecordDeclaration {
date: Date::from_dmy(23, 07, 18),
time: Time::from_hms(09, 20, 44),
flight_date: None,
task_id: 2,
turnpoint_count: 4,
task_name: Some("Foo task"),
};
assert_eq!(format!("{}", declaration), expected_string);
let expected_string = "C230718092044000000000204";
declaration.task_name = None;
assert_eq!(format!("{}", declaration), expected_string);
}
#[test]
fn c_record_declaration_format_with_negative_tp_count() {
let declaration = CRecordDeclaration {
date: Date::from_dmy(10, 05, 09),
time: Time::from_hms(12, 01, 53),
flight_date: Some(Date::from_dmy(10, 05, 09)),
task_id: 1,
turnpoint_count: -2,
task_name: None,
};
assert_eq!(format!("{}", declaration), "C1005091201531005090001-2");
}
#[test]
fn c_record_turnpoint_parse() {
let sample_string = "C5156040N00038120WLBZ-Leighton Buzzard NE";
let parsed_turnpoint = CRecordTurnpoint::parse(sample_string).unwrap();
let expected = CRecordTurnpoint {
position: RawPosition {
lat: RawLatitude::new(51, 56_040, Compass::North),
lon: RawLongitude::new(0, 38_120, Compass::West),
},
turnpoint_name: Some("LBZ-Leighton Buzzard NE"),
};
assert_eq!(parsed_turnpoint, expected);
}
#[test]
fn c_record_turnpoint_parse_with_missing_content() {
assert!(CRecordTurnpoint::parse("C").is_err());
}
#[test]
fn c_record_turnpoint_parse_with_invalid_char_boundary() {
assert!(CRecordTurnpoint::parse("C𑠀𖭽ₐ𞸧\u{1daa1}").is_err());
}
proptest! {
#[test]
#[allow(unused_must_use)]
fn parse_declaration_doesnt_crash(s in "C\\PC*") {
CRecordDeclaration::parse(&s);
}
#[test]
#[allow(unused_must_use)]
fn parse_turnpoint_doesnt_crash(s in "C\\PC*") {
CRecordTurnpoint::parse(&s);
}
}
}