Skip to main content

dial9_trace_format/
encoder.rs

1//! High-level encoder for writing trace files.
2//!
3//! [`Encoder`] writes the file header, registers schemas, interns strings, and
4//! encodes events with delta-compressed timestamps. It is the primary entry
5//! point for producing trace data.
6
7use crate::TraceEvent;
8use crate::codec::{self, PoolEntry, StackPoolEntry, WireTypeId};
9use crate::schema::{SchemaEntry, SchemaRegistry};
10use crate::types::{
11    CountingWriter, EncodeState, EventEncoder, InternedStackFrames, InternedString,
12};
13use std::any::TypeId;
14use std::collections::{HashMap, HashSet};
15use std::hash::{BuildHasherDefault, Hasher};
16use std::io::{self, Write};
17use std::sync::Arc;
18
19/// A fast, non-cryptographic hasher using FxHash's multiply-shift strategy.
20///
21/// For HashMap keys that are already well-distributed (TypeId, Arc<str>), this
22/// avoids hash collisions.
23#[doc(hidden)]
24#[derive(Default)]
25pub struct FxHasher(u64);
26
27impl FxHasher {
28    #[inline]
29    fn hash_word(&mut self, word: u64) {
30        self.0 = (self.0.rotate_left(5) ^ word).wrapping_mul(0x517cc1b727220a95);
31    }
32}
33
34impl Hasher for FxHasher {
35    #[inline]
36    fn write(&mut self, mut bytes: &[u8]) {
37        while bytes.len() >= 8 {
38            self.hash_word(u64::from_ne_bytes(bytes[..8].try_into().unwrap()));
39            bytes = &bytes[8..];
40        }
41        if bytes.len() >= 4 {
42            self.hash_word(u32::from_ne_bytes(bytes[..4].try_into().unwrap()) as u64);
43            bytes = &bytes[4..];
44        }
45        for &b in bytes {
46            self.hash_word(b as u64);
47        }
48    }
49
50    #[inline]
51    fn write_u8(&mut self, i: u8) {
52        self.hash_word(i as u64);
53    }
54
55    #[inline]
56    fn write_u16(&mut self, i: u16) {
57        self.hash_word(i as u64);
58    }
59
60    #[inline]
61    fn write_u32(&mut self, i: u32) {
62        self.hash_word(i as u64);
63    }
64
65    #[inline]
66    fn write_u64(&mut self, i: u64) {
67        self.hash_word(i);
68    }
69
70    #[inline]
71    fn write_usize(&mut self, i: usize) {
72        self.hash_word(i as u64);
73    }
74
75    #[inline]
76    fn write_u128(&mut self, i: u128) {
77        self.hash_word(i as u64);
78        self.hash_word((i >> 64) as u64);
79    }
80
81    #[inline]
82    fn finish(&self) -> u64 {
83        self.0
84    }
85}
86
87#[doc(hidden)]
88pub type FxBuildHasher = BuildHasherDefault<FxHasher>;
89#[doc(hidden)]
90pub type FxHashMap<K, V> = HashMap<K, V, FxBuildHasher>;
91#[doc(hidden)]
92pub type FxHashSet<T> = HashSet<T, FxBuildHasher>;
93
94/// A schema handle returned by [`Encoder::register_schema`] or created via
95/// [`Schema::new`].
96///
97/// Carries the full schema definition (name + fields) so it can auto-register
98/// itself with any encoder on first use. This means a `Schema` created on one
99/// encoder can be passed to a different encoder and it will just work.
100///
101/// `Schema` is cheap to clone (internally `Arc`-backed).
102#[derive(Clone, Debug)]
103pub struct Schema {
104    pub(crate) entry: Arc<SchemaEntry>,
105    /// Pre-computed `Arc<str>` of the schema name, used as a cheap HashMap key
106    /// (clone is a pointer bump instead of a String allocation).
107    name_key: Arc<str>,
108}
109
110impl Schema {
111    /// Create a schema handle without an encoder.
112    ///
113    /// The schema will be lazily registered the first time it is passed to
114    /// [`Encoder::write_event`].
115    pub fn new(name: &str, fields: Vec<crate::schema::FieldDef>) -> Self {
116        let name_key: Arc<str> = Arc::from(name);
117        Self {
118            entry: Arc::new(SchemaEntry {
119                name: name.to_string(),
120                has_timestamp: true,
121                fields,
122                annotations: Vec::new(),
123            }),
124            name_key,
125        }
126    }
127
128    /// Create a schema handle from a complete [`SchemaEntry`].
129    pub fn from_entry(entry: SchemaEntry) -> Self {
130        let name_key: Arc<str> = Arc::from(entry.name.as_str());
131        Self {
132            entry: Arc::new(entry),
133            name_key,
134        }
135    }
136
137    /// Schema name.
138    pub fn name(&self) -> &str {
139        &self.entry.name
140    }
141
142    /// Schema field definitions.
143    pub fn fields(&self) -> &[crate::schema::FieldDef] {
144        &self.entry.fields
145    }
146}
147
148/// Key for schema lookup — either by name (manual registration) or by Rust
149/// `TypeId` (derive macro path).
150#[derive(Clone, PartialEq, Eq, Hash)]
151enum SchemaKey {
152    Name(Arc<str>),
153    RustType(TypeId),
154}
155
156/// Trace file encoder.
157///
158/// Writes the binary file header, registers event schemas, interns strings
159/// into a pool, and encodes events with delta-compressed timestamps.
160///
161/// The default type parameter (`Vec<u8>`) buffers everything in memory;
162/// use [`Encoder::new_to`] to write to an arbitrary [`Write`] sink.
163pub struct Encoder<W: Write = Vec<u8>> {
164    state: EncodeState<W>,
165    registry: SchemaRegistry,
166    string_pool: FxHashMap<String, u32>,
167    next_pool_id: u32,
168    stack_pool: FxHashMap<Box<[u64]>, u32>,
169    next_stack_pool_id: u32,
170    schema_ids: FxHashMap<SchemaKey, WireTypeId>,
171    /// Per-type dense cache keyed by `TraceEvent::type_slot()`.
172    /// Stores `wire_id + 1` so that `0` means "unset".
173    slot_cache: Vec<u32>,
174    /// Bitset over `0..STATIC_WIRE_ID_LIMIT`: which fast-path wire IDs (type
175    /// slots) have had their schema frame emitted on this encoder. 256 bits =
176    /// 32 bytes inline.
177    registered_ids: [u64; (crate::STATIC_WIRE_ID_LIMIT as usize) / 64],
178}
179
180impl Default for Encoder<Vec<u8>> {
181    fn default() -> Self {
182        Self::new()
183    }
184}
185
186impl Encoder<Vec<u8>> {
187    pub fn new() -> Self {
188        let mut buf = Vec::new();
189        codec::encode_header(&mut buf).expect("Vec::write_all cannot fail");
190        Self {
191            state: EncodeState::new(buf),
192            registry: SchemaRegistry::new(),
193            string_pool: FxHashMap::default(),
194            next_pool_id: 0,
195            stack_pool: FxHashMap::default(),
196            next_stack_pool_id: 0,
197            schema_ids: FxHashMap::default(),
198            slot_cache: Vec::new(),
199            registered_ids: [0; (crate::STATIC_WIRE_ID_LIMIT as usize) / 64],
200        }
201    }
202
203    /// Consume the encoder and return the encoded bytes.
204    pub fn finish(self) -> Vec<u8> {
205        self.state.writer.into_inner()
206    }
207}
208
209impl<W: Write> Encoder<W> {
210    /// Create an encoder that writes to an arbitrary writer.
211    /// Writes the file header immediately.
212    pub fn new_to(mut writer: W) -> io::Result<Self> {
213        codec::encode_header(&mut writer)?;
214        Ok(Self {
215            state: EncodeState::new(writer),
216            registry: SchemaRegistry::new(),
217            string_pool: FxHashMap::default(),
218            next_pool_id: 0,
219            stack_pool: FxHashMap::default(),
220            next_stack_pool_id: 0,
221            schema_ids: FxHashMap::default(),
222            slot_cache: Vec::new(),
223            registered_ids: [0; (crate::STATIC_WIRE_ID_LIMIT as usize) / 64],
224        })
225    }
226
227    /// Create an encoder seeded from decoded state. Used by
228    /// [`Decoder::into_encoder`](crate::decoder::Decoder::into_encoder).
229    pub(crate) fn from_decoder(
230        mut registry: SchemaRegistry,
231        string_pool: crate::decoder::StringPool,
232        stack_pool: crate::decoder::StackPool,
233        timestamp_base_ns: u64,
234        writer: W,
235    ) -> Self {
236        let mut pool = FxHashMap::default();
237        let mut next_pool_id: u32 = 0;
238        for (id, value) in string_pool.0.into_iter() {
239            pool.insert(value, id.raw_id());
240            if id.raw_id() >= next_pool_id {
241                next_pool_id = id.raw_id() + 1;
242            }
243        }
244
245        let mut new_stack_pool: FxHashMap<Box<[u64]>, u32> = FxHashMap::default();
246        let mut next_stack_pool_id: u32 = 0;
247        for (id, frames) in stack_pool.0.into_iter() {
248            new_stack_pool.insert(frames.into_boxed_slice(), id.raw_id());
249            if id.raw_id() >= next_stack_pool_id {
250                next_stack_pool_id = id.raw_id() + 1;
251            }
252        }
253
254        let mut schema_ids = FxHashMap::default();
255        for (wire_id, entry) in registry.entries() {
256            schema_ids.insert(SchemaKey::Name(Arc::from(entry.name.as_str())), wire_id);
257        }
258        registry.sync_next_id();
259
260        let mut state = EncodeState::new(writer);
261        state.set_ts_base_unchecked(timestamp_base_ns);
262
263        Self {
264            state,
265            registry,
266            string_pool: pool,
267            next_pool_id,
268            stack_pool: new_stack_pool,
269            next_stack_pool_id,
270            schema_ids,
271            slot_cache: Vec::new(),
272            registered_ids: [0; (crate::STATIC_WIRE_ID_LIMIT as usize) / 64],
273        }
274    }
275
276    /// Consume the encoder and return the inner writer.
277    pub fn into_inner(self) -> W {
278        self.state.writer.into_inner()
279    }
280
281    /// Borrow the inner writer.
282    pub fn as_inner(&self) -> &W {
283        self.state.writer.inner()
284    }
285
286    /// Total bytes written through this encoder (including the file header).
287    pub fn bytes_written(&self) -> u64 {
288        self.state.writer.bytes_written()
289    }
290
291    /// Reset the encoder to a new writer, preserving internal allocations.
292    /// Returns the old writer. Writes a file header to the new writer.
293    pub fn reset_to(&mut self, mut new_writer: W) -> io::Result<W> {
294        codec::encode_header(&mut new_writer)?;
295        self.string_pool.clear();
296        self.next_pool_id = 0;
297        self.stack_pool.clear();
298        self.next_stack_pool_id = 0;
299        self.registry.clear();
300        self.schema_ids.clear();
301        self.slot_cache.fill(0);
302        self.registered_ids.fill(0);
303        // creating a new EncodeState resets the timestamp delta
304        let old_state = std::mem::replace(&mut self.state, EncodeState::new(new_writer));
305        Ok(old_state.writer.into_inner())
306    }
307
308    /// Ensure a schema is registered with this encoder. Returns the wire type
309    /// ID for this encoder's output stream.
310    ///
311    /// Idempotent if the schema matches. Errors if a different schema was
312    /// already registered under the same name.
313    fn ensure_registered(&mut self, schema: &Schema) -> io::Result<WireTypeId> {
314        let key = SchemaKey::Name(Arc::clone(&schema.name_key));
315        if let Some(&wire_id) = self.schema_ids.get(&key) {
316            // TODO: unify registry and schema_ids to avoid this error case
317            let Some(existing) = self.registry.get(wire_id) else {
318                return Err(io::Error::other(format!(
319                    "corrupted internal state. {wire_id:?} in schema_ids but not in registry."
320                )));
321            };
322            if *existing == *schema.entry {
323                return Ok(wire_id);
324            }
325            return Err(io::Error::new(
326                io::ErrorKind::InvalidInput,
327                format!(
328                    "schema already registered with different definition: {}",
329                    schema.name()
330                ),
331            ));
332        }
333        let id = self.registry.next_type_id();
334        codec::encode_schema(id, &schema.entry, &mut self.state.writer)?;
335        if !schema.entry.annotations.is_empty() {
336            codec::encode_schema_annotations(
337                id,
338                &schema.entry.annotations,
339                &mut self.state.writer,
340            )?;
341        }
342        self.registry
343            .register(id, (*schema.entry).clone())
344            .expect("schema registration failed");
345        self.schema_ids.insert(key, id);
346        Ok(id)
347    }
348
349    /// Register a schema by name. Returns a [`Schema`] handle that can be
350    /// passed to [`write_event`](Self::write_event) (on this or any other
351    /// encoder).
352    ///
353    /// All schemas have timestamps. When writing events, the first element of
354    /// `values` must be `FieldValue::Varint(timestamp_ns)`. It is extracted and
355    /// encoded in the event header (not as a regular field).
356    ///
357    /// Eagerly writes the schema frame. Idempotent if the definition matches.
358    pub fn register_schema(
359        &mut self,
360        name: &str,
361        fields: Vec<crate::schema::FieldDef>,
362    ) -> io::Result<Schema> {
363        let schema = Schema::new(name, fields);
364        self.ensure_registered(&schema)?;
365        Ok(schema)
366    }
367
368    /// Register a pre-built [`Schema`] handle with this encoder.
369    ///
370    /// Eagerly writes the schema frame (and annotation frame if annotations
371    /// are present). Idempotent if the definition matches.
372    pub fn register_existing(&mut self, schema: &Schema) -> io::Result<WireTypeId> {
373        self.ensure_registered(schema)
374    }
375
376    /// Write an event for a schema.
377    ///
378    /// The first element of `values` must be `FieldValue::Varint(timestamp_ns)`
379    /// — it is extracted and encoded in the event header, not as a regular
380    /// field. The remaining values must match the schema's field count.
381    ///
382    /// If this encoder hasn't seen `schema` before, it is auto-registered
383    /// (the schema frame is written before the event).
384    pub fn write_event(
385        &mut self,
386        schema: &Schema,
387        values: &[crate::types::FieldValue],
388    ) -> io::Result<()> {
389        use crate::types::FieldValue;
390
391        let type_id = self.ensure_registered(schema)?;
392        let expected_fields = schema.entry.fields.len();
393
394        let ts_ns = match values.first() {
395            Some(FieldValue::Varint(ns)) => *ns,
396            _ => {
397                return Err(io::Error::new(
398                    io::ErrorKind::InvalidInput,
399                    "first value must be FieldValue::Varint(timestamp_ns)",
400                ));
401            }
402        };
403        let field_values = &values[1..];
404
405        if field_values.len() != expected_fields {
406            return Err(io::Error::new(
407                io::ErrorKind::InvalidInput,
408                format!(
409                    "value count ({}) does not match schema field count ({}) for schema '{}'",
410                    field_values.len(),
411                    expected_fields,
412                    schema.name(),
413                ),
414            ));
415        }
416
417        let ts_delta = self.state.encode_timestamp_delta(ts_ns)?;
418        self.state.writer.write_all(&[codec::TAG_EVENT])?;
419        self.state.writer.write_all(&type_id.0.to_le_bytes())?;
420        codec::encode_u24_le(ts_delta, &mut self.state.writer)?;
421        let mut enc = EventEncoder::new(&mut self.state);
422        for (i, v) in field_values.iter().enumerate() {
423            enc.write_field_value(v, schema.entry.fields[i].field_type)?;
424        }
425        Ok(())
426    }
427
428    /// Write a derived TraceEvent. Auto-registers the schema on first call for this type.
429    /// Handles timestamp encoding: emits TimestampReset if needed, packs u24 delta in header.
430    pub fn write<T: TraceEvent + 'static>(&mut self, event: &T) -> io::Result<()> {
431        let slot = T::type_slot();
432        let tid = if slot != 0 && slot < crate::STATIC_WIRE_ID_LIMIT {
433            let word = (slot >> 6) as usize;
434            let bit = 1u64 << (slot & 63);
435            if self.registered_ids[word] & bit == 0 {
436                self.register_fast_id::<T>(slot)?;
437            }
438            WireTypeId(slot)
439        } else {
440            let s = slot as usize;
441            let cached = self.slot_cache.get(s).copied().unwrap_or(0);
442            if cached != 0 {
443                WireTypeId((cached - 1) as u16)
444            } else {
445                self.resolve_dynamic_wire_id::<T>(s)?
446            }
447        };
448        let ts_ns = event.timestamp();
449        let ts_delta = self.state.encode_timestamp_delta(ts_ns)?;
450        self.state.writer.write_all(&[codec::TAG_EVENT])?;
451        self.state.writer.write_all(&tid.0.to_le_bytes())?;
452        codec::encode_u24_le(ts_delta, &mut self.state.writer)?;
453        let mut enc = EventEncoder::new(&mut self.state);
454        event.encode_fields(&mut enc)
455    }
456
457    /// Slow path for `write::<T>`: resolve the wire ID via the schema-ids
458    /// hashmap (registering the schema if needed) and populate the slot cache
459    /// so the next call for the same type takes the fast path.
460    #[cold]
461    fn resolve_dynamic_wire_id<T: TraceEvent + 'static>(
462        &mut self,
463        slot: usize,
464    ) -> io::Result<WireTypeId> {
465        let key = SchemaKey::RustType(TypeId::of::<T>());
466        let tid = if let Some(&existing) = self.schema_ids.get(&key) {
467            existing
468        } else {
469            let schema = Schema::from_entry(T::schema_entry());
470            let id = self.ensure_registered(&schema)?;
471            self.schema_ids.insert(key, id);
472            id
473        };
474        if slot != 0 {
475            if self.slot_cache.len() <= slot {
476                self.slot_cache.resize(slot + 1, 0);
477            }
478            self.slot_cache[slot] = (tid.0 as u32) + 1;
479        }
480        Ok(tid)
481    }
482
483    /// First write of a slot in `1..STATIC_WIRE_ID_LIMIT`: emit the schema frame
484    /// at the slot `id` and mark the bitset, so later writes skip registration.
485    #[cold]
486    fn register_fast_id<T: TraceEvent + 'static>(&mut self, id: u16) -> io::Result<()> {
487        let entry = T::schema_entry();
488        let wire = WireTypeId(id);
489        codec::encode_schema(wire, &entry, &mut self.state.writer)?;
490        if !entry.annotations.is_empty() {
491            codec::encode_schema_annotations(wire, &entry.annotations, &mut self.state.writer)?;
492        }
493        self.registry.register(wire, entry).map_err(|e| {
494            io::Error::new(
495                io::ErrorKind::InvalidInput,
496                format!("wire id {id} collision: {e}"),
497            )
498        })?;
499        // mark id registered: set bit (id % 64) in word (id / 64)
500        self.registered_ids[(id >> 6) as usize] |= 1u64 << (id & 63);
501        Ok(())
502    }
503
504    /// Intern a string, emitting a pool frame if new. Returns an [`InternedString`] handle.
505    pub fn intern_string(&mut self, s: &str) -> io::Result<InternedString> {
506        if let Some(&id) = self.string_pool.get(s) {
507            return Ok(InternedString(id));
508        }
509        let id = self.next_pool_id;
510        self.next_pool_id += 1;
511        self.string_pool.insert(s.to_string(), id);
512        codec::encode_string_pool(
513            &[PoolEntry {
514                pool_id: id,
515                data: s.as_bytes().to_vec(),
516            }],
517            &mut self.state.writer,
518        )?;
519        Ok(InternedString(id))
520    }
521
522    pub fn write_string_pool(&mut self, entries: &[PoolEntry]) -> io::Result<()> {
523        codec::encode_string_pool(entries, &mut self.state.writer)
524    }
525
526    /// Intern a stack-frame vector, emitting a stack-pool frame if new.
527    /// Returns an [`InternedStackFrames`] handle.
528    pub fn intern_stack_frames(&mut self, frames: &[u64]) -> io::Result<InternedStackFrames> {
529        if let Some(&id) = self.stack_pool.get(frames) {
530            return Ok(InternedStackFrames(id));
531        }
532        let id = self.next_stack_pool_id;
533        self.next_stack_pool_id += 1;
534        self.stack_pool.insert(frames.into(), id);
535        codec::encode_stack_pool(
536            &[StackPoolEntry {
537                pool_id: id,
538                // TODO: allow `StackPoolEntry` to have borrowed frames avoiding the unecessary clone here
539                // https://github.com/dial9-rs/dial9-tokio-telemetry/issues/358
540                frames: frames.to_vec(),
541            }],
542            &mut self.state.writer,
543        )?;
544        Ok(InternedStackFrames(id))
545    }
546
547    pub fn write_stack_pool(&mut self, entries: &[StackPoolEntry]) -> io::Result<()> {
548        codec::encode_stack_pool(entries, &mut self.state.writer)
549    }
550
551    /// Flush the underlying writer.
552    pub fn flush(&mut self) -> io::Result<()> {
553        self.state.writer.flush()
554    }
555
556    /// Convert this encoder into a [`RawEncoder`] that only supports writing
557    /// pre-encoded bytes. The byte count is preserved so rotation decisions
558    /// remain correct.
559    ///
560    /// Use this after writing any structured data (headers, segment metadata)
561    /// to switch to a raw-only mode for appending pre-encoded batches.
562    pub fn into_raw_encoder(self) -> RawEncoder<W> {
563        RawEncoder {
564            writer: self.state.writer,
565        }
566    }
567}
568
569/// A write-only encoder that accepts pre-encoded bytes.
570///
571/// Created by [`Encoder::into_raw_encoder`] after the file header and any
572/// structured metadata have been written. Carries no schema registry, string
573/// pool, or timestamp state — it simply forwards bytes to the underlying
574/// writer while tracking the total byte count.
575pub struct RawEncoder<W> {
576    writer: CountingWriter<W>,
577}
578
579impl<W: Write> RawEncoder<W> {
580    /// Write pre-encoded bytes to the underlying writer.
581    pub fn write_raw(&mut self, bytes: &[u8]) -> io::Result<()> {
582        self.writer.write_all(bytes)
583    }
584
585    /// Total bytes written (including bytes written by the [`Encoder`] before
586    /// conversion).
587    pub fn bytes_written(&self) -> u64 {
588        self.writer.bytes_written()
589    }
590
591    /// Flush the underlying writer.
592    pub fn flush(&mut self) -> io::Result<()> {
593        self.writer.flush()
594    }
595
596    /// Consume the raw encoder and return the inner writer.
597    pub fn into_inner(self) -> W {
598        self.writer.into_inner()
599    }
600}
601
602impl Encoder<Vec<u8>> {
603    pub fn write_infallible<T: TraceEvent + 'static>(&mut self, event: &T) {
604        self.write(event).expect("writing to Vec<u8> is infallible")
605    }
606
607    pub fn intern_string_infallible(&mut self, s: &str) -> InternedString {
608        self.intern_string(s)
609            .expect("interning into Vec<u8> is infallible")
610    }
611
612    pub fn intern_stack_frames_infallible(&mut self, frames: &[u64]) -> InternedStackFrames {
613        self.intern_stack_frames(frames)
614            .expect("interning into Vec<u8> is infallible")
615    }
616
617    /// Resets the encoder to point to a new backing Vec returning the old one
618    pub fn reset_to_infallible(&mut self, data: Vec<u8>) -> Vec<u8> {
619        self.reset_to(data)
620            .expect("writing to Vec<u8> is infallible")
621    }
622}
623
624#[cfg(test)]
625mod tests {
626    use super::*;
627    use crate::schema::FieldDef;
628    use crate::types::{FieldType, FieldValue};
629
630    #[test]
631    fn encoder_writes_header() {
632        let enc = Encoder::new();
633        let data = enc.finish();
634        assert_eq!(&data[..5], &[0x54, 0x52, 0x43, 0x00, 1]);
635    }
636
637    #[test]
638    fn encoder_register_and_write_event() {
639        let mut enc = Encoder::new();
640        let schema = enc
641            .register_schema(
642                "Ev",
643                vec![FieldDef {
644                    name: "v".into(),
645                    field_type: FieldType::Varint,
646                }],
647            )
648            .unwrap();
649        enc.write_event(
650            &schema,
651            &[FieldValue::Varint(1_000), FieldValue::Varint(42)],
652        )
653        .unwrap();
654        let data = enc.finish();
655        assert!(data.len() > 5);
656    }
657
658    #[test]
659    fn idempotent_re_registration() {
660        let mut enc = Encoder::new();
661        let fields = vec![FieldDef {
662            name: "v".into(),
663            field_type: FieldType::Varint,
664        }];
665        let _s1 = enc.register_schema("Ev", fields.clone()).unwrap();
666        let _s2 = enc.register_schema("Ev", fields).unwrap();
667        // Both succeed — same schema, same name
668    }
669
670    #[test]
671    fn re_registration_different_schema_errors() {
672        let mut enc = Encoder::new();
673        enc.register_schema(
674            "Ev",
675            vec![FieldDef {
676                name: "v".into(),
677                field_type: FieldType::Varint,
678            }],
679        )
680        .unwrap();
681        let result = enc.register_schema(
682            "Ev",
683            vec![FieldDef {
684                name: "different".into(),
685                field_type: FieldType::Bool,
686            }],
687        );
688        assert!(result.is_err());
689    }
690
691    #[test]
692    fn schema_auto_registers_on_write() {
693        use crate::decoder::{DecodedFrame, Decoder};
694
695        // Create a schema without an encoder
696        let schema = Schema::new(
697            "Lazy",
698            vec![FieldDef {
699                name: "v".into(),
700                field_type: FieldType::Varint,
701            }],
702        );
703
704        // Write to an encoder that hasn't seen this schema — auto-registers
705        let mut enc = Encoder::new();
706        enc.write_event(
707            &schema,
708            &[FieldValue::Varint(1_000), FieldValue::Varint(42)],
709        )
710        .unwrap();
711
712        let bytes = enc.finish();
713        let mut dec = Decoder::new(&bytes).unwrap();
714        let frames = dec.decode_all();
715        assert!(matches!(&frames[0], DecodedFrame::Schema(s) if s.name == "Lazy"));
716        if let DecodedFrame::Event { values, .. } = &frames[1] {
717            assert_eq!(*values, vec![FieldValue::Varint(42)]);
718        } else {
719            panic!("expected event");
720        }
721    }
722
723    #[test]
724    fn schema_portable_across_encoders() {
725        use crate::decoder::{DecodedFrame, Decoder};
726
727        let mut enc1 = Encoder::new();
728        let schema = enc1
729            .register_schema(
730                "Shared",
731                vec![FieldDef {
732                    name: "v".into(),
733                    field_type: FieldType::Varint,
734                }],
735            )
736            .unwrap();
737        enc1.write_event(&schema, &[FieldValue::Varint(1_000), FieldValue::Varint(1)])
738            .unwrap();
739
740        // Pass the same Schema to a different encoder
741        let mut enc2 = Encoder::new();
742        enc2.write_event(&schema, &[FieldValue::Varint(2_000), FieldValue::Varint(2)])
743            .unwrap();
744
745        // Both encoders produce valid output
746        for (enc, expected_val) in [(enc1, 1u64), (enc2, 2u64)] {
747            let bytes = enc.finish();
748            let mut dec = Decoder::new(&bytes).unwrap();
749            let frames = dec.decode_all();
750            let event = frames
751                .iter()
752                .find(|f| matches!(f, DecodedFrame::Event { .. }))
753                .unwrap();
754            if let DecodedFrame::Event { values, .. } = event {
755                assert_eq!(values[0], FieldValue::Varint(expected_val));
756            }
757        }
758    }
759
760    #[test]
761    fn encoder_intern_string_deduplicates() {
762        let mut enc = Encoder::new();
763        let id1 = enc.intern_string("hello").unwrap();
764        let id2 = enc.intern_string("hello").unwrap();
765        let id3 = enc.intern_string("world").unwrap();
766        assert_eq!(id1, id2);
767        assert_ne!(id1, id3);
768    }
769
770    #[test]
771    fn encoder_intern_stack_frames_deduplicates() {
772        let mut enc = Encoder::new();
773        let stack_a: &[u64] = &[0x1000, 0x2000, 0x3000];
774        let stack_b: &[u64] = &[0x4000, 0x5000];
775        let id1 = enc.intern_stack_frames(stack_a).unwrap();
776        let id2 = enc.intern_stack_frames(stack_a).unwrap();
777        let id3 = enc.intern_stack_frames(stack_b).unwrap();
778        assert_eq!(id1, id2);
779        assert_ne!(id1, id3);
780    }
781
782    #[test]
783    fn stack_pool_round_trip_via_decoder() {
784        use crate::decoder::Decoder;
785        use crate::types::InternedStackFrames;
786
787        let mut enc = Encoder::new();
788        let stack_a: &[u64] = &[0xdead, 0xbeef, 0xcafe];
789        let stack_b: &[u64] = &[0x1, 0x2];
790        let id_a = enc.intern_stack_frames(stack_a).unwrap();
791        let id_b = enc.intern_stack_frames(stack_b).unwrap();
792        let bytes = enc.finish();
793
794        let mut dec = Decoder::new(&bytes).unwrap();
795        let _ = dec.decode_all();
796        assert_eq!(
797            dec.stack_pool().get(InternedStackFrames(id_a.raw_id())),
798            Some(stack_a)
799        );
800        assert_eq!(
801            dec.stack_pool().get(InternedStackFrames(id_b.raw_id())),
802            Some(stack_b)
803        );
804    }
805
806    #[test]
807    fn for_each_event_populates_stack_pool() {
808        use crate::decoder::Decoder;
809        use crate::schema::FieldDef;
810        use crate::types::{FieldType, FieldValue, InternedStackFrames};
811
812        let mut enc = Encoder::new();
813        let schema = enc
814            .register_schema(
815                "CpuSampleEvent",
816                vec![FieldDef {
817                    name: "callchain".into(),
818                    field_type: FieldType::PooledStackFrames,
819                }],
820            )
821            .unwrap();
822        let stack: &[u64] = &[0x1234, 0x5678, 0x9abc];
823        let id = enc.intern_stack_frames(stack).unwrap();
824        enc.write_event(
825            &schema,
826            &[
827                FieldValue::Varint(1_000_000),
828                FieldValue::PooledStackFrames(id),
829            ],
830        )
831        .unwrap();
832        let bytes = enc.finish();
833
834        let mut dec = Decoder::new(&bytes).unwrap();
835        let mut event_count = 0;
836        dec.for_each_event(|_ev| {
837            event_count += 1;
838        })
839        .unwrap();
840        assert_eq!(event_count, 1);
841        assert_eq!(
842            dec.stack_pool().get(InternedStackFrames(id.raw_id())),
843            Some(stack),
844        );
845    }
846
847    #[test]
848    fn encoder_intern_empty_stack_frames() {
849        use crate::decoder::Decoder;
850        use crate::types::InternedStackFrames;
851
852        let mut enc = Encoder::new();
853        let id1 = enc.intern_stack_frames(&[]).unwrap();
854        let id2 = enc.intern_stack_frames(&[]).unwrap();
855        assert_eq!(id1, id2);
856        let bytes = enc.finish();
857
858        let mut dec = Decoder::new(&bytes).unwrap();
859        let _ = dec.decode_all();
860        assert_eq!(
861            dec.stack_pool().get(InternedStackFrames(id1.raw_id())),
862            Some(&[][..])
863        );
864    }
865
866    #[test]
867    fn write_stack_pool_multi_entry_round_trip() {
868        use crate::decoder::Decoder;
869        use crate::types::InternedStackFrames;
870
871        let mut enc = Encoder::new();
872        let entries = vec![
873            StackPoolEntry {
874                pool_id: 0,
875                frames: vec![0xaaaa, 0xbbbb, 0xcccc],
876            },
877            StackPoolEntry {
878                pool_id: 1,
879                frames: vec![0x1111],
880            },
881            StackPoolEntry {
882                pool_id: 2,
883                frames: vec![],
884            },
885        ];
886        enc.write_stack_pool(&entries).unwrap();
887        let bytes = enc.finish();
888
889        let mut dec = Decoder::new(&bytes).unwrap();
890        let _ = dec.decode_all();
891        assert_eq!(
892            dec.stack_pool().get(InternedStackFrames(0)),
893            Some(&[0xaaaa, 0xbbbb, 0xcccc][..])
894        );
895        assert_eq!(
896            dec.stack_pool().get(InternedStackFrames(1)),
897            Some(&[0x1111][..])
898        );
899        assert_eq!(dec.stack_pool().get(InternedStackFrames(2)), Some(&[][..]));
900    }
901
902    #[test]
903    fn decoder_into_encoder_deduplicates_interned_stack_frames() {
904        use crate::decoder::Decoder;
905
906        let mut enc = Encoder::new();
907        let id1 = enc.intern_stack_frames(&[0x10, 0x20]).unwrap();
908        let base = enc.finish();
909
910        let mut decoder = Decoder::new(&base).unwrap();
911        while decoder.next_frame_ref().ok().flatten().is_some() {}
912        let mut output = Vec::new();
913        let mut ext = decoder.into_encoder(&mut output);
914        let id2 = ext.intern_stack_frames(&[0x10, 0x20]).unwrap();
915        let id3 = ext.intern_stack_frames(&[0x30]).unwrap();
916        assert_eq!(id1.raw_id(), id2.raw_id());
917        assert_ne!(id2.raw_id(), id3.raw_id());
918    }
919
920    #[test]
921    fn timestamp_round_trip() {
922        use crate::decoder::{DecodedFrame, Decoder};
923
924        let mut enc = Encoder::new();
925        let schema = enc
926            .register_schema(
927                "TS",
928                vec![FieldDef {
929                    name: "v".into(),
930                    field_type: FieldType::Varint,
931                }],
932            )
933            .unwrap();
934
935        let ts1 = 100_000u64;
936        let ts2 = 50_000u64;
937        let ts3 = 200_000_000u64;
938        let ts4 = 100_000_000u64;
939        enc.write_event(&schema, &[FieldValue::Varint(ts1), FieldValue::Varint(1)])
940            .unwrap();
941        enc.write_event(&schema, &[FieldValue::Varint(ts2), FieldValue::Varint(2)])
942            .unwrap();
943        enc.write_event(&schema, &[FieldValue::Varint(ts3), FieldValue::Varint(3)])
944            .unwrap();
945        enc.write_event(&schema, &[FieldValue::Varint(ts4), FieldValue::Varint(4)])
946            .unwrap();
947
948        let bytes = enc.finish();
949        let mut dec = Decoder::new(&bytes).unwrap();
950        let events: Vec<_> = dec
951            .decode_all()
952            .into_iter()
953            .filter_map(|f| match f {
954                DecodedFrame::Event {
955                    timestamp_ns,
956                    values,
957                    ..
958                } => Some((timestamp_ns, values)),
959                _ => None,
960            })
961            .collect();
962
963        assert_eq!(events.len(), 4);
964        assert_eq!(events[0].0, Some(ts1));
965        assert_eq!(events[0].1, vec![FieldValue::Varint(1)]);
966        assert_eq!(events[1].0, Some(ts2));
967        assert_eq!(events[1].1, vec![FieldValue::Varint(2)]);
968        assert_eq!(events[2].0, Some(ts3));
969        assert_eq!(events[2].1, vec![FieldValue::Varint(3)]);
970        assert_eq!(events[3].0, Some(ts4));
971        assert_eq!(events[3].1, vec![FieldValue::Varint(4)]);
972    }
973
974    #[test]
975    fn encoder_new_to_writer() {
976        let mut buf = Vec::new();
977        let enc = Encoder::new_to(&mut buf).unwrap();
978        drop(enc);
979        assert!(buf.len() >= 5);
980        assert_eq!(&buf[..5], &[0x54, 0x52, 0x43, 0x00, 1]);
981    }
982
983    #[test]
984    fn decoder_into_encoder_appends_without_header() {
985        use crate::decoder::{DecodedFrame, Decoder};
986
987        // Create a trace with a header, a schema, and an event
988        let mut enc = Encoder::new();
989        let schema = enc
990            .register_schema(
991                "Ev",
992                vec![FieldDef {
993                    name: "v".into(),
994                    field_type: FieldType::Varint,
995                }],
996            )
997            .unwrap();
998        enc.write_event(&schema, &[FieldValue::Varint(1_000), FieldValue::Varint(1)])
999            .unwrap();
1000        let base = enc.finish();
1001
1002        // Decode all frames, then convert into an encoder that appends to output
1003        let mut decoder = Decoder::new(&base).unwrap();
1004        while decoder.next_frame_ref().ok().flatten().is_some() {}
1005        let mut output = Vec::new();
1006        let mut ext = decoder.into_encoder(&mut output);
1007        // Schema "Ev" is already known — no duplicate schema frame emitted
1008        ext.write_event(&schema, &[FieldValue::Varint(2_000), FieldValue::Varint(2)])
1009            .unwrap();
1010        drop(ext);
1011
1012        // Concatenate and decode
1013        let mut combined = base.clone();
1014        combined.extend_from_slice(&output);
1015        let mut dec = Decoder::new(&combined).unwrap();
1016        let events: Vec<_> = dec
1017            .decode_all()
1018            .into_iter()
1019            .filter_map(|f| match f {
1020                DecodedFrame::Event {
1021                    timestamp_ns,
1022                    values,
1023                    ..
1024                } => Some((timestamp_ns, values)),
1025                _ => None,
1026            })
1027            .collect();
1028        assert_eq!(events.len(), 2);
1029        assert_eq!(events[0].0, Some(1_000));
1030        assert_eq!(events[1].0, Some(2_000));
1031    }
1032
1033    #[test]
1034    fn decoder_into_encoder_deduplicates_interned_strings() {
1035        use crate::decoder::{DecodedFrame, Decoder};
1036
1037        // Create a trace with an interned string
1038        let mut enc = Encoder::new();
1039        let id1 = enc.intern_string("hello").unwrap();
1040        let base = enc.finish();
1041
1042        // Decode all frames, then convert into an encoder
1043        let mut decoder = Decoder::new(&base).unwrap();
1044        while decoder.next_frame_ref().ok().flatten().is_some() {}
1045        let mut output = Vec::new();
1046        let mut ext = decoder.into_encoder(&mut output);
1047        // "hello" is already interned, should reuse the same ID
1048        let id2 = ext.intern_string("hello").unwrap();
1049        let id3 = ext.intern_string("world").unwrap();
1050        drop(ext);
1051
1052        assert_eq!(id1, id2, "existing string should reuse pool ID");
1053        assert_ne!(id2, id3);
1054
1055        // "hello" should not produce a new StringPool frame; "world" should
1056        let mut combined = base.clone();
1057        combined.extend_from_slice(&output);
1058        let mut dec = Decoder::new(&combined).unwrap();
1059        let frames = dec.decode_all();
1060        let pool_frames: Vec<_> = frames
1061            .iter()
1062            .filter(|f| matches!(f, DecodedFrame::StringPool(_)))
1063            .collect();
1064        // One from the base trace ("hello"), one from extend ("world")
1065        assert_eq!(pool_frames.len(), 2);
1066    }
1067
1068    /// Minimal hand-rolled `TraceEvent` with a fixed fast-path slot, so the
1069    /// encoder tests don't depend on the derive crate.
1070    struct FastSlot {
1071        ts: u64,
1072    }
1073    impl TraceEvent for FastSlot {
1074        fn type_slot() -> u16 {
1075            5
1076        }
1077        fn event_name() -> &'static str {
1078            "FastSlot"
1079        }
1080        fn field_defs() -> Vec<FieldDef> {
1081            Vec::new()
1082        }
1083        fn timestamp(&self) -> u64 {
1084            self.ts
1085        }
1086        fn encode_fields<W: Write>(&self, _enc: &mut EventEncoder<'_, W>) -> io::Result<()> {
1087            Ok(())
1088        }
1089    }
1090
1091    #[test]
1092    fn fast_slot_registers_at_slot_id() {
1093        use crate::decoder::Decoder;
1094
1095        let mut enc = Encoder::new();
1096        enc.write(&FastSlot { ts: 1_000_000 }).unwrap();
1097        // A plain dynamic schema must land in the dynamic range, above slots.
1098        let dynamic = enc
1099            .register_schema(
1100                "Dyn",
1101                vec![FieldDef {
1102                    name: "v".into(),
1103                    field_type: FieldType::Varint,
1104                }],
1105            )
1106            .unwrap();
1107        enc.write_event(
1108            &dynamic,
1109            &[FieldValue::Varint(2_000), FieldValue::Varint(1)],
1110        )
1111        .unwrap();
1112        let bytes = enc.finish();
1113
1114        let mut dec = Decoder::new(&bytes).unwrap();
1115        let _ = dec.decode_all();
1116        // Fast-path event registered at its slot id.
1117        assert_eq!(
1118            dec.registry().get(WireTypeId(5)).unwrap().name(),
1119            "FastSlot"
1120        );
1121        // Dynamic schema sits at STATIC_WIRE_ID_LIMIT, not colliding with slots.
1122        assert_eq!(
1123            dec.registry()
1124                .get(WireTypeId(crate::STATIC_WIRE_ID_LIMIT))
1125                .unwrap()
1126                .name(),
1127            "Dyn"
1128        );
1129    }
1130
1131    #[test]
1132    fn register_and_write() {
1133        use crate::decoder::{DecodedFrame, Decoder};
1134
1135        let mut enc = Encoder::new();
1136        let schema = enc
1137            .register_schema(
1138                "MyEvent",
1139                vec![
1140                    FieldDef {
1141                        name: "count".into(),
1142                        field_type: FieldType::Varint,
1143                    },
1144                    FieldDef {
1145                        name: "name".into(),
1146                        field_type: FieldType::String,
1147                    },
1148                ],
1149            )
1150            .unwrap();
1151
1152        enc.write_event(
1153            &schema,
1154            &[
1155                FieldValue::Varint(1_000_000),
1156                FieldValue::Varint(42),
1157                FieldValue::String("hello".into()),
1158            ],
1159        )
1160        .unwrap();
1161
1162        let bytes = enc.finish();
1163        let mut dec = Decoder::new(&bytes).unwrap();
1164        let frames = dec.decode_all();
1165        let events: Vec<_> = frames
1166            .into_iter()
1167            .filter_map(|f| match f {
1168                DecodedFrame::Event {
1169                    timestamp_ns,
1170                    values,
1171                    ..
1172                } => Some((timestamp_ns, values)),
1173                _ => None,
1174            })
1175            .collect();
1176        assert_eq!(events.len(), 1);
1177        assert_eq!(events[0].0, Some(1_000_000));
1178        assert_eq!(events[0].1[0], FieldValue::Varint(42));
1179        assert_eq!(events[0].1[1], FieldValue::String("hello".into()));
1180    }
1181
1182    #[test]
1183    fn register_conflict_errors() {
1184        let mut enc = Encoder::new();
1185        enc.register_schema(
1186            "Ev",
1187            vec![FieldDef {
1188                name: "v".into(),
1189                field_type: FieldType::Varint,
1190            }],
1191        )
1192        .unwrap();
1193        let result = enc.register_schema(
1194            "Ev",
1195            vec![FieldDef {
1196                name: "other".into(),
1197                field_type: FieldType::Bool,
1198            }],
1199        );
1200        assert!(result.is_err());
1201    }
1202
1203    #[test]
1204    fn write_wrong_field_count_errors() {
1205        let mut enc = Encoder::new();
1206        let schema = enc
1207            .register_schema(
1208                "Ev",
1209                vec![FieldDef {
1210                    name: "v".into(),
1211                    field_type: FieldType::Varint,
1212                }],
1213            )
1214            .unwrap();
1215        // Pass 3 values (ts + 2 fields) for a 1-field schema
1216        let result = enc.write_event(
1217            &schema,
1218            &[
1219                FieldValue::Varint(0),
1220                FieldValue::Varint(1),
1221                FieldValue::Varint(2),
1222            ],
1223        );
1224        assert!(result.is_err());
1225    }
1226
1227    /// Verify that the encoder advances the timestamp base after each event,
1228    /// producing inter-event deltas rather than base-relative deltas.
1229    #[test]
1230    fn timestamp_base_advances_per_event() {
1231        use crate::decoder::{DecodedFrame, Decoder};
1232
1233        let mut enc = Encoder::new();
1234        let schema = enc
1235            .register_schema(
1236                "Ev",
1237                vec![FieldDef {
1238                    name: "v".into(),
1239                    field_type: FieldType::Varint,
1240                }],
1241            )
1242            .unwrap();
1243
1244        let ts1 = 12_000_000u64;
1245        let ts2 = 24_000_000u64;
1246        enc.write_event(&schema, &[FieldValue::Varint(ts1), FieldValue::Varint(1)])
1247            .unwrap();
1248        enc.write_event(&schema, &[FieldValue::Varint(ts2), FieldValue::Varint(2)])
1249            .unwrap();
1250
1251        let bytes = enc.finish();
1252
1253        let reset_count = bytes.iter().filter(|&&b| b == 0x05).count();
1254        assert_eq!(
1255            reset_count, 0,
1256            "base should advance per event, avoiding unnecessary resets"
1257        );
1258
1259        let mut dec = Decoder::new(&bytes).unwrap();
1260        let events: Vec<_> = dec
1261            .decode_all()
1262            .into_iter()
1263            .filter_map(|f| match f {
1264                DecodedFrame::Event { timestamp_ns, .. } => timestamp_ns,
1265                _ => None,
1266            })
1267            .collect();
1268        assert_eq!(events, vec![ts1, ts2]);
1269    }
1270
1271    #[test]
1272    fn reset_to_preserves_capacity() {
1273        let mut enc = Encoder::new();
1274        for i in 0..100 {
1275            enc.intern_string(&format!("string_{}", i)).unwrap();
1276        }
1277        let cap_before = enc.string_pool.capacity();
1278        let _bytes = enc.reset_to(Vec::new());
1279        let cap_after = enc.string_pool.capacity();
1280        assert_eq!(
1281            cap_before, cap_after,
1282            "string_pool capacity should be preserved after reset_to"
1283        );
1284    }
1285
1286    #[test]
1287    fn reset_to_returns_old_data_and_clears_state() {
1288        use crate::decoder::{DecodedFrame, Decoder};
1289
1290        let mut enc = Encoder::new();
1291        let schema = enc
1292            .register_schema(
1293                "Ev",
1294                vec![FieldDef {
1295                    name: "v".into(),
1296                    field_type: FieldType::Varint,
1297                }],
1298            )
1299            .unwrap();
1300        enc.write_event(
1301            &schema,
1302            &[FieldValue::Varint(1_000), FieldValue::Varint(42)],
1303        )
1304        .unwrap();
1305        let _s = enc.intern_string("hello").unwrap();
1306
1307        let old_bytes_written = enc.bytes_written();
1308        assert!(old_bytes_written > 0);
1309
1310        // --- reset ---
1311        let old = enc.reset_to_infallible(Vec::new());
1312
1313        // Invariant 1: old writer contains the data we wrote (decodable)
1314        let mut dec = Decoder::new(&old).unwrap();
1315        let frames = dec.decode_all();
1316        assert!(frames.iter().any(|f| matches!(f, DecodedFrame::Schema(_))));
1317        assert!(
1318            frames
1319                .iter()
1320                .any(|f| matches!(f, DecodedFrame::Event { .. }))
1321        );
1322        assert!(
1323            frames
1324                .iter()
1325                .any(|f| matches!(f, DecodedFrame::StringPool(_)))
1326        );
1327
1328        // Invariant 2: bytes_written resets to just the header size
1329        assert!(
1330            enc.bytes_written() < old_bytes_written,
1331            "bytes_written should reset (got {} vs old {})",
1332            enc.bytes_written(),
1333            old_bytes_written
1334        );
1335
1336        // Invariant 3: schemas are cleared — same schema must re-register
1337        // (write_event auto-registers, so we verify a new schema frame appears)
1338        enc.write_event(
1339            &schema,
1340            &[FieldValue::Varint(2_000), FieldValue::Varint(99)],
1341        )
1342        .unwrap();
1343
1344        // Invariant 4: string pool is cleared — re-interning emits a new pool frame
1345        let _s2 = enc.intern_string("hello").unwrap();
1346
1347        // Invariant 5: new output is a valid standalone trace
1348        let new_bytes = enc.reset_to_infallible(Vec::new());
1349        let mut dec2 = Decoder::new(&new_bytes).unwrap();
1350        let new_frames = dec2.decode_all();
1351        // Must have its own schema definition (not relying on old encoder state)
1352        assert!(
1353            new_frames
1354                .iter()
1355                .any(|f| matches!(f, DecodedFrame::Schema(s) if s.name == "Ev")),
1356            "new trace must contain schema definition"
1357        );
1358        // Must have its own string pool entry
1359        assert!(
1360            new_frames
1361                .iter()
1362                .any(|f| matches!(f, DecodedFrame::StringPool(_))),
1363            "new trace must contain string pool"
1364        );
1365        // Event must decode with correct timestamp (timestamp_base was reset)
1366        let event = new_frames
1367            .iter()
1368            .find_map(|f| match f {
1369                DecodedFrame::Event {
1370                    timestamp_ns,
1371                    values,
1372                    ..
1373                } => Some((timestamp_ns, values)),
1374                _ => None,
1375            })
1376            .expect("new trace must contain event");
1377        assert_eq!(*event.0, Some(2_000));
1378        assert_eq!(event.1[0], FieldValue::Varint(99));
1379    }
1380
1381    #[test]
1382    fn into_raw_encoder_preserves_byte_count() {
1383        let mut enc = Encoder::new();
1384        let schema = enc
1385            .register_schema(
1386                "Ev",
1387                vec![FieldDef {
1388                    name: "v".into(),
1389                    field_type: FieldType::Varint,
1390                }],
1391            )
1392            .unwrap();
1393        enc.write_event(
1394            &schema,
1395            &[FieldValue::Varint(1_000), FieldValue::Varint(42)],
1396        )
1397        .unwrap();
1398
1399        let bytes_before = enc.bytes_written();
1400        assert!(bytes_before > 0);
1401
1402        let raw = enc.into_raw_encoder();
1403        assert_eq!(
1404            raw.bytes_written(),
1405            bytes_before,
1406            "byte count must be preserved across conversion"
1407        );
1408    }
1409
1410    #[test]
1411    fn raw_encoder_write_raw_and_bytes_written() {
1412        let enc = Encoder::new();
1413        let initial = enc.bytes_written();
1414        let mut raw = enc.into_raw_encoder();
1415
1416        let payload = [0xAA; 100];
1417        raw.write_raw(&payload).unwrap();
1418
1419        assert_eq!(
1420            raw.bytes_written(),
1421            initial + payload.len() as u64,
1422            "bytes_written must include raw payload"
1423        );
1424    }
1425
1426    #[test]
1427    fn raw_encoder_into_inner_returns_all_data() {
1428        use crate::decoder::{DecodedFrame, Decoder};
1429
1430        // Write a structured event via Encoder, then append a raw batch
1431        // via RawEncoder, and verify the combined output decodes correctly.
1432        let mut enc = Encoder::new();
1433        let schema = enc
1434            .register_schema(
1435                "Ev",
1436                vec![FieldDef {
1437                    name: "v".into(),
1438                    field_type: FieldType::Varint,
1439                }],
1440            )
1441            .unwrap();
1442        enc.write_event(&schema, &[FieldValue::Varint(1_000), FieldValue::Varint(1)])
1443            .unwrap();
1444
1445        // Build a raw batch with the same schema
1446        let raw_batch = {
1447            let mut batch_enc = Encoder::new();
1448            batch_enc
1449                .write_event(&schema, &[FieldValue::Varint(2_000), FieldValue::Varint(2)])
1450                .unwrap();
1451            batch_enc.finish()
1452        };
1453
1454        let mut raw = enc.into_raw_encoder();
1455        raw.write_raw(&raw_batch).unwrap();
1456        let combined = raw.into_inner();
1457
1458        let mut dec = Decoder::new(&combined).unwrap();
1459        let events: Vec<_> = dec
1460            .decode_all()
1461            .into_iter()
1462            .filter_map(|f| match f {
1463                DecodedFrame::Event {
1464                    timestamp_ns,
1465                    values,
1466                    ..
1467                } => Some((timestamp_ns, values)),
1468                _ => None,
1469            })
1470            .collect();
1471
1472        assert_eq!(events.len(), 2);
1473        assert_eq!(events[0].0, Some(1_000));
1474        assert_eq!(events[0].1, vec![FieldValue::Varint(1)]);
1475        assert_eq!(events[1].0, Some(2_000));
1476        assert_eq!(events[1].1, vec![FieldValue::Varint(2)]);
1477    }
1478}