Skip to main content

dial9_trace_format/
decoder.rs

1//! Streaming decoder for reading trace files.
2//!
3//! [`Decoder`] reads the file header, processes schema and string-pool frames,
4//! and yields events as [`DecodedFrame`] (owned) or [`DecodedFrameRef`]
5//! (zero-copy). It also implements [`Iterator`] and provides a
6//! [`for_each_event`](Decoder::for_each_event) callback API for
7//! allocation-free processing.
8
9use crate::codec::{
10    self, Frame, FrameRef, HEADER_SIZE, PoolEntry, PoolEntryRef, SchemaInfo, StackPoolEntry,
11    StackPoolEntryRef, WireTypeId,
12};
13use crate::schema::{SchemaEntry, SchemaRegistry};
14use crate::types::{FieldType, FieldValueRef, InternedStackFrames, InternedString, StackFrames};
15use std::collections::HashMap;
16use std::fmt;
17
18/// Error returned when the decoder cannot continue reading the stream.
19/// Because frames are not length-prefixed, a decode error is unrecoverable —
20/// the decoder cannot skip the malformed frame to find the next one.
21#[derive(Debug, Clone)]
22pub struct DecodeError {
23    pub pos: usize,
24    pub message: String,
25}
26
27impl fmt::Display for DecodeError {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "decode error at byte {}: {}", self.pos, self.message)
30    }
31}
32
33impl std::error::Error for DecodeError {}
34
35/// Error returned by [`Decoder::try_for_each_event`].
36#[derive(Debug)]
37pub enum TryForEachError<E> {
38    Decode(DecodeError),
39    User(E),
40}
41
42impl<E: fmt::Display> fmt::Display for TryForEachError<E> {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            TryForEachError::Decode(e) => write!(f, "{e}"),
46            TryForEachError::User(e) => write!(f, "{e}"),
47        }
48    }
49}
50
51impl<E: fmt::Display + fmt::Debug> std::error::Error for TryForEachError<E> {}
52
53/// A decoded event passed to [`Decoder::for_each_event`].
54///
55/// `'a` is the lifetime of the input data buffer (strings, stack frames borrow from it).
56/// `'f` is the lifetime of the `fields` slice and schema name (reused across calls).
57#[non_exhaustive]
58pub struct RawEvent<'a, 'f> {
59    pub type_id: WireTypeId,
60    pub name: &'f str,
61    pub timestamp_ns: Option<u64>,
62    pub fields: &'f [FieldValueRef<'a>],
63    pub schema: &'f SchemaEntry,
64    pub string_pool: &'f StringPool,
65    pub stack_pool: &'f StackPool,
66}
67
68impl<'a, 'f> RawEvent<'a, 'f> {
69    /// Field names from the schema, parallel to `fields`.
70    pub fn field_names(&self) -> impl Iterator<Item = &'f str> {
71        self.schema.fields.iter().map(|f| f.name.as_str())
72    }
73
74    /// Deserialize this event into a typed value `E` via serde.
75    ///
76    /// The deserializer presents the event as a flat map containing:
77    ///
78    /// 1. `"event"` → the schema name (the discriminant for
79    ///    `#[serde(tag = "event")]`).
80    /// 2. `"timestamp_ns"` → the absolute frame-header timestamp (only if
81    ///    the schema has `has_timestamp = true`).
82    /// 3. One entry per schema field, keyed by field name.
83    ///
84    /// Pool-resolved values appear as their resolved form: `PooledString`
85    /// presents as a string, `PooledStackFrames` presents as a sequence of
86    /// `u64`. See [`crate::de`] for details.
87    ///
88    /// Available only when the `serde-deserialize` feature is enabled.
89    #[cfg(feature = "serde-deserialize")]
90    pub fn deserialize<E: serde::de::DeserializeOwned>(&self) -> Result<E, crate::de::DeserError> {
91        crate::de::from_raw_event(self)
92    }
93}
94
95/// A map from interned string IDs to their resolved string values.
96///
97/// Populated automatically by the [`Decoder`] as it processes `StringPool` frames.
98/// Pass a reference to [`crate::TraceEvent::decode`] so that `InternedString` fields
99/// resolve to `&str` in derived `Ref` types.
100#[derive(Debug, Default)]
101pub struct StringPool(pub(crate) HashMap<InternedString, String>);
102
103impl StringPool {
104    pub(crate) fn new() -> Self {
105        Self(HashMap::default())
106    }
107
108    pub(crate) fn insert(&mut self, id: InternedString, value: String) {
109        self.0.insert(id, value);
110    }
111
112    pub fn get(&self, id: InternedString) -> Option<&str> {
113        self.0.get(&id).map(|s| s.as_str())
114    }
115
116    pub fn len(&self) -> usize {
117        self.0.len()
118    }
119
120    pub fn is_empty(&self) -> bool {
121        self.0.is_empty()
122    }
123
124    /// Iterate over all interned strings as `(id, value)` pairs.
125    pub fn iter(&self) -> impl Iterator<Item = (InternedString, &str)> {
126        self.0.iter().map(|(&id, v)| (id, v.as_str()))
127    }
128}
129
130/// A map from interned stack-frame IDs to their resolved address vectors.
131///
132/// Populated automatically by the [`Decoder`] as it processes `StackPool` frames.
133#[derive(Debug, Default)]
134pub struct StackPool(pub(crate) HashMap<InternedStackFrames, Vec<u64>>);
135
136impl StackPool {
137    pub(crate) fn new() -> Self {
138        Self(HashMap::default())
139    }
140
141    pub(crate) fn insert(&mut self, id: InternedStackFrames, frames: StackFrames) {
142        self.0.insert(id, frames.0);
143    }
144
145    pub fn get(&self, id: InternedStackFrames) -> Option<&[u64]> {
146        self.0.get(&id).map(|v| v.as_slice())
147    }
148
149    pub fn len(&self) -> usize {
150        self.0.len()
151    }
152
153    pub fn is_empty(&self) -> bool {
154        self.0.is_empty()
155    }
156
157    /// Iterate over all interned stack frames as `(id, frames)` pairs.
158    pub fn iter(&self) -> impl Iterator<Item = (InternedStackFrames, &[u64])> {
159        self.0.iter().map(|(&id, v)| (id, v.as_slice()))
160    }
161}
162
163/// Decoded events yielded by the decoder.
164#[derive(Debug, Clone, PartialEq)]
165pub enum DecodedFrame {
166    Schema(SchemaEntry),
167    Event {
168        type_id: WireTypeId,
169        /// Absolute timestamp in nanoseconds, if the schema has `has_timestamp`.
170        timestamp_ns: Option<u64>,
171        values: Vec<crate::types::FieldValue>,
172    },
173    StringPool(Vec<PoolEntry>),
174    StackPool(Vec<StackPoolEntry>),
175    SchemaAnnotations {
176        type_id: WireTypeId,
177        annotations: Vec<crate::schema::FieldAnnotation>,
178    },
179}
180
181/// Zero-copy decoded frame that borrows from the input buffer.
182#[derive(Debug, Clone, PartialEq)]
183pub enum DecodedFrameRef<'a> {
184    Schema(SchemaEntry),
185    Event {
186        type_id: WireTypeId,
187        timestamp_ns: Option<u64>,
188        values: Vec<FieldValueRef<'a>>,
189    },
190    StringPool(Vec<PoolEntryRef<'a>>),
191    StackPool(Vec<StackPoolEntryRef<'a>>),
192    SchemaAnnotations {
193        type_id: WireTypeId,
194        annotations: Vec<crate::schema::FieldAnnotation>,
195    },
196}
197
198struct SchemaCache {
199    entry: SchemaEntry,
200    /// Raw field type tags for fast decode (avoids re-extracting from entry.fields).
201    field_tags: Vec<u8>,
202}
203
204/// Streaming trace file decoder.
205///
206/// Reads from a byte slice, processing schema, string-pool, and event frames.
207/// Implements [`Iterator`] over [`DecodedFrameRef`] for convenient consumption.
208pub struct Decoder<'a> {
209    data: &'a [u8],
210    pos: usize,
211    registry: SchemaRegistry,
212    schema_cache: Vec<Option<SchemaCache>>,
213    string_pool: StringPool,
214    stack_pool: StackPool,
215    version: u8,
216    timestamp_base_ns: u64,
217}
218
219impl<'a> Decoder<'a> {
220    pub fn new(data: &'a [u8]) -> Option<Self> {
221        let version = codec::decode_header(data)?;
222        Some(Self {
223            data,
224            pos: HEADER_SIZE,
225            registry: SchemaRegistry::new(),
226            schema_cache: Vec::new(),
227            string_pool: StringPool::new(),
228            stack_pool: StackPool::new(),
229            version,
230            timestamp_base_ns: 0,
231        })
232    }
233
234    pub fn registry(&self) -> &SchemaRegistry {
235        &self.registry
236    }
237
238    pub fn version(&self) -> u8 {
239        self.version
240    }
241
242    /// Returns the current byte offset within the input data.
243    ///
244    /// After `next_frame()` returns `Ok(None)`, this should equal `data_len()`
245    /// for a well-formed, non-truncated trace. A mismatch indicates trailing
246    /// bytes that could not be decoded.
247    pub fn position(&self) -> usize {
248        self.pos
249    }
250
251    /// Returns the total length of the input data slice.
252    pub fn data_len(&self) -> usize {
253        self.data.len()
254    }
255
256    pub fn string_pool(&self) -> &StringPool {
257        &self.string_pool
258    }
259
260    pub fn stack_pool(&self) -> &StackPool {
261        &self.stack_pool
262    }
263
264    /// Reset decoder state (schemas, string pool, timestamp base) as if
265    /// starting a fresh stream. Used when a mid-stream header is encountered
266    /// (the "reset frame" pattern for concatenated thread-local batches).
267    fn reset_state(&mut self) {
268        self.registry = SchemaRegistry::new();
269        self.schema_cache.clear();
270        self.string_pool = StringPool::new();
271        self.stack_pool = StackPool::new();
272        self.timestamp_base_ns = 0;
273    }
274
275    /// If the current position starts with a valid header, reset state and
276    /// skip past it, returning true.
277    fn try_consume_reset_header(&mut self) -> bool {
278        if self.pos + HEADER_SIZE <= self.data.len()
279            && codec::decode_header(&self.data[self.pos..]).is_some()
280        {
281            self.reset_state();
282            self.pos += HEADER_SIZE;
283            true
284        } else {
285            false
286        }
287    }
288
289    /// Consume this decoder and create an [`Encoder`](crate::encoder::Encoder) that appends to the
290    /// decoded trace. The encoder inherits the string pool, schema registry,
291    /// and timestamp base so new frames are compatible with the existing data.
292    ///
293    /// No file header is written — the caller is responsible for concatenating
294    /// the encoder's output after the original trace bytes.
295    pub fn into_encoder<W: std::io::Write>(self, writer: W) -> crate::encoder::Encoder<W> {
296        crate::encoder::Encoder::from_decoder(
297            self.registry,
298            self.string_pool,
299            self.stack_pool,
300            self.timestamp_base_ns,
301            writer,
302        )
303    }
304
305    pub(crate) fn schema_info(&self, type_id: WireTypeId) -> Option<SchemaInfo<'_>> {
306        self.schema_cache
307            .get(type_id.0 as usize)
308            .and_then(|s| s.as_ref())
309            .map(|c| SchemaInfo {
310                field_tags: &c.field_tags,
311                has_timestamp: c.entry.has_timestamp,
312            })
313    }
314
315    fn register_schema(&mut self, type_id: WireTypeId, entry: SchemaEntry) -> Result<(), String> {
316        let idx = type_id.0 as usize;
317        if idx >= self.schema_cache.len() {
318            self.schema_cache.resize_with(idx + 1, || None);
319        }
320        self.schema_cache[idx] = Some(SchemaCache {
321            field_tags: entry.fields.iter().map(|f| f.field_type as u8).collect(),
322            entry: entry.clone(),
323        });
324        self.registry.register(type_id, entry)
325    }
326
327    /// Decode the next frame. Returns `Ok(None)` when stream is exhausted.
328    /// Returns `Err` if the stream is malformed (e.g. duplicate type_id with
329    /// a different schema).
330    pub fn next_frame(&mut self) -> Result<Option<DecodedFrame>, DecodeError> {
331        if self.pos >= self.data.len() {
332            return Ok(None);
333        }
334        if self.try_consume_reset_header() {
335            return self.next_frame();
336        }
337        let remaining = &self.data[self.pos..];
338        let base = self.timestamp_base_ns;
339        let (frame, consumed) =
340            match codec::decode_frame(remaining, |type_id| self.schema_info(type_id), base) {
341                Some(r) => r,
342                None => return Ok(None),
343            };
344        self.pos += consumed;
345        match frame {
346            Frame::Schema { type_id, entry } => {
347                let result = DecodedFrame::Schema(entry.clone());
348                self.register_schema(type_id, entry)
349                    .map_err(|msg| DecodeError {
350                        pos: self.pos,
351                        message: msg,
352                    })?;
353                Ok(Some(result))
354            }
355            Frame::Event {
356                type_id,
357                timestamp_ns,
358                values,
359            } => {
360                if let Some(ts) = timestamp_ns {
361                    self.timestamp_base_ns = ts;
362                }
363                Ok(Some(DecodedFrame::Event {
364                    type_id,
365                    timestamp_ns,
366                    values,
367                }))
368            }
369            Frame::StringPool(entries) => {
370                for e in &entries {
371                    if let Ok(s) = String::from_utf8(e.data.clone()) {
372                        self.string_pool.insert(InternedString(e.pool_id), s);
373                    }
374                }
375                Ok(Some(DecodedFrame::StringPool(entries)))
376            }
377            Frame::StackPool(entries) => {
378                for e in &entries {
379                    self.stack_pool
380                        .insert(InternedStackFrames(e.pool_id), e.frames.clone().into());
381                }
382                Ok(Some(DecodedFrame::StackPool(entries)))
383            }
384            Frame::TimestampReset(ts) => {
385                self.timestamp_base_ns = ts;
386                self.next_frame() // consume silently, return next real frame
387            }
388            Frame::SchemaAnnotations {
389                type_id,
390                annotations,
391            } => {
392                // Merge annotations into the cached schema (lenient: skip if unknown type_id)
393                if let Some(cache) = self
394                    .schema_cache
395                    .get_mut(type_id.0 as usize)
396                    .and_then(|s| s.as_mut())
397                {
398                    cache.entry.annotations.extend_from_slice(&annotations);
399                }
400                if let Some(entry) = self.registry.schemas.get_mut(&type_id) {
401                    entry.annotations.extend_from_slice(&annotations);
402                }
403                Ok(Some(DecodedFrame::SchemaAnnotations {
404                    type_id,
405                    annotations,
406                }))
407            }
408        }
409    }
410
411    /// Collect all remaining frames. Stops on error or end of stream.
412    pub fn decode_all(&mut self) -> Vec<DecodedFrame> {
413        let mut frames = Vec::new();
414        while let Ok(Some(f)) = self.next_frame() {
415            frames.push(f);
416        }
417        frames
418    }
419
420    /// Decode the next frame without copying field data. Returns `Ok(None)` when
421    /// stream is exhausted. Returns `Err` on malformed data.
422    pub fn next_frame_ref(&mut self) -> Result<Option<DecodedFrameRef<'a>>, DecodeError> {
423        if self.pos >= self.data.len() {
424            return Ok(None);
425        }
426        if self.try_consume_reset_header() {
427            return self.next_frame_ref();
428        }
429        let remaining = &self.data[self.pos..];
430        let base = self.timestamp_base_ns;
431        let (frame, consumed) =
432            match codec::decode_frame_ref(remaining, |type_id| self.schema_info(type_id), base) {
433                Some(r) => r,
434                None => return Ok(None),
435            };
436        self.pos += consumed;
437        match frame {
438            FrameRef::Schema { type_id, entry } => {
439                let result = DecodedFrameRef::Schema(entry.clone());
440                self.register_schema(type_id, entry)
441                    .map_err(|msg| DecodeError {
442                        pos: self.pos,
443                        message: msg,
444                    })?;
445                Ok(Some(result))
446            }
447            FrameRef::Event {
448                type_id,
449                timestamp_ns,
450                values,
451            } => {
452                if let Some(ts) = timestamp_ns {
453                    self.timestamp_base_ns = ts;
454                }
455                Ok(Some(DecodedFrameRef::Event {
456                    type_id,
457                    timestamp_ns,
458                    values,
459                }))
460            }
461            FrameRef::StringPool(entries) => {
462                for e in &entries {
463                    if let Ok(s) = std::str::from_utf8(e.data) {
464                        self.string_pool
465                            .insert(InternedString(e.pool_id), s.to_string());
466                    }
467                }
468                Ok(Some(DecodedFrameRef::StringPool(entries)))
469            }
470            FrameRef::StackPool(entries) => {
471                for e in &entries {
472                    self.stack_pool
473                        .insert(InternedStackFrames(e.pool_id), e.to_stack_frames());
474                }
475                Ok(Some(DecodedFrameRef::StackPool(entries)))
476            }
477            FrameRef::TimestampReset(ts) => {
478                self.timestamp_base_ns = ts;
479                self.next_frame_ref()
480            }
481            FrameRef::SchemaAnnotations {
482                type_id,
483                annotations,
484            } => {
485                if let Some(cache) = self
486                    .schema_cache
487                    .get_mut(type_id.0 as usize)
488                    .and_then(|s| s.as_mut())
489                {
490                    cache.entry.annotations.extend_from_slice(&annotations);
491                }
492                if let Some(entry) = self.registry.schemas.get_mut(&type_id) {
493                    entry.annotations.extend_from_slice(&annotations);
494                }
495                Ok(Some(DecodedFrameRef::SchemaAnnotations {
496                    type_id,
497                    annotations,
498                }))
499            }
500        }
501    }
502
503    /// Collect all remaining frames using zero-copy decoding. Stops on error or end of stream.
504    pub fn decode_all_ref(&mut self) -> Vec<DecodedFrameRef<'a>> {
505        let mut frames = Vec::new();
506        while let Ok(Some(f)) = self.next_frame_ref() {
507            frames.push(f);
508        }
509        frames
510    }
511
512    /// Process all events with a callback, avoiding per-event Vec allocations.
513    /// Schemas and string pools are registered automatically.
514    ///
515    /// The [`RawEvent`] passed to the callback borrows from the decoder's input
516    /// buffer. The `fields` slice is reused across calls, so values cannot be
517    /// stored across iterations without copying.
518    ///
519    /// Returns `Err` if the stream is malformed.
520    pub fn for_each_event(
521        &mut self,
522        mut f: impl for<'f> FnMut(RawEvent<'a, 'f>),
523    ) -> Result<(), DecodeError> {
524        self.try_for_each_event(|ev| {
525            f(ev);
526            Ok::<(), std::convert::Infallible>(())
527        })
528        .map_err(|e| match e {
529            TryForEachError::Decode(d) => d,
530            TryForEachError::User(inf) => match inf {},
531        })
532    }
533
534    /// Like [`for_each_event`](Self::for_each_event), but the callback may
535    /// return an error to stop iteration early.
536    pub fn try_for_each_event<E>(
537        &mut self,
538        mut f: impl for<'f> FnMut(RawEvent<'a, 'f>) -> Result<(), E>,
539    ) -> Result<(), TryForEachError<E>> {
540        let mut values_buf: Vec<FieldValueRef<'a>> = Vec::new();
541        while self.pos < self.data.len() {
542            let remaining = &self.data[self.pos..];
543            let tag = match remaining.first() {
544                Some(t) => *t,
545                None => break,
546            };
547            match tag {
548                codec::TAG_EVENT => {
549                    let mut pos = 1;
550                    let type_id = match remaining.get(pos..pos + 2) {
551                        Some(b) => {
552                            pos += 2;
553                            WireTypeId(u16::from_le_bytes(b.try_into().unwrap()))
554                        }
555                        None => {
556                            return Err(TryForEachError::Decode(DecodeError {
557                                pos: self.pos,
558                                message: "truncated event frame".into(),
559                            }));
560                        }
561                    };
562                    let cache = match self
563                        .schema_cache
564                        .get(type_id.0 as usize)
565                        .and_then(|s| s.as_ref())
566                    {
567                        Some(c) => c,
568                        None => {
569                            return Err(TryForEachError::Decode(DecodeError {
570                                pos: self.pos,
571                                message: format!("unknown type_id {type_id:?}"),
572                            }));
573                        }
574                    };
575
576                    let timestamp_ns = if cache.entry.has_timestamp {
577                        match codec::decode_u24_le(&remaining[pos..]) {
578                            Some(delta) => {
579                                pos += 3;
580                                Some(self.timestamp_base_ns + delta as u64)
581                            }
582                            None => {
583                                return Err(TryForEachError::Decode(DecodeError {
584                                    pos: self.pos + pos,
585                                    message: "truncated timestamp delta".into(),
586                                }));
587                            }
588                        }
589                    } else {
590                        None
591                    };
592
593                    values_buf.clear();
594                    for &ftag in &cache.field_tags {
595                        let inner_type = match FieldType::from_tag(ftag) {
596                            Some(ft) => ft,
597                            None => {
598                                return Err(TryForEachError::Decode(DecodeError {
599                                    pos: self.pos + pos,
600                                    message: format!("unknown field type tag {ftag:#x}"),
601                                }));
602                            }
603                        };
604                        if inner_type.is_optional() {
605                            match remaining.get(pos) {
606                                Some(0x00) => {
607                                    values_buf.push(FieldValueRef::None);
608                                    pos += 1;
609                                }
610                                Some(_) => {
611                                    pos += 1;
612                                    match FieldValueRef::decode(inner_type.inner(), remaining, pos)
613                                    {
614                                        Some((val, consumed)) => {
615                                            values_buf.push(val);
616                                            pos += consumed;
617                                        }
618                                        None => {
619                                            return Err(TryForEachError::Decode(DecodeError {
620                                                pos: self.pos + pos,
621                                                message: "truncated optional field value".into(),
622                                            }));
623                                        }
624                                    }
625                                }
626                                None => {
627                                    return Err(TryForEachError::Decode(DecodeError {
628                                        pos: self.pos + pos,
629                                        message: "truncated optional field prefix".into(),
630                                    }));
631                                }
632                            }
633                        } else {
634                            match FieldValueRef::decode(inner_type, remaining, pos) {
635                                Some((val, consumed)) => {
636                                    values_buf.push(val);
637                                    pos += consumed;
638                                }
639                                None => {
640                                    return Err(TryForEachError::Decode(DecodeError {
641                                        pos: self.pos + pos,
642                                        message: "truncated field value".into(),
643                                    }));
644                                }
645                            }
646                        }
647                    }
648                    // Update mutable state. The borrow checker allows this
649                    // because `cache` borrows `self.schema_cache` while we
650                    // mutate `self.pos` and `self.timestamp_base_ns`, which
651                    // are disjoint fields. We use a block with destructured
652                    // refs to make this explicit.
653                    {
654                        let Self {
655                            pos: self_pos,
656                            timestamp_base_ns,
657                            ..
658                        } = self;
659                        *self_pos += pos;
660                        if let Some(ts) = timestamp_ns {
661                            *timestamp_base_ns = ts;
662                        }
663                    }
664                    f(RawEvent {
665                        type_id,
666                        name: &cache.entry.name,
667                        timestamp_ns,
668                        fields: &values_buf,
669                        schema: &cache.entry,
670                        string_pool: &self.string_pool,
671                        stack_pool: &self.stack_pool,
672                    })
673                    .map_err(TryForEachError::User)?;
674                }
675                codec::TAG_TIMESTAMP_RESET => {
676                    let ts = match self.data.get(self.pos + 1..self.pos + 9) {
677                        Some(b) => u64::from_le_bytes(b.try_into().unwrap()),
678                        None => {
679                            return Err(TryForEachError::Decode(DecodeError {
680                                pos: self.pos,
681                                message: "truncated timestamp reset".into(),
682                            }));
683                        }
684                    };
685                    self.timestamp_base_ns = ts;
686                    self.pos += 9;
687                }
688                _ => {
689                    // Mid-stream header = reset frame (tag 0x54 = 'T' from TRC\0)
690                    if tag == codec::MAGIC[0] && self.try_consume_reset_header() {
691                        continue;
692                    }
693                    match self.next_frame_ref() {
694                        Ok(Some(_)) => {}
695                        Ok(None) => {
696                            return Err(TryForEachError::Decode(DecodeError {
697                                pos: self.pos,
698                                message: format!("failed to decode frame with tag 0x{tag:02x}"),
699                            }));
700                        }
701                        Err(e) => return Err(TryForEachError::Decode(e)),
702                    }
703                }
704            }
705        }
706        Ok(())
707    }
708
709    /// Returns an iterator that yields only [`DecodedFrameRef::Event`] variants,
710    /// silently consuming schema, string-pool, and symbol-table frames
711    /// (while still updating internal decoder state).
712    pub fn events(&mut self) -> EventIter<'_, 'a> {
713        EventIter { decoder: self }
714    }
715}
716
717impl<'a> Iterator for Decoder<'a> {
718    type Item = Result<DecodedFrameRef<'a>, DecodeError>;
719
720    fn next(&mut self) -> Option<Self::Item> {
721        self.next_frame_ref().transpose()
722    }
723}
724
725/// Iterator that yields only [`DecodedFrameRef::Event`] frames,
726/// consuming non-event frames to keep decoder state up to date.
727pub struct EventIter<'d, 'a> {
728    decoder: &'d mut Decoder<'a>,
729}
730
731impl<'d, 'a> Iterator for EventIter<'d, 'a> {
732    type Item = Result<DecodedFrameRef<'a>, DecodeError>;
733
734    fn next(&mut self) -> Option<Self::Item> {
735        loop {
736            match self.decoder.next()? {
737                Ok(frame @ DecodedFrameRef::Event { .. }) => return Some(Ok(frame)),
738                Ok(_) => continue, // schema, string pool, symbol table — skip
739                Err(e) => return Some(Err(e)),
740            }
741        }
742    }
743}
744
745#[cfg(test)]
746mod tests {
747    use super::*;
748    use crate::encoder::Encoder;
749    use crate::schema::FieldDef;
750    use crate::types::{FieldType, FieldValue};
751
752    #[test]
753    fn decode_empty_stream() {
754        let enc = Encoder::new();
755        let data = enc.finish();
756        let mut dec = Decoder::new(&data).unwrap();
757        assert_eq!(dec.version(), 1);
758        assert!(dec.next_frame().unwrap().is_none());
759    }
760
761    #[test]
762    fn decode_schema_frame() {
763        let mut enc = Encoder::new();
764        enc.register_schema(
765            "Ev",
766            vec![FieldDef {
767                name: "v".into(),
768                field_type: FieldType::Varint,
769            }],
770        )
771        .unwrap();
772        let data = enc.finish();
773        let mut dec = Decoder::new(&data).unwrap();
774        let frame = dec.next_frame().unwrap().unwrap();
775        assert!(matches!(frame, DecodedFrame::Schema(s) if s.name == "Ev"));
776    }
777
778    #[test]
779    fn decode_event_after_schema() {
780        let mut enc = Encoder::new();
781        let schema = enc
782            .register_schema(
783                "Ev",
784                vec![FieldDef {
785                    name: "v".into(),
786                    field_type: FieldType::Varint,
787                }],
788            )
789            .unwrap();
790        enc.write_event(
791            &schema,
792            &[FieldValue::Varint(1_000), FieldValue::Varint(42)],
793        )
794        .unwrap();
795        let data = enc.finish();
796
797        let mut dec = Decoder::new(&data).unwrap();
798        let frames = dec.decode_all();
799        assert_eq!(frames.len(), 2);
800        if let DecodedFrame::Event { values, .. } = &frames[1] {
801            assert_eq!(*values, vec![FieldValue::Varint(42)]);
802        } else {
803            panic!("expected event");
804        }
805    }
806
807    #[test]
808    fn decode_string_pool_builds_map() {
809        let mut enc = Encoder::new();
810        let id = enc.intern_string("hello").unwrap();
811        let data = enc.finish();
812
813        let mut dec = Decoder::new(&data).unwrap();
814        dec.decode_all();
815        assert_eq!(dec.string_pool().get(id), Some("hello"));
816    }
817
818    #[test]
819    fn decode_multiple_events() {
820        let mut enc = Encoder::new();
821        let schema = enc
822            .register_schema(
823                "Ev",
824                vec![FieldDef {
825                    name: "v".into(),
826                    field_type: FieldType::Varint,
827                }],
828            )
829            .unwrap();
830        for i in 0..10u64 {
831            enc.write_event(
832                &schema,
833                &[FieldValue::Varint(i * 1000), FieldValue::Varint(i)],
834            )
835            .unwrap();
836        }
837        let data = enc.finish();
838
839        let mut dec = Decoder::new(&data).unwrap();
840        let frames = dec.decode_all();
841        assert_eq!(frames.len(), 11);
842    }
843
844    #[test]
845    fn bad_header_returns_none() {
846        assert!(Decoder::new(&[0x00, 0x00, 0x00, 0x00, 1]).is_none());
847    }
848
849    #[test]
850    fn iterator_yields_all_frames() {
851        let mut enc = Encoder::new();
852        let schema = enc
853            .register_schema(
854                "Ev",
855                vec![FieldDef {
856                    name: "v".into(),
857                    field_type: FieldType::Varint,
858                }],
859            )
860            .unwrap();
861        for i in 0..3u64 {
862            enc.write_event(
863                &schema,
864                &[FieldValue::Varint(i * 1000), FieldValue::Varint(i)],
865            )
866            .unwrap();
867        }
868        let data = enc.finish();
869
870        let dec = Decoder::new(&data).unwrap();
871        let frames: Vec<_> = dec.collect::<Result<Vec<_>, _>>().unwrap();
872        // 1 schema + 3 events
873        assert_eq!(frames.len(), 4);
874        assert!(matches!(frames[0], DecodedFrameRef::Schema(_)));
875        assert!(matches!(frames[1], DecodedFrameRef::Event { .. }));
876    }
877
878    #[test]
879    fn iterator_early_termination() {
880        let mut enc = Encoder::new();
881        let schema = enc
882            .register_schema(
883                "Ev",
884                vec![FieldDef {
885                    name: "v".into(),
886                    field_type: FieldType::Varint,
887                }],
888            )
889            .unwrap();
890        for i in 0..10u64 {
891            enc.write_event(
892                &schema,
893                &[FieldValue::Varint(i * 1000), FieldValue::Varint(i)],
894            )
895            .unwrap();
896        }
897        let data = enc.finish();
898
899        let mut dec = Decoder::new(&data).unwrap();
900        // Take just 2 frames (schema + first event), don't decode the rest
901        let first_two: Vec<_> = dec.by_ref().take(2).collect::<Result<Vec<_>, _>>().unwrap();
902        assert_eq!(first_two.len(), 2);
903        // Decoder should still have remaining data
904        let next = dec.next();
905        assert!(next.is_some());
906    }
907
908    #[test]
909    fn events_iterator_skips_schema() {
910        let mut enc = Encoder::new();
911        let schema = enc
912            .register_schema(
913                "Ev",
914                vec![FieldDef {
915                    name: "v".into(),
916                    field_type: FieldType::Varint,
917                }],
918            )
919            .unwrap();
920        enc.write_event(
921            &schema,
922            &[FieldValue::Varint(1_000), FieldValue::Varint(42)],
923        )
924        .unwrap();
925        enc.write_event(
926            &schema,
927            &[FieldValue::Varint(2_000), FieldValue::Varint(99)],
928        )
929        .unwrap();
930        let data = enc.finish();
931
932        let mut dec = Decoder::new(&data).unwrap();
933        let events: Vec<_> = dec.events().collect::<Result<Vec<_>, _>>().unwrap();
934        // Only events, no schema frame
935        assert_eq!(events.len(), 2);
936        for ev in &events {
937            assert!(matches!(ev, DecodedFrameRef::Event { .. }));
938        }
939    }
940
941    #[test]
942    fn events_iterator_first_event_only() {
943        let mut enc = Encoder::new();
944        let schema = enc
945            .register_schema(
946                "Ev",
947                vec![FieldDef {
948                    name: "v".into(),
949                    field_type: FieldType::Varint,
950                }],
951            )
952            .unwrap();
953        for i in 0..5u64 {
954            enc.write_event(
955                &schema,
956                &[FieldValue::Varint(i * 1000), FieldValue::Varint(i)],
957            )
958            .unwrap();
959        }
960        let data = enc.finish();
961
962        let mut dec = Decoder::new(&data).unwrap();
963        // Get just the first event — schema is consumed internally
964        let first = dec.events().next().unwrap().unwrap();
965        assert!(matches!(first, DecodedFrameRef::Event { .. }));
966    }
967}