Skip to content

3.1.0 — typed loops, serialize-only serde, yoke ownership

Choose a tag to compare

@fishloa fishloa released this 05 Jun 22:41
· 27 commits to main since this release
fb5dc8c

3.1.0 — typed descriptor loops, Serialize-only serde, owned views

2.0 made individual text fields decode on demand (DvbText). 3.1 finishes the
job for the descriptor loops themselves, settles serde into a one-way
display/export format, types the last raw service loop, and adds an optional
zero-cost way to own a parsed view past its input buffer. Wire parsing stays
byte-identical throughout: this release only changes field types, serialized
shape, and what you can do with a parsed value after the bytes are gone. All four
crates release in lockstep at 3.1.0.

Highlights

The loop walks itself

Every SI descriptor-loop field on a table — SdtService.descriptors,
EitEvent.descriptors, Pmt.program_info, and the rest — is now a zero-copy
DescriptorLoop that walks into typed AnyDescriptors on demand. No more
parse_loop(field) by hand at the call site.

use dvb_si::descriptors::AnyDescriptor;

// service.descriptors is a DescriptorLoop — iterate it directly.
for item in service.descriptors.iter() {
    if let Ok(AnyDescriptor::Service(sd)) = item {
        println!("{}", sd.service_name.decode());   // DvbText, decoded on demand
    }
}
let raw: &[u8] = service.descriptors.raw();          // wire bytes still one call away

parse_loop is unchanged and still available for free byte slices.

Decoded JSON, for free

Serialize a whole table and its descriptor loops decode themselves — the typed
service_name appears inline, no manual walking:

// 2.0 — descriptors was a raw byte array
{ "service_id": 1, "descriptors": [72, 9, 1, 3, 66, 66, 67, 3, 79, 78, 69] }

// 3.1 — descriptors walks into typed, decoded descriptors
{
  "service_id": 1,
  "descriptors": [
    { "service": { "service_type": 1, "provider_name": "BBC", "service_name": "ONE" } }
  ]
}

Per-entry parse errors surface as {"parseError": "…"} rather than being
dropped. Variant keys are camelCase (service), struct fields stay snake_case.

serde is Serialize-only, workspace-wide

JSON is a display/export format only. Every Deserialize derive and impl is
removed across dvb-si, dvb-t2mi, dvb-bbframe, and dvb-common (including
the manual text::LangCode impl and the now-dead serde(borrow) /
serde(bound(deserialize = …)) attributes). Parsing FROM JSON is deliberately
unsupported — to reconstruct a value, re-parse the wire bytes. Serialize
output is byte-for-byte unchanged.

let json = serde_json::to_string(&sdt)?;   // Serialize: unchanged
let sdt  = Sdt::parse(&section_bytes)?;     // reconstruct via Parse, not Deserialize

The last raw loop gets typed: SIT services

The SIT per-service loop is now a typed Vec<SitService> — mirroring
SdtService and completing table consistency (EN 300 468 §7.1.2, Table 164).

pub struct SitService<'a> {
    pub service_id: u16,
    pub running_status: u8,          // 3 bits
    pub descriptors: DescriptorLoop<'a>,
}

Own a parsed view: the yoke feature

Every view borrows its section bytes, which keeps parsing zero-copy but pins the
view to the input buffer's lifetime. Enable the optional yoke feature and a
parsed table becomes a 'static, Send + Sync, cheaply-Clone Owned<T> — no
re-parse, no hand-written mirror type, no lifetime in your own structs.

use std::sync::Arc;
use dvb_common::Parse;
use dvb_si::owned::Owned;
use dvb_si::tables::pmt::Pmt;

// Move the bytes into an Arc cart; parse once; keep the result forever.
let cart: Arc<[u8]> = Arc::from(section_bytes);          // source bytes consumed
let owned: Owned<Pmt<'static>> = Owned::try_new(cart, |b| Pmt::parse(b))?;

// 'static + Send + Sync + Clone — store it, cache it, send it across threads.
let program = owned.get().program_number;
let handle = std::thread::spawn(move || owned.get().streams.len());

dvb-t2mi and dvb-bbframe ship the same yoke feature on their own view
types. It is purely additive, off by default, and pulls in no dependencies on
default builds.

Breaking changes

Change Migration
SI descriptor-loop fields are DescriptorLoop<'a> instead of &[u8] / Vec<u8> Call .iter() to walk, .raw() for wire bytes; Deref<[u8]> still works
Cat, Tsdt, Sit are now borrowed (<'a>) Keep the section bytes alive; add a lifetime to any struct that stores them
Deserialize dropped workspace-wide — serde is Serialize-only Re-parse from wire bytes instead of deserializing from JSON
serde JSON: descriptor loops emit typed decoded arrays, not byte arrays Update JSON expectations; per-entry errors become {"parseError": …}
SIT service loop is typed: Sit.services: Vec<SitService> replaces the raw service_loop Iterate sit.services; each entry carries a typed descriptors loop

Full details and before/after code for every break:
MIGRATION-3.1.md

Links


All four crates released in lockstep at 3.1.0.