Skip to main content

nmea_kit/nmea/
field.rs

1//! Field parsing and formatting helpers for NMEA sentence fields.
2//!
3//! [`FieldReader`] reads fields sequentially from a parsed frame.
4//! [`FieldWriter`] builds fields sequentially for encoding.
5
6/// Sequential field reader for NMEA sentence parsing.
7///
8/// Wraps a slice of `&str` fields and reads them in order,
9/// advancing an internal index after each read.
10pub struct FieldReader<'a> {
11    fields: &'a [&'a str],
12    idx: usize,
13}
14
15impl<'a> FieldReader<'a> {
16    pub fn new(fields: &'a [&'a str]) -> Self {
17        Self { fields, idx: 0 }
18    }
19
20    /// Read an optional f32 and advance.
21    pub fn f32(&mut self) -> Option<f32> {
22        let val = self.fields.get(self.idx).and_then(|f| {
23            if f.is_empty() {
24                None
25            } else {
26                f.parse::<f32>().ok()
27            }
28        });
29        self.idx += 1;
30        val
31    }
32
33    /// Read an optional f64 and advance.
34    pub fn f64(&mut self) -> Option<f64> {
35        let val = self.fields.get(self.idx).and_then(|f| {
36            if f.is_empty() {
37                None
38            } else {
39                f.parse::<f64>().ok()
40            }
41        });
42        self.idx += 1;
43        val
44    }
45
46    /// Read an optional u8 and advance.
47    pub fn u8(&mut self) -> Option<u8> {
48        let val = self.fields.get(self.idx).and_then(|f| {
49            if f.is_empty() {
50                None
51            } else {
52                f.parse::<u8>().ok()
53            }
54        });
55        self.idx += 1;
56        val
57    }
58
59    /// Read an optional u32 and advance.
60    pub fn u32(&mut self) -> Option<u32> {
61        let val = self.fields.get(self.idx).and_then(|f| {
62            if f.is_empty() {
63                None
64            } else {
65                f.parse::<u32>().ok()
66            }
67        });
68        self.idx += 1;
69        val
70    }
71
72    /// Read an optional single character and advance.
73    pub fn char(&mut self) -> Option<char> {
74        let val = self
75            .fields
76            .get(self.idx)
77            .and_then(|f| f.chars().next().filter(|_| !f.is_empty()));
78        self.idx += 1;
79        val
80    }
81
82    /// Read an optional non-empty string and advance.
83    pub fn string(&mut self) -> Option<String> {
84        let val = self.fields.get(self.idx).and_then(|f| {
85            if f.is_empty() {
86                None
87            } else {
88                Some((*f).to_string())
89            }
90        });
91        self.idx += 1;
92        val
93    }
94
95    /// Skip one field (fixed indicator) and advance.
96    pub fn skip(&mut self) {
97        self.idx += 1;
98    }
99}
100
101/// Sequential field writer for NMEA sentence encoding.
102///
103/// Builds a `Vec<String>` of field values in wire order.
104pub struct FieldWriter {
105    fields: Vec<String>,
106}
107
108impl FieldWriter {
109    pub fn new() -> Self {
110        Self { fields: Vec::new() }
111    }
112
113    /// Write an optional f32. `None` → empty field.
114    pub fn f32(&mut self, value: Option<f32>) {
115        self.fields.push(match value {
116            Some(v) => format!("{v}"),
117            None => String::new(),
118        });
119    }
120
121    /// Write an optional f64. `None` → empty field.
122    pub fn f64(&mut self, value: Option<f64>) {
123        self.fields.push(match value {
124            Some(v) => format!("{v}"),
125            None => String::new(),
126        });
127    }
128
129    /// Write an optional u8. `None` → empty field.
130    pub fn u8(&mut self, value: Option<u8>) {
131        self.fields.push(match value {
132            Some(v) => v.to_string(),
133            None => String::new(),
134        });
135    }
136
137    /// Write an optional u32. `None` → empty field.
138    pub fn u32(&mut self, value: Option<u32>) {
139        self.fields.push(match value {
140            Some(v) => v.to_string(),
141            None => String::new(),
142        });
143    }
144
145    /// Write an optional char. `None` → empty field.
146    pub fn char(&mut self, value: Option<char>) {
147        self.fields.push(match value {
148            Some(c) => c.to_string(),
149            None => String::new(),
150        });
151    }
152
153    /// Write a fixed indicator character (always emitted).
154    pub fn fixed(&mut self, c: char) {
155        self.fields.push(c.to_string());
156    }
157
158    /// Write an optional string. `None` → empty field.
159    pub fn string(&mut self, value: Option<&str>) {
160        self.fields.push(value.unwrap_or("").to_string());
161    }
162
163    /// Consume and return the built field list.
164    pub fn finish(self) -> Vec<String> {
165        self.fields
166    }
167}
168
169impl Default for FieldWriter {
170    fn default() -> Self {
171        Self::new()
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn reader_char() {
181        let fields = &["T", "", "AB"];
182        let mut r = FieldReader::new(fields);
183        assert_eq!(r.char(), Some('T'));
184        assert_eq!(r.char(), None);
185        assert_eq!(r.char(), Some('A')); // takes first char
186    }
187
188    #[test]
189    fn reader_f32() {
190        let fields = &["270.0", "", "abc"];
191        let mut r = FieldReader::new(fields);
192        assert_eq!(r.f32(), Some(270.0));
193        assert_eq!(r.f32(), None);
194        assert_eq!(r.f32(), None); // invalid
195    }
196
197    #[test]
198    fn reader_past_end() {
199        let fields: &[&str] = &[];
200        let mut r = FieldReader::new(fields);
201        assert_eq!(r.f32(), None);
202        assert_eq!(r.char(), None);
203    }
204
205    #[test]
206    fn reader_skip() {
207        let fields = &["10.0", "T", "20.0"];
208        let mut r = FieldReader::new(fields);
209        assert_eq!(r.f32(), Some(10.0));
210        r.skip();
211        assert_eq!(r.f32(), Some(20.0));
212    }
213
214    #[test]
215    fn reader_string() {
216        let fields = &["DEST", ""];
217        let mut r = FieldReader::new(fields);
218        assert_eq!(r.string(), Some("DEST".to_string()));
219        assert_eq!(r.string(), None);
220    }
221
222    #[test]
223    fn writer_roundtrip() {
224        let mut w = FieldWriter::new();
225        w.f32(Some(270.0));
226        w.fixed('T');
227        w.f32(None);
228        w.fixed('M');
229        let fields = w.finish();
230        assert_eq!(fields, vec!["270", "T", "", "M"]);
231    }
232}