use crate::{
encoder::NmeaEncode,
macros::{write_byte, write_str},
message::NmeaMessageError,
parser::NmeaParse,
};
#[derive(Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Txt<'a> {
pub total_messages: &'a str,
pub message_number: &'a str,
pub message_type: &'a str,
pub text: &'a str,
}
impl<'a> NmeaParse<'a> for Txt<'a> {
fn parse(fields: &'a str) -> Result<Self, NmeaMessageError> {
let mut f = fields.splitn(4, ',');
Ok(Self {
total_messages: f.next().ok_or(NmeaMessageError::MissingField)?,
message_number: f.next().ok_or(NmeaMessageError::MissingField)?,
message_type: f.next().ok_or(NmeaMessageError::MissingField)?,
text: f.next().ok_or(NmeaMessageError::MissingField)?,
})
}
}
impl NmeaEncode for Txt<'_> {
fn encoded_len(&self) -> usize {
self.total_messages.len()
+ self.message_number.len()
+ self.message_type.len()
+ self.text.len()
+ 3
}
fn encode(&self, buf: &mut [u8]) -> usize {
let mut pos = 0;
write_str!(buf, pos, self.total_messages);
write_byte!(buf, pos, b',');
write_str!(buf, pos, self.message_number);
write_byte!(buf, pos, b',');
write_str!(buf, pos, self.message_type);
write_byte!(buf, pos, b',');
write_str!(buf, pos, self.text);
pos
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TxtMessageType {
Error = 0,
Warning = 1,
Notice = 2,
Unknown = 7,
}
impl TxtMessageType {
#[must_use]
pub fn parse(raw: u8) -> Option<Self> {
match raw {
0 => Some(TxtMessageType::Error),
1 => Some(TxtMessageType::Warning),
2 => Some(TxtMessageType::Notice),
7 => Some(TxtMessageType::Unknown),
_ => None,
}
}
}
impl Txt<'_> {
#[must_use]
pub fn total_messages(&self) -> Option<u8> {
self.total_messages.parse().ok()
}
#[must_use]
pub fn message_number(&self) -> Option<u8> {
self.message_number.parse().ok()
}
#[must_use]
pub fn message_type(&self) -> Option<TxtMessageType> {
TxtMessageType::parse(self.message_type.parse().ok()?)
}
}
#[cfg(test)]
mod tests {
use super::*;
const FIELDS: &str = "01,01,02,u-blox AG - www.u-blox.com";
#[test]
fn parses_raw_fields() {
let txt = Txt::parse(FIELDS).unwrap();
assert_eq!(txt.total_messages, "01");
assert_eq!(txt.message_number, "01");
assert_eq!(txt.message_type, "02");
assert_eq!(txt.text, "u-blox AG - www.u-blox.com");
}
#[test]
fn total_messages_parsed() {
let txt = Txt::parse(FIELDS).unwrap();
assert_eq!(txt.total_messages(), Some(1));
}
#[test]
fn message_number_parsed() {
let txt = Txt::parse(FIELDS).unwrap();
assert_eq!(txt.message_number(), Some(1));
}
#[test]
fn message_type_error() {
let txt = Txt::parse("01,01,00,something went wrong").unwrap();
assert_eq!(txt.message_type().unwrap(), TxtMessageType::Error);
}
#[test]
fn message_type_warning() {
let txt = Txt::parse("01,01,01,something to note").unwrap();
assert_eq!(txt.message_type().unwrap(), TxtMessageType::Warning);
}
#[test]
fn message_type_notice() {
let txt = Txt::parse("01,01,02,informational").unwrap();
assert_eq!(txt.message_type().unwrap(), TxtMessageType::Notice);
}
#[test]
fn message_type_unknown() {
let txt = Txt::parse("01,01,07,informational").unwrap();
assert_eq!(txt.message_type().unwrap(), TxtMessageType::Unknown);
}
#[test]
fn message_type_random() {
let txt = Txt::parse("01,01,99,something").unwrap();
assert!(txt.message_type().is_none());
}
#[test]
fn text_with_commas() {
let txt = Txt::parse("01,01,02,hello, world, this has commas").unwrap();
assert_eq!(txt.text, "hello, world, this has commas");
}
#[test]
fn multi_message_sequence() {
let txt1 = Txt::parse("03,01,07,first part").unwrap();
let txt2 = Txt::parse("03,02,07,second part").unwrap();
let txt3 = Txt::parse("03,03,07,third part").unwrap();
assert_eq!(txt1.total_messages(), Some(3));
assert_eq!(txt2.message_number(), Some(2));
assert_eq!(txt3.message_number(), Some(3));
}
#[test]
fn missing_required_field() {
assert!(Txt::parse("01,01").is_err());
assert!(Txt::parse("01").is_err());
assert!(Txt::parse("").is_err());
}
#[test]
fn encode_round_trip() {
let input = "01,01,02,u-blox AG - www.u-blox.com";
let txt = Txt::parse(input).unwrap();
let mut buf = [0u8; 64];
let len = txt.encode(&mut buf);
let encoded = core::str::from_utf8(&buf[..len]).unwrap();
let txt2 = Txt::parse(encoded).unwrap();
assert_eq!(txt.total_messages, txt2.total_messages);
assert_eq!(txt.message_number, txt2.message_number);
assert_eq!(txt.message_type, txt2.message_type);
assert_eq!(txt.text, txt2.text);
}
#[test]
fn encoded_len_matches_actual() {
let txt = Txt::parse(FIELDS).unwrap();
let mut buf = [0u8; 64];
let len = txt.encode(&mut buf);
assert_eq!(len, txt.encoded_len());
}
}