Domain DTOs
Use when you want owned values (Vec groups, owned tails) and simple
structs — not the zero-copy hot path. DTOs allocate on decode; the
flyweight path does not. A DTO-vs-flyweight latency A/B was not measured.
For re-encode, eligible flat groups are bulk-written directly from
&[EntryDomain]: no temporary Vec<Entry> and no encode-time allocation.
Eligibility requires fixed-size entries whose domain fields have the same wire
representation; nested groups, var-data, optional/versioned fields, configured
domain conversions, and bool remapping use the general add path. Integer
min/max checks are preserved in both paths. with_all_enums_as_option() /
with_null_as_option maps a required enum to Option<Enum> on the DTO:
None writes the schema NullVal image (including on a reused buffer), and
bulk_add_domain / to_wire_entry use that same mapping.
DTO re-encode of eligible flat groups is a DTO-to-DTO diagnostic
(group_encode_bench), not an ergon/sbe-tool ratio. Read a fresh run; do not
quote numbers from this page.
#![allow(unused)]
fn main() {
// build.rs — DomainVarData picks the DTO field type for var-data:
// .with_domain_objects(DomainVarData::Strings) // String (invalid UTF-8 → InvalidUtf8 error)
// .with_domain_objects(DomainVarData::Bytes) // Vec<u8> (byte-exact)
}
Generated shape (illustrative — your names follow your schema):
pub struct QuoteDomain {
pub seq: u32,
pub some_numbers: [u32; 4],
pub vehicle_code: [u8; 6],
pub qty: u32,
pub legs: Vec<QuoteLegsEntryDomain>,
pub note: Vec<u8>, // Bytes|String per DomainVarData
}
impl QuoteDomain {
// Named methods, not TryFrom/From: two fallible sources (decoder vs framed
// slice+offset), and materialisation is never infallible.
pub fn try_from_decoder(dec: QuoteDecoder<'_>) -> Result<Self, DecodeError>;
pub fn try_from_slice_with_header(buf: &[u8], offset: usize) -> Result<Self, DecodeError>;
pub fn encode(&self, buf: &mut [u8]) -> Result<usize, EncodeError>;
pub fn encoded_length_with_header(&self) -> Result<usize, EncodeError>;
}
Wire → DTO → wire round-trip (the docs fixture uses DomainVarData::Bytes):
#![allow(unused)]
fn main() {
// Encode a message first (the usual flyweight path)
let mut buf = [0u8; QuoteEncoder::compute_length_with_header(1, 2)];
let len = QuoteEncoder::wrap_and_apply_header(&mut buf, 0)
.fixed(&QuoteFixedFields {
seq: 1,
some_numbers: [1, 2, 3, 4],
vehicle_code: *b"ABCDEF",
qty: 10,
})
.legs(1, |legs| {
legs.add(|leg| { leg.value(99); Ok(()) })?;
Ok(())
})?
.note_as_str("hi")?
.encoded_length_with_header();
// Decode → owned DTO (allocates — not for the hot path)
let dec = QuoteDecoder::try_from(&buf[..len])?;
let mut dto = QuoteDomain::try_from_decoder(dec)?;
assert_eq!(dto.seq, 1);
assert_eq!(&dto.note, b"hi");
dto.qty = 500;
// Re-encode (integer min/max checked; eligible groups use bulk write)
let n = dto.encode(&mut buf)?;
assert_eq!(n, len);
}
with_domain_objects(DomainVarData)
SBE <data> is length-prefixed bytes. The enum picks the DTO field type:
| Call | Field type | Invalid UTF-8 | When to use |
|---|---|---|---|
.with_domain_objects(DomainVarData::Strings) | String | InvalidUtf8 error (strict; 0.1.10) | Text schemas when validity is known |
.with_domain_objects(DomainVarData::Bytes) | Vec<u8> | n/a (raw copy) | Binary tails or byte-exact re-encode |
Strings rejects invalid UTF-8. Materialise returns InvalidUtf8
for bad bytes; there is no silent empty-string fallback. Use Bytes (or stay
on flyweights) when you need audit / replay fidelity of non-UTF-8 tails.
Runnable demo (text path):
sbe-feature-tour
uses DomainVarData::Strings. Flyweight path is unchanged: with schema
characterEncoding="UTF-8" you still get into_manufacturer_as_str() without
a DTO.