Skip to main content

bgpkit_parser/parser/iters/
fallible.rs

1/*!
2Fallible iterator implementations that return Results, exposing parsing errors to users.
3
4These iterators complement the default iterators by returning `Result<T, ParserErrorWithBytes>`
5instead of silently skipping errors. This allows users to handle errors explicitly while
6maintaining backward compatibility with existing code.
7*/
8use crate::error::{ParserError, ParserErrorWithBytes};
9use crate::models::*;
10use crate::parser::BgpkitParser;
11use crate::{Elementor, Filterable};
12use std::io::Read;
13
14/// Fallible iterator over MRT records that returns parsing errors.
15///
16/// Unlike the default `RecordIterator`, this iterator returns `Result<MrtRecord, ParserErrorWithBytes>`
17/// allowing users to handle parsing errors explicitly instead of having them logged and skipped.
18pub struct FallibleRecordIterator<R> {
19    parser: BgpkitParser<R>,
20    elementor: Elementor,
21}
22
23impl<R> FallibleRecordIterator<R> {
24    pub(crate) fn new(parser: BgpkitParser<R>) -> Self {
25        FallibleRecordIterator {
26            parser,
27            elementor: Elementor::new(),
28        }
29    }
30}
31
32impl<R: Read> Iterator for FallibleRecordIterator<R> {
33    type Item = Result<MrtRecord, ParserErrorWithBytes>;
34
35    fn next(&mut self) -> Option<Self::Item> {
36        // Text-dump parsers have no MRT-record representation; short-circuit
37        // instead of repeatedly returning Unsupported errors from next_record().
38        if self.parser.text_dump_iter.is_some() {
39            return None;
40        }
41        loop {
42            match self.parser.next_record() {
43                Ok(record) => {
44                    // Apply filters if any are set
45                    let filters = &self.parser.filters;
46                    if filters.is_empty() {
47                        return Some(Ok(record));
48                    }
49
50                    // Special handling for PeerIndexTable - always pass through
51                    if let MrtMessage::TableDumpV2Message(TableDumpV2Message::PeerIndexTable(_)) =
52                        &record.message
53                    {
54                        let _ = self.elementor.record_to_elems(record.clone());
55                        return Some(Ok(record));
56                    }
57
58                    // Check if any elements from this record match the filters
59                    let elems = self.elementor.record_to_elems(record.clone());
60                    if elems.iter().any(|e| e.match_filters(filters)) {
61                        return Some(Ok(record));
62                    }
63                    // Record doesn't match filters, continue to next
64                    continue;
65                }
66                Err(e) if matches!(e.error, ParserError::EofExpected) => {
67                    // Normal end of file
68                    return None;
69                }
70                Err(e) => {
71                    // Return the error to the user
72                    return Some(Err(e));
73                }
74            }
75        }
76    }
77}
78
79/// Fallible iterator over BGP elements that returns parsing errors.
80///
81/// Unlike the default `ElemIterator`, this iterator returns `Result<BgpElem, ParserErrorWithBytes>`
82/// for each successfully parsed element, and surfaces any parsing errors encountered.
83pub struct FallibleElemIterator<R> {
84    cache_elems: Vec<BgpElem>,
85    record_iter: FallibleRecordIterator<R>,
86    elementor: Elementor,
87}
88
89impl<R> FallibleElemIterator<R> {
90    pub(crate) fn new(parser: BgpkitParser<R>) -> Self {
91        FallibleElemIterator {
92            record_iter: FallibleRecordIterator::new(parser),
93            cache_elems: vec![],
94            elementor: Elementor::new(),
95        }
96    }
97}
98
99impl<R: Read> Iterator for FallibleElemIterator<R> {
100    type Item = Result<BgpElem, ParserErrorWithBytes>;
101
102    fn next(&mut self) -> Option<Self::Item> {
103        loop {
104            // Fast path: drain streaming text-dump elems directly, with filter support.
105            if let Some(iter) = &mut self.record_iter.parser.text_dump_iter {
106                for elem in iter.by_ref() {
107                    if elem.match_filters(&self.record_iter.parser.filters) {
108                        return Some(Ok(elem));
109                    }
110                }
111                return None;
112            }
113
114            // First check if we have cached elements
115            if !self.cache_elems.is_empty() {
116                if let Some(elem) = self.cache_elems.pop() {
117                    if elem.match_filters(&self.record_iter.parser.filters) {
118                        return Some(Ok(elem));
119                    }
120                    // Element doesn't match filters, continue to next
121                    continue;
122                }
123            }
124
125            // Need to refill cache from next record
126            match self.record_iter.next() {
127                None => return None,
128                Some(Err(e)) => return Some(Err(e)),
129                Some(Ok(record)) => {
130                    let mut elems = self.elementor.record_to_elems(record);
131                    if elems.is_empty() {
132                        // No elements from this record, try next
133                        continue;
134                    }
135                    // Reverse to maintain order when popping
136                    elems.reverse();
137                    self.cache_elems = elems;
138                    continue;
139                }
140            }
141        }
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use std::io::Cursor;
149
150    /// Create a test parser with mock data that will cause parsing errors
151    fn create_test_parser_with_errors() -> BgpkitParser<Cursor<Vec<u8>>> {
152        // Create some invalid MRT data that will trigger parsing errors
153        let invalid_data = vec![
154            // MRT header with invalid type
155            0x00, 0x00, 0x00, 0x00, // timestamp
156            0xFF, 0xFF, // invalid type
157            0x00, 0x00, // subtype
158            0x00, 0x00, 0x00, 0x04, // length
159            0x00, 0x00, 0x00, 0x00, // dummy data
160        ];
161
162        let cursor = Cursor::new(invalid_data);
163        BgpkitParser::from_reader(cursor)
164    }
165
166    /// Create a test parser with valid data
167    fn create_test_parser_with_valid_data() -> BgpkitParser<Cursor<Vec<u8>>> {
168        // This would need actual valid MRT data - for now using empty data
169        // which will result in EOF
170        let cursor = Cursor::new(vec![]);
171        BgpkitParser::from_reader(cursor)
172    }
173
174    #[test]
175    fn test_fallible_record_iterator_with_errors() {
176        let parser = create_test_parser_with_errors();
177        let mut iter = parser.into_fallible_record_iter();
178
179        // First item should be an error
180        let result = iter.next();
181        assert!(result.is_some());
182        assert!(result.unwrap().is_err());
183    }
184
185    #[test]
186    fn test_fallible_record_iterator_eof() {
187        let parser = create_test_parser_with_valid_data();
188        let mut iter = parser.into_fallible_record_iter();
189
190        // Should return None on EOF
191        let result = iter.next();
192        assert!(result.is_none());
193    }
194
195    #[test]
196    fn test_fallible_elem_iterator_with_errors() {
197        let parser = create_test_parser_with_errors();
198        let mut iter = parser.into_fallible_elem_iter();
199
200        // First item should be an error
201        let result = iter.next();
202        assert!(result.is_some());
203        assert!(result.unwrap().is_err());
204    }
205
206    #[test]
207    fn test_fallible_elem_iterator_eof() {
208        let parser = create_test_parser_with_valid_data();
209        let mut iter = parser.into_fallible_elem_iter();
210
211        // Should return None on EOF
212        let result = iter.next();
213        assert!(result.is_none());
214    }
215}