//! Code generation for parsed SBE schemas.
//!
//! The generator produces one Rust module per message along with a
//! `types.rs` module which contains definitions for user defined
//! enums, sets and composites. A `mod.rs` file is also emitted to
//! re‑export the generated types for ergonomic use.
use crate::GeneratorOptions;
use crate::parser::{
CompositeField, Field, Group, GroupMember, Message, MessageMember, Schema, TypeDef,
VarDataField,
};
use heck::ToSnakeCase;
use std::collections::HashSet;
const PARSE_PREFIX_METHOD: &str = " #[inline]\n pub fn parse_prefix(body: &[u8]) -> Option<(&Self, &[u8])> {\n Ref::<_, Self>::from_prefix(body)\n .ok()\n .map(|(r, b)| (Ref::into_ref(r), b))\n }\n";
fn push_doc_comment(buf: &mut String, text: &str) {
for line in text.lines() {
buf.push_str("/// ");
buf.push_str(line.trim_end());
buf.push('\n');
}
}
fn maybe_doc_comment(buf: &mut String, desc: &Option<String>) {
if let Some(text) = desc.as_deref() {
push_doc_comment(buf, text);
}
}
fn push_file_preamble(buf: &mut String, opts: &GeneratorOptions, default_allow: Option<&str>) {
if let Some(allow) = opts.allow_attr.as_deref() {
buf.push_str(allow);
if !allow.ends_with('\n') {
buf.push('\n');
}
}
buf.push_str("// Automatically generated by sbe_gen.\n");
if let Some(allow) = default_allow {
buf.push_str(allow);
if !allow.ends_with('\n') {
buf.push('\n');
}
}
}
fn message_fields(msg: &Message) -> Vec<&Field> {
msg.members
.iter()
.filter_map(|m| {
if let MessageMember::Field(f) = m {
Some(f)
} else {
None
}
})
.collect()
}
fn message_groups(msg: &Message) -> Vec<&Group> {
msg.members
.iter()
.filter_map(|m| {
if let MessageMember::Group(g) = m {
Some(g)
} else {
None
}
})
.collect()
}
fn message_data(msg: &Message) -> Vec<&VarDataField> {
msg.members
.iter()
.filter_map(|m| {
if let MessageMember::Data(d) = m {
Some(d)
} else {
None
}
})
.collect()
}
fn group_fields(g: &Group) -> Vec<&Field> {
g.members
.iter()
.filter_map(|m| {
if let GroupMember::Field(f) = m {
Some(f)
} else {
None
}
})
.collect()
}
fn group_data(g: &Group) -> Vec<&VarDataField> {
g.members
.iter()
.filter_map(|m| {
if let GroupMember::Data(d) = m {
Some(d)
} else {
None
}
})
.collect()
}
fn group_groups(g: &Group) -> Vec<&Group> {
g.members
.iter()
.filter_map(|m| {
if let GroupMember::Group(gr) = m {
Some(gr)
} else {
None
}
})
.collect()
}
fn collect_used_types(schema: &Schema) -> HashSet<String> {
let mut used = HashSet::new();
for msg in &schema.messages {
collect_used_types_in_message(msg, &mut used);
}
for ty in schema.types.values() {
if let TypeDef::Composite { fields, .. } = ty {
for field in fields {
if let CompositeField::Ref { ty, .. } = field {
used.insert(ty.clone());
}
}
}
}
used
}
fn collect_schema_fields(schema: &Schema) -> Vec<&Field> {
let mut out = Vec::new();
for msg in &schema.messages {
collect_message_fields(msg, &mut out);
}
out
}
fn collect_message_fields<'a>(msg: &'a Message, out: &mut Vec<&'a Field>) {
for member in &msg.members {
match member {
MessageMember::Field(field) => out.push(field),
MessageMember::Group(group) => collect_group_fields(group, out),
MessageMember::Data(_) => {}
}
}
}
fn collect_group_fields<'a>(group: &'a Group, out: &mut Vec<&'a Field>) {
for member in &group.members {
match member {
GroupMember::Field(field) => out.push(field),
GroupMember::Group(nested) => collect_group_fields(nested, out),
GroupMember::Data(_) => {}
}
}
}
fn constant_type_alias_from_schema(type_name: &str, schema: &Schema) -> Option<String> {
let mut candidates = HashSet::new();
for field in collect_schema_fields(schema) {
if field.ty != type_name {
continue;
}
if let Some(value_ref) = &field.value_ref
&& let Some((ty, _)) = value_ref.split_once('.')
&& ty != type_name
&& let Some(td) = schema.types.get(ty)
&& !matches!(td, TypeDef::Composite { .. })
{
candidates.insert(ty.to_string());
}
if field.name != type_name
&& let Some(td) = schema.types.get(&field.name)
&& !matches!(td, TypeDef::Composite { .. })
{
candidates.insert(field.name.clone());
}
}
if candidates.len() == 1 {
candidates.into_iter().next()
} else {
None
}
}
fn collect_used_types_in_message(msg: &Message, used: &mut HashSet<String>) {
for member in &msg.members {
match member {
MessageMember::Field(field) => {
used.insert(field.ty.clone());
}
MessageMember::Group(group) => collect_used_types_in_group(group, used),
MessageMember::Data(data) => {
used.insert(data.ty.clone());
}
}
}
}
fn collect_used_types_in_group(group: &Group, used: &mut HashSet<String>) {
used.insert(group.dimension_type.clone());
for member in &group.members {
match member {
GroupMember::Field(field) => {
used.insert(field.ty.clone());
}
GroupMember::Group(nested) => collect_used_types_in_group(nested, used),
GroupMember::Data(data) => {
used.insert(data.ty.clone());
}
}
}
}
fn group_has_var_data(g: &Group) -> bool {
if !group_data(g).is_empty() {
return true;
}
for sub in group_groups(g) {
if group_has_var_data(sub) {
return true;
}
}
false
}
/// Generate a collection of `(filename, contents)` tuples for the
/// supplied schema.
pub fn generate(schema: &Schema, opts: &GeneratorOptions) -> Vec<(String, String)> {
let mut files = Vec::new();
// always emit a types module
let types_code = generate_types(schema, opts);
files.push(("types.rs".to_string(), types_code));
// emit standard SBE message header
files.push((
"message_header.rs".to_string(),
generate_message_header(opts),
));
// emit one file per message
for msg in &schema.messages {
let code = generate_message(msg, schema, opts);
let fname = format!("{}.rs", msg.name.to_snake_case());
files.push((fname, code));
}
// emit a mod.rs which links everything together
let mod_code = generate_mod_rs(schema, opts);
files.push(("mod.rs".to_string(), mod_code));
files
}
/// Generate the content of `mod.rs` re‑exporting messages and types.
fn generate_mod_rs(schema: &Schema, opts: &GeneratorOptions) -> String {
let mut mod_lines = String::new();
push_file_preamble(
&mut mod_lines,
opts,
Some("#![allow(dead_code, non_camel_case_types)]"),
);
mod_lines.push('\n');
mod_lines.push_str("pub mod types;\n");
mod_lines.push_str("pub mod message_header;\n");
for msg in &schema.messages {
let module = msg.name.to_snake_case();
mod_lines.push_str(&format!("pub mod {};\n", module));
}
mod_lines.push('\n');
mod_lines.push_str("// Re‑export message types\n");
mod_lines.push_str("pub use message_header::MessageHeader;\n");
for msg in &schema.messages {
let module = msg.name.to_snake_case();
mod_lines.push_str(&format!("pub use {}::{};\n", module, msg.name));
mod_lines.push_str(&format!("pub use {}::{}Builder;\n", module, msg.name));
}
mod_lines
}
/// Generate the `types.rs` module containing definitions of enums, sets and composites.
fn generate_types(schema: &Schema, opts: &GeneratorOptions) -> String {
let mut code = String::new();
let types = &schema.types;
let used_types = collect_used_types(schema);
push_file_preamble(
&mut code,
opts,
Some("#![allow(dead_code, non_camel_case_types)]"),
);
code.push_str(
"use zerocopy::{Ref, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned};\n",
);
code.push_str(&format!(
"use zerocopy::byteorder::{}_endian::*;\n",
opts.endian
));
code.push('\n');
// for each type
let mut sorted: Vec<_> = types.values().collect();
// sort by name for deterministic output
sorted.sort_by_key(|t| match t {
TypeDef::Primitive { name, .. } => name.clone(),
TypeDef::Enum { name, .. } => name.clone(),
TypeDef::Set { name, .. } => name.clone(),
TypeDef::Composite { name, .. } => name.clone(),
});
for ty in sorted {
match ty {
TypeDef::Primitive {
name,
primitive,
length,
presence,
null_value,
constant,
description,
} => {
let rust_type = primitive_to_rust(primitive, opts);
if rust_type.is_none() {
continue;
}
let rust_type = rust_type.unwrap();
maybe_doc_comment(&mut code, description);
let is_constant = presence.as_deref() == Some("constant");
let is_optional = presence.as_deref() == Some("optional");
let mut has_alias = false;
if let Some(len) = length {
code.push_str(&format!("pub type {} = [{}; {}];\n", name, rust_type, len));
has_alias = true;
} else if presence.as_deref() == Some("constant") {
if used_types.contains(name) {
let alias = opts
.constant_type_aliases
.get(name)
.cloned()
.or_else(|| constant_type_alias_from_schema(name, schema))
.unwrap_or_else(|| rust_type.clone());
code.push_str(&format!("pub type {} = {};\n", name, alias));
has_alias = true;
} else if let Some(value) = constant {
if let Some(expr) = const_scalar_expr(primitive, &rust_type, value) {
code.push_str(&format!(
"pub const {}: {} = {};\n",
name, rust_type, expr
));
} else {
code.push_str(&format!(
"pub const {}: {} = {} as {};\n",
name, rust_type, value, rust_type
));
}
} else {
code.push_str(&format!(
"pub const {}: {} = {} as {};\n",
name,
rust_type,
primitive_default_value(primitive),
rust_type
));
}
} else {
code.push_str(&format!("pub type {} = {};\n", name, rust_type));
has_alias = true;
}
if has_alias && !is_constant && (null_value.is_some() || is_optional) {
let null_raw = null_value.as_deref().or_else(|| {
if is_optional && length.is_none() {
optional_null_literal(primitive)
} else {
None
}
});
if let Some(null_raw) = null_raw {
let cname = format!("{}_NULL", name.to_uppercase());
if let Some(len) = length {
if let Some((_, expr)) =
const_array_expr(primitive, &rust_type, null_raw, *len)
{
code.push_str(&format!(
"pub const {}: {} = {};\n",
cname, name, expr
));
}
} else if let Some(expr) =
const_scalar_expr(primitive, &rust_type, null_raw)
{
code.push_str(&format!("pub const {}: {} = {};\n", cname, name, expr));
}
}
}
}
TypeDef::Enum {
name,
encoding,
values,
description,
} => {
let primitive = encoding_primitive(encoding, types);
let rust_type = primitive
.as_deref()
.and_then(|p| primitive_to_rust(p, opts))
.or_else(|| primitive_to_rust(encoding, opts))
.unwrap_or_else(|| "u8".into());
let repr_ty = primitive
.as_deref()
.and_then(optional_host_type)
.unwrap_or("u8");
let prim_is_char = primitive.as_deref() == Some("char");
maybe_doc_comment(&mut code, description);
code.push_str("#[repr(transparent)]\n");
code.push_str(
"#[derive(Debug, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned, Clone, Copy, PartialEq, Eq)]\n",
);
code.push_str(&format!("pub struct {}(pub {});\n", name, rust_type));
code.push_str(&format!("impl {} {{\n", name));
code.push_str(PARSE_PREFIX_METHOD);
code.push_str("}\n\n");
if !values.is_empty() {
let enum_name = format!("{}Enum", name);
maybe_doc_comment(&mut code, description);
code.push_str(&format!("#[repr({})]\n", repr_ty));
code.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n");
code.push_str(&format!("pub enum {} {{\n", enum_name));
for (vname, val, vdesc) in values {
let literal = if prim_is_char {
if val.len() == 1 {
let c = val.chars().next().unwrap();
format!("{}u8", c as u32)
} else {
val.clone()
}
} else {
val.clone()
};
maybe_doc_comment(&mut code, vdesc);
code.push_str(&format!(" {} = {},\n", vname, literal));
}
code.push_str("}\n");
}
// define associated constants for variants
if !values.is_empty() {
code.push_str(&format!("impl {} {{\n", name));
for (vname, val, vdesc) in values {
// convert char values to numeric if necessary
let literal = if prim_is_char {
if val.len() == 1 {
let c = val.chars().next().unwrap();
format!("{}u8", c as u32)
} else {
val.clone()
}
} else {
val.clone()
};
maybe_doc_comment(&mut code, vdesc);
code.push_str(&format!(
" pub const {}: Self = Self({});\n",
vname,
builder_value_expr(&literal, &rust_type)
));
}
if let Some(raw_expr) = scalar_expr("self.0", &rust_type) {
let enum_name = format!("{}Enum", name);
code.push_str(&format!(
" #[inline]\n pub fn as_enum(self) -> Option<{enum_name}> {{\n let raw = {raw_expr};\n match raw {{\n",
enum_name = enum_name,
raw_expr = raw_expr
));
for (vname, val, _) in values {
let literal = if prim_is_char {
if val.len() == 1 {
let c = val.chars().next().unwrap();
format!("{}u8", c as u32)
} else {
val.clone()
}
} else {
val.clone()
};
code.push_str(&format!(
" {lit} => Some({enum_name}::{vname}),\n",
lit = literal,
enum_name = enum_name,
vname = vname
));
}
code.push_str(" _ => None,\n }\n }\n");
}
code.push_str("}\n");
let enum_name = format!("{}Enum", name);
code.push_str(&format!("impl From<{}> for {} {{\n", enum_name, name));
code.push_str(&format!(
" fn from(v: {}) -> Self {{\n let raw: {} = v as {};\n let encoded = {};\n Self(encoded)\n }}\n",
enum_name,
repr_ty,
repr_ty,
builder_value_expr("raw", &rust_type)
));
code.push_str("}\n");
}
code.push('\n');
}
TypeDef::Set {
name,
encoding,
choices,
description,
} => {
let primitive = encoding_primitive(encoding, types);
let rust_type = primitive
.as_deref()
.and_then(|p| primitive_to_rust(p, opts))
.or_else(|| primitive_to_rust(encoding, opts))
.unwrap_or_else(|| "u8".into());
maybe_doc_comment(&mut code, description);
code.push_str("#[repr(transparent)]\n");
code.push_str(
"#[derive(Debug, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned, Clone, Copy, PartialEq, Eq)]\n",
);
code.push_str(&format!("pub struct {}(pub {});\n", name, rust_type));
code.push_str(&format!("impl {} {{\n", name));
code.push_str(PARSE_PREFIX_METHOD);
code.push_str("}\n\n");
if !choices.is_empty() {
code.push_str(&format!("impl {} {{\n", name));
for (cname, bit, cdesc) in choices {
// bit may be decimal or integer string
let bit_idx: u32 = bit.parse().unwrap_or(0);
maybe_doc_comment(&mut code, cdesc);
code.push_str(&format!(
" pub const {}: Self = Self({});\n",
cname,
set_bit_value_expr(bit_idx, &rust_type)
));
}
code.push_str("}\n");
}
code.push('\n');
}
TypeDef::Composite {
name,
fields,
description,
} => {
// attach doc comment if present on composite
maybe_doc_comment(&mut code, description);
// generate a struct representing the composite
code.push_str("#[repr(C)]\n");
code.push_str(
"#[derive(Debug, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned, Clone, Copy)]\n",
);
code.push_str(&format!("pub struct {} {{\n", name));
let mut const_fields = Vec::new();
for f in fields {
match f {
CompositeField::Type {
name: fname,
primitive,
length,
presence,
constant,
description,
..
} => {
if presence.as_deref() == Some("constant") {
if let Some(val) = constant {
const_fields.push((
fname.clone(),
primitive.clone(),
*length,
val.clone(),
));
}
continue;
}
if let Some(rust_type) = primitive_to_rust(primitive, opts) {
if let Some(len) = length {
maybe_doc_comment(&mut code, description);
code.push_str(&format!(
" pub {}: [{}; {}],\n",
fname.to_snake_case(),
rust_type,
len
));
} else {
maybe_doc_comment(&mut code, description);
code.push_str(&format!(
" pub {}: {},\n",
fname.to_snake_case(),
rust_type
));
}
}
}
CompositeField::Ref { name: fname, ty } => {
code.push_str(&format!(" pub {}: {},\n", fname.to_snake_case(), ty));
}
}
}
code.push_str("}\n\n");
code.push_str(&format!("impl {} {{\n", name));
code.push_str(PARSE_PREFIX_METHOD);
code.push_str("}\n\n");
let mut impl_body = String::new();
if !const_fields.is_empty() {
for (fname, prim, len, val) in const_fields {
if let Some(rust_type) = primitive_to_rust(&prim, opts) {
let cname = fname.to_uppercase();
if let Some(len) = len
&& len > 1
{
if let Some((ty, expr)) =
const_array_expr(&prim, &rust_type, &val, len)
{
impl_body.push_str(&format!(
" pub const {}: {} = {};\n",
cname, ty, expr
));
}
continue;
}
if let Some(expr) = const_scalar_expr(&prim, &rust_type, &val) {
impl_body.push_str(&format!(
" pub const {}: {} = {};\n",
cname, rust_type, expr
));
}
}
}
}
composite_null_constants(&mut impl_body, fields, schema, opts);
optional_methods_for_composite_fields(&mut impl_body, fields, schema, opts, "self");
if !impl_body.is_empty() {
code.push_str(&format!("impl {} {{\n", name));
code.push_str(&impl_body);
code.push_str("}\n\n");
}
}
}
}
code
}
/// Generate the standard SBE message header module (block length, template id, schema id, version).
fn generate_message_header(opts: &GeneratorOptions) -> String {
let mut code = String::new();
push_file_preamble(
&mut code,
opts,
Some("#![allow(dead_code, non_camel_case_types)]"),
);
code.push_str(
"use zerocopy::{Ref, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned};\n",
);
code.push_str(&format!(
"use zerocopy::byteorder::{}_endian::*;\n",
opts.endian
));
code.push('\n');
code.push_str("#[repr(C)]\n");
code.push_str(
"#[derive(Debug, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned, Clone, Copy)]\n",
);
code.push_str("pub struct MessageHeader {\n pub block_length: U16,\n pub template_id: U16,\n pub schema_id: U16,\n pub version: U16,\n}\n\n");
code.push_str("impl MessageHeader {\n");
code.push_str(PARSE_PREFIX_METHOD);
code.push_str("}\n");
code
}
/// Generate the module for a single message. This includes imports,
/// macro definition and the message struct itself. Fields using
/// unsupported types are skipped.
fn generate_message(msg: &Message, schema: &Schema, opts: &GeneratorOptions) -> String {
let fields = message_fields(msg);
let groups = message_groups(msg);
let data_fields = message_data(msg);
let has_group_data = groups.iter().any(|g| group_has_var_data(g));
let has_var_data = has_group_data || !data_fields.is_empty();
let has_fixed_string_helpers = fields.iter().any(|field| {
if field_is_constant(field, schema) {
return false;
}
resolve_type(&field.ty, schema, opts, field.byte_order.as_deref())
.as_deref()
.and_then(parse_u8_array_len)
.is_some()
});
let msg_block_length = message_block_length(msg, schema, opts);
let field_layouts = layout_fields(&fields, schema, opts);
let _offsets_present = fields.iter().any(|f| f.offset.is_some());
let mut code = String::new();
push_file_preamble(&mut code, opts, None);
code.push_str(
"use zerocopy::{Ref, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned};\n",
);
code.push_str(&format!(
"use zerocopy::byteorder::{}_endian::*;\n",
opts.endian
));
code.push_str("use crate::types::*;\n");
code.push_str("use crate::message_header::MessageHeader;\n\n");
if has_var_data || has_fixed_string_helpers {
code.push_str("use core::str;\n\n");
}
code.push_str(&format!("const ENDIAN: &str = \"{}\";\n\n", opts.endian));
code.push_str(
"#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum EncodeIntoError {\n BufferTooSmall { required: usize, available: usize },\n LengthOverflow { len: usize, max: usize },\n InvalidState(&'static str),\n}\n\n",
);
code.push_str(
"#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum DecodeFieldError {\n MissingField(&'static str),\n NullValue(&'static str),\n}\n\n",
);
if has_var_data {
code.push_str("#[derive(Debug, Clone, Copy)]\n");
code.push_str(
"pub struct VarData<'a> {\n pub len: usize,\n pub bytes: &'a [u8],\n}\n\n",
);
code.push_str("impl<'a> VarData<'a> {\n #[inline]\n pub fn as_str(&self) -> Option<&'a str> { str::from_utf8(self.bytes).ok() }\n}\n\n");
code.push_str("#[derive(Clone, Copy)]\n");
code.push_str("enum LengthKind { U8, U16, U32, U64 }\n\n");
code.push_str(
"#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct EncodeError {\n pub len: usize,\n pub max: usize,\n}\n\n",
);
code.push_str(
"fn length_max(kind: LengthKind) -> usize {\n match kind {\n LengthKind::U8 => u8::MAX as usize,\n LengthKind::U16 => u16::MAX as usize,\n LengthKind::U32 => u32::MAX as usize,\n LengthKind::U64 => u64::MAX as usize,\n }\n}\n\n",
);
code.push_str("fn read_length(kind: LengthKind, buf: &[u8]) -> Option<(usize, &[u8])> {\n match kind {\n LengthKind::U8 => {\n let (&b, rest) = buf.split_first()?;\n Some((b as usize, rest))\n }\n LengthKind::U16 => {\n let (v, rest) = Ref::<_, U16>::from_prefix(buf).ok()?;\n Some((v.get() as usize, rest))\n }\n LengthKind::U32 => {\n let (v, rest) = Ref::<_, U32>::from_prefix(buf).ok()?;\n Some((v.get() as usize, rest))\n }\n LengthKind::U64 => {\n let (v, rest) = Ref::<_, U64>::from_prefix(buf).ok()?;\n Some((v.get() as usize, rest))\n }\n }\n}\n\n");
code.push_str("fn parse_var_data<'a>(buf: &'a [u8], kind: LengthKind) -> Option<(VarData<'a>, &'a [u8])> {\n let (len, rest) = read_length(kind, buf)?;\n if rest.len() < len { return None; }\n let (bytes, tail) = rest.split_at(len);\n Some((VarData { len, bytes }, tail))\n}\n\n");
code.push_str("fn write_length(buf: &mut Vec<u8>, len: usize, kind: LengthKind, endian: &str) -> Result<(), EncodeError> {\n let max = length_max(kind);\n if len > max { return Err(EncodeError { len, max }); }\n match kind {\n LengthKind::U8 => buf.push(len as u8),\n LengthKind::U16 => {\n let v = len as u16;\n if endian == \"big\" { buf.extend_from_slice(&v.to_be_bytes()); } else { buf.extend_from_slice(&v.to_le_bytes()); }\n }\n LengthKind::U32 => {\n let v = len as u32;\n if endian == \"big\" { buf.extend_from_slice(&v.to_be_bytes()); } else { buf.extend_from_slice(&v.to_le_bytes()); }\n }\n LengthKind::U64 => {\n let v = len as u64;\n if endian == \"big\" { buf.extend_from_slice(&v.to_be_bytes()); } else { buf.extend_from_slice(&v.to_le_bytes()); }\n }\n }\n Ok(())\n}\n\n");
code.push_str("fn write_var_data(buf: &mut Vec<u8>, bytes: &[u8], kind: LengthKind, endian: &str) -> Result<(), EncodeError> {\n write_length(buf, bytes.len(), kind, endian)?;\n buf.extend_from_slice(bytes);\n Ok(())\n}\n\n");
code.push_str("fn write_length_into(dst: &mut [u8], offset: usize, len: usize, kind: LengthKind, endian: &str) -> Result<usize, EncodeIntoError> {\n let max = length_max(kind);\n if len > max { return Err(EncodeIntoError::LengthOverflow { len, max }); }\n match kind {\n LengthKind::U8 => {\n let end = offset.checked_add(1).ok_or(EncodeIntoError::InvalidState(\"length offset overflow\"))?;\n if end > dst.len() { return Err(EncodeIntoError::BufferTooSmall { required: end, available: dst.len() }); }\n dst[offset] = len as u8;\n Ok(1)\n }\n LengthKind::U16 => {\n let end = offset.checked_add(2).ok_or(EncodeIntoError::InvalidState(\"length offset overflow\"))?;\n if end > dst.len() { return Err(EncodeIntoError::BufferTooSmall { required: end, available: dst.len() }); }\n let v = len as u16;\n if endian == \"big\" { dst[offset..end].copy_from_slice(&v.to_be_bytes()); } else { dst[offset..end].copy_from_slice(&v.to_le_bytes()); }\n Ok(2)\n }\n LengthKind::U32 => {\n let end = offset.checked_add(4).ok_or(EncodeIntoError::InvalidState(\"length offset overflow\"))?;\n if end > dst.len() { return Err(EncodeIntoError::BufferTooSmall { required: end, available: dst.len() }); }\n let v = len as u32;\n if endian == \"big\" { dst[offset..end].copy_from_slice(&v.to_be_bytes()); } else { dst[offset..end].copy_from_slice(&v.to_le_bytes()); }\n Ok(4)\n }\n LengthKind::U64 => {\n let end = offset.checked_add(8).ok_or(EncodeIntoError::InvalidState(\"length offset overflow\"))?;\n if end > dst.len() { return Err(EncodeIntoError::BufferTooSmall { required: end, available: dst.len() }); }\n let v = len as u64;\n if endian == \"big\" { dst[offset..end].copy_from_slice(&v.to_be_bytes()); } else { dst[offset..end].copy_from_slice(&v.to_le_bytes()); }\n Ok(8)\n }\n }\n}\n\n");
code.push_str("fn write_var_data_into(dst: &mut [u8], offset: usize, bytes: &[u8], kind: LengthKind, endian: &str) -> Result<usize, EncodeIntoError> {\n let prefix = write_length_into(dst, offset, bytes.len(), kind, endian)?;\n let data_start = offset.checked_add(prefix).ok_or(EncodeIntoError::InvalidState(\"var data offset overflow\"))?;\n let data_end = data_start.checked_add(bytes.len()).ok_or(EncodeIntoError::InvalidState(\"var data length overflow\"))?;\n if data_end > dst.len() { return Err(EncodeIntoError::BufferTooSmall { required: data_end, available: dst.len() }); }\n dst[data_start..data_end].copy_from_slice(bytes);\n Ok(prefix + bytes.len())\n}\n\n");
}
code.push_str(
"fn ensure_len(buf: &mut Vec<u8>, len: usize) {\n if buf.len() < len { buf.resize(len, 0); }\n}\n\n",
);
code.push_str(
"fn write_bytes_at<T: IntoBytes + Immutable>(buf: &mut Vec<u8>, offset: usize, value: &T) {\n let bytes = value.as_bytes();\n ensure_len(buf, offset + bytes.len());\n buf[offset..offset + bytes.len()].copy_from_slice(bytes);\n}\n\n",
);
code.push_str(
"fn write_bytes_into<T: IntoBytes + Immutable>(dst: &mut [u8], offset: usize, value: &T) -> Result<(), EncodeIntoError> {\n let bytes = value.as_bytes();\n let end = offset.checked_add(bytes.len()).ok_or(EncodeIntoError::InvalidState(\"fixed field offset overflow\"))?;\n if end > dst.len() { return Err(EncodeIntoError::BufferTooSmall { required: end, available: dst.len() }); }\n dst[offset..end].copy_from_slice(bytes);\n Ok(())\n}\n\n",
);
code.push_str(
"fn write_bytes_into_in_bounds<T: IntoBytes + Immutable>(dst: &mut [u8], offset: usize, value: &T) {\n let bytes = value.as_bytes();\n let end = offset + bytes.len();\n debug_assert!(end <= dst.len(), \"fixed field write in bounds\");\n dst[offset..end].copy_from_slice(bytes);\n}\n\n",
);
if has_fixed_string_helpers {
code.push_str(
"#[inline]\nfn trim_ascii_space_and_nul_right(mut bytes: &[u8]) -> &[u8] {\n while let Some((&last, rest)) = bytes.split_last() {\n if last == b' ' || last == 0 {\n bytes = rest;\n } else {\n break;\n }\n }\n bytes\n}\n\n",
);
}
// struct definition
code.push_str("#[repr(C)]\n");
code.push_str(
"#[derive(Debug, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned, Clone, Copy)]\n",
);
code.push_str(&format!("pub struct {} {{\n", msg.name));
write_fields_with_offsets(&mut code, &fields, schema, opts);
code.push_str("}\n\n");
// associated constants for constant fields
push_constant_field_impl(&mut code, &msg.name, &fields, schema, opts);
// impl parse_prefix
code.push_str(&format!("impl {} {{\n", msg.name));
code.push_str(PARSE_PREFIX_METHOD);
code.push_str(&format!(
" pub const BLOCK_LENGTH: u16 = {};\n",
msg_block_length
));
code.push_str(&format!(" pub const TEMPLATE_ID: u16 = {};\n", msg.id));
let schema_id = schema.schema_id.unwrap_or(0);
let schema_version = schema.version.unwrap_or(0);
code.push_str(&format!(" pub const SCHEMA_ID: u16 = {};\n", schema_id));
code.push_str(&format!(
" pub const SCHEMA_VERSION: u16 = {};\n",
schema_version
));
optional_methods_for_fields(&mut code, &fields, schema, opts, "self");
// message metadata
let msg_since = msg.since_version.unwrap_or(0);
let msg_sem = msg.semantic_type.clone().unwrap_or_default();
code.push_str(&format!(
" pub const SINCE_VERSION: u32 = {};\n",
msg_since
));
code.push_str(&format!(
" pub const SEMANTIC_TYPE: &'static str = \"{}\";\n",
msg_sem
));
// field metadata
for f in &fields {
let sv = f.since_version.unwrap_or(0);
let sem = f.semantic_type.clone().unwrap_or_default();
if let Some(off) = f.offset {
code.push_str(&format!(
" pub const {}_OFFSET: u32 = {};\n",
f.name.to_uppercase(),
off
));
}
if let Some(ref minv) = f.min_value {
code.push_str(&format!(
" pub const {}_MIN: &str = \"{}\";\n",
f.name.to_uppercase(),
minv
));
}
if let Some(ref maxv) = f.max_value {
code.push_str(&format!(
" pub const {}_MAX: &str = \"{}\";\n",
f.name.to_uppercase(),
maxv
));
}
if let Some(ref nullv) = f.null_value {
code.push_str(&format!(
" pub const {}_NULL: &str = \"{}\";\n",
f.name.to_uppercase(),
nullv
));
}
if let Some(ref initv) = f.initial_value {
code.push_str(&format!(
" pub const {}_INITIAL: &str = \"{}\";\n",
f.name.to_uppercase(),
initv
));
}
code.push_str(&format!(
" pub const {}_SINCE_VERSION: u32 = {};\n",
f.name.to_uppercase(),
sv
));
code.push_str(&format!(
" pub const {}_SEMANTIC_TYPE: &'static str = \"{}\";\n",
f.name.to_uppercase(),
sem
));
}
if !data_fields.is_empty() {
for d in &data_fields {
let fn_name = d.name.to_snake_case();
let kind = length_kind_token(length_kind_for_data(&d.ty, schema, opts));
code.push_str(&format!(
" #[inline]\n pub fn parse_{}<'a>(&self, buf: &'a [u8]) -> Option<(VarData<'a>, &'a [u8])> {{\n parse_var_data(buf, {})\n }}\n",
fn_name, kind
));
}
}
code.push_str("}\n\n");
let builder_name = format!("{}Builder", msg.name);
let encoder_name = format!("{}Encoder", msg.name);
let fixed_required = msg_block_length.max(
field_layouts
.iter()
.map(|layout| layout.offset + layout.size)
.max()
.unwrap_or(0),
);
code.push_str(&format!(
"pub struct {} {{\n buf: Vec<u8>,\n}}\n\n",
builder_name
));
code.push_str(&format!("impl {} {{\n", builder_name));
code.push_str(&format!(
" pub const BLOCK_LENGTH: u16 = {}::BLOCK_LENGTH;\n",
msg.name
));
code.push_str(&format!(
" pub const TEMPLATE_ID: u16 = {}::TEMPLATE_ID;\n",
msg.name
));
code.push_str(&format!(
" pub const SCHEMA_ID: u16 = {}::SCHEMA_ID;\n",
msg.name
));
code.push_str(&format!(
" pub const SCHEMA_VERSION: u16 = {}::SCHEMA_VERSION;\n",
msg.name
));
code.push_str(
" pub fn new() -> Self {\n Self { buf: vec![0u8; Self::BLOCK_LENGTH as usize] }\n }\n",
);
code.push_str(
" pub fn with_capacity(capacity: usize) -> Self {\n let mut buf = vec![0u8; Self::BLOCK_LENGTH as usize];\n buf.reserve(capacity);\n Self { buf }\n }\n",
);
for layout in &field_layouts {
let field = layout.field;
if field_is_constant(field, schema) {
continue;
}
let offset = layout.offset;
let field_name = field.name.to_snake_case();
if let Some(resolved_type) =
resolve_type(&field.ty, schema, opts, field.byte_order.as_deref())
{
let param_ty = builder_param_type(&resolved_type);
let encoded_expr = builder_value_expr("value", &resolved_type);
let needs_checks = field.min_value.is_some()
|| field.max_value.is_some()
|| (field.presence.as_deref() != Some("optional") && field.null_value.is_some());
if needs_checks {
if let (Some(host_ty), Some(raw_expr)) = (
host_type_for_field(field, schema),
raw_expr_for_value(field, "value", schema, opts),
) {
code.push_str(&format!(" pub fn {name}(&mut self, value: {param}) -> &mut Self {{\n let raw: {host} = {raw};\n", name = field_name, param = param_ty, host = host_ty, raw = raw_expr));
push_validation_asserts(&mut code, field, schema, &host_ty);
code.push_str(&format!(
" let encoded = {expr};\n write_bytes_at(&mut self.buf, {offset}usize, &encoded);\n self\n }}\n",
expr = encoded_expr,
offset = offset
));
} else {
code.push_str(&format!(
" pub fn {name}(&mut self, value: {param}) -> &mut Self {{\n let encoded = {expr};\n write_bytes_at(&mut self.buf, {offset}usize, &encoded);\n self\n }}\n",
name = field_name,
param = param_ty,
expr = encoded_expr,
offset = offset
));
}
} else {
code.push_str(&format!(
" pub fn {name}(&mut self, value: {param}) -> &mut Self {{\n let encoded = {expr};\n write_bytes_at(&mut self.buf, {offset}usize, &encoded);\n self\n }}\n",
name = field_name,
param = param_ty,
expr = encoded_expr,
offset = offset
));
}
}
}
for g in &groups {
let builder_ty = format!("{}GroupBuilder", g.name);
let gname = g.name.to_snake_case();
code.push_str(&format!(
" pub fn {name}<F>(&mut self, f: F) -> &mut Self\n where\n F: FnOnce(&mut {builder_ty}),\n {{\n let mut builder = {builder_ty}::new(&mut self.buf);\n f(&mut builder);\n builder.finish();\n self\n }}\n",
name = gname,
builder_ty = builder_ty
));
}
for d in &data_fields {
let name = d.name.to_snake_case();
let kind = length_kind_token(length_kind_for_data(&d.ty, schema, opts));
code.push_str(&format!(
" pub fn {name}(&mut self, bytes: &[u8]) -> Result<&mut Self, EncodeError> {{\n write_var_data(&mut self.buf, bytes, {kind}, ENDIAN)?;\n Ok(self)\n }}\n",
name = name,
kind = kind
));
}
code.push_str(" pub fn finish(self) -> Vec<u8> {\n self.buf\n }\n");
code.push_str(
" pub fn finish_with_header(self) -> Vec<u8> {\n let mut out = Vec::with_capacity(self.buf.len() + core::mem::size_of::<MessageHeader>());\n let header = MessageHeader {\n block_length: U16::new(Self::BLOCK_LENGTH),\n template_id: U16::new(Self::TEMPLATE_ID),\n schema_id: U16::new(Self::SCHEMA_ID),\n version: U16::new(Self::SCHEMA_VERSION),\n };\n out.extend_from_slice(header.as_bytes());\n out.extend_from_slice(&self.buf);\n out\n }\n",
);
code.push_str("}\n\n");
code.push_str(&format!(
"/// Zero-allocation encoder writing directly into caller-provided memory.\n\
pub struct {}<'a> {{\n buf: &'a mut [u8],\n used: usize,\n}}\n\n",
encoder_name
));
code.push_str(&format!("impl<'a> {}<'a> {{\n", encoder_name));
code.push_str(&format!(
" pub const BLOCK_LENGTH: u16 = {}::BLOCK_LENGTH;\n",
msg.name
));
code.push_str(&format!(
" pub const FIXED_LEN: usize = {};\n",
fixed_required
));
code.push_str(
" /// Create an encoder over `buf`.\n ///\n /// The slice must have at least `FIXED_LEN` bytes. The fixed block is zero-initialized,\n /// and variable-size fields append after the fixed block.\n pub fn new(buf: &'a mut [u8]) -> Result<Self, EncodeIntoError> {\n if buf.len() < Self::FIXED_LEN {\n return Err(EncodeIntoError::BufferTooSmall { required: Self::FIXED_LEN, available: buf.len() });\n }\n buf[..Self::FIXED_LEN].fill(0);\n Ok(Self { buf, used: Self::FIXED_LEN })\n }\n",
);
code.push_str(" pub fn encoded_len(&self) -> usize {\n self.used\n }\n");
code.push_str(" pub fn as_slice(&self) -> &[u8] {\n &self.buf[..self.used]\n }\n");
for layout in &field_layouts {
let field = layout.field;
if field_is_constant(field, schema) {
continue;
}
let offset = layout.offset;
let field_name = field.name.to_snake_case();
if let Some(resolved_type) =
resolve_type(&field.ty, schema, opts, field.byte_order.as_deref())
{
let param_ty = builder_param_type(&resolved_type);
let encoded_expr = builder_value_expr("value", &resolved_type);
let needs_checks = field.min_value.is_some()
|| field.max_value.is_some()
|| (field.presence.as_deref() != Some("optional") && field.null_value.is_some());
if needs_checks {
if let (Some(host_ty), Some(raw_expr)) = (
host_type_for_field(field, schema),
raw_expr_for_value(field, "value", schema, opts),
) {
code.push_str(&format!(" pub fn {name}(&mut self, value: {param}) -> &mut Self {{\n let raw: {host} = {raw};\n", name = field_name, param = param_ty, host = host_ty, raw = raw_expr));
push_validation_asserts(&mut code, field, schema, &host_ty);
code.push_str(&format!(
" let encoded = {expr};\n write_bytes_into_in_bounds(self.buf, {offset}usize, &encoded);\n self\n }}\n",
expr = encoded_expr,
offset = offset
));
} else {
code.push_str(&format!(
" pub fn {name}(&mut self, value: {param}) -> &mut Self {{\n let encoded = {expr};\n write_bytes_into_in_bounds(self.buf, {offset}usize, &encoded);\n self\n }}\n",
name = field_name,
param = param_ty,
expr = encoded_expr,
offset = offset
));
}
} else {
code.push_str(&format!(
" pub fn {name}(&mut self, value: {param}) -> &mut Self {{\n let encoded = {expr};\n write_bytes_into_in_bounds(self.buf, {offset}usize, &encoded);\n self\n }}\n",
name = field_name,
param = param_ty,
expr = encoded_expr,
offset = offset
));
}
}
}
for d in &data_fields {
let name = d.name.to_snake_case();
let kind = length_kind_token(length_kind_for_data(&d.ty, schema, opts));
code.push_str(&format!(
" pub fn {name}(&mut self, bytes: &[u8]) -> Result<&mut Self, EncodeIntoError> {{\n let written = write_var_data_into(self.buf, self.used, bytes, {kind}, ENDIAN)?;\n self.used = self.used.checked_add(written).ok_or(EncodeIntoError::InvalidState(\"encoded length overflow\"))?;\n Ok(self)\n }}\n",
name = name,
kind = kind
));
}
for g in &groups {
let encoder_ty = format!("{}GroupEncoder", g.name);
let gname = g.name.to_snake_case();
code.push_str(&format!(
" pub fn {name}<F>(&mut self, f: F) -> Result<&mut Self, EncodeIntoError>\n where\n F: FnOnce(&mut {encoder}<'_>) -> Result<(), EncodeIntoError>,\n {{\n let mut encoder = {encoder}::new(self.buf, self.used)?;\n f(&mut encoder)?;\n self.used = encoder.finish()?;\n Ok(self)\n }}\n",
name = gname,
encoder = encoder_ty
));
}
code.push_str(" pub fn finish(self) -> usize {\n self.used\n }\n");
code.push_str("}\n\n");
code.push_str(&format!("impl {} {{\n", msg.name));
code.push_str(&format!(
" /// Encode message body (fixed block + variable data) into `dst` without allocation.\n ///\n /// Returns number of body bytes written.\n pub fn encode_body_into<F>(dst: &mut [u8], f: F) -> Result<usize, EncodeIntoError>\n where\n F: FnOnce(&mut {encoder}<'_>) -> Result<(), EncodeIntoError>,\n {{\n let mut encoder = {encoder}::new(dst)?;\n f(&mut encoder)?;\n Ok(encoder.finish())\n }}\n",
encoder = encoder_name
));
code.push_str(&format!(
" /// Encode header + body into `dst` without allocation.\n ///\n /// `dst` must fit `size_of::<MessageHeader>() + body_len`. Returns total bytes written.\n pub fn encode_with_header_into<F>(dst: &mut [u8], header: MessageHeader, f: F) -> Result<usize, EncodeIntoError>\n where\n F: FnOnce(&mut {encoder}<'_>) -> Result<(), EncodeIntoError>,\n {{\n let header_len = core::mem::size_of::<MessageHeader>();\n if dst.len() < header_len {{\n return Err(EncodeIntoError::BufferTooSmall {{ required: header_len, available: dst.len() }});\n }}\n let body_len = {{\n let (_, body_dst) = dst.split_at_mut(header_len);\n let mut encoder = {encoder}::new(body_dst)?;\n f(&mut encoder)?;\n encoder.finish()\n }};\n let total = header_len.checked_add(body_len).ok_or(EncodeIntoError::InvalidState(\"encoded frame length overflow\"))?;\n if dst.len() < total {{\n return Err(EncodeIntoError::BufferTooSmall {{ required: total, available: dst.len() }});\n }}\n write_bytes_into(dst, 0, &header)?;\n Ok(total)\n }}\n",
encoder = encoder_name
));
code.push_str("}\n\n");
// group helpers (supports nesting)
for g in groups {
emit_group(g, schema, opts, &mut code);
}
// acting version aware view
code.push_str(&format!(
"#[derive(Debug, Clone, Copy)]\npub struct {}Body<'a> {{\n parsed: Option<&'a {}>,\n raw: &'a [u8],\n}}\n\n",
msg.name, msg.name
));
code.push_str(&format!(
"impl<'a> core::ops::Deref for {}Body<'a> {{\n type Target = {};\n fn deref(&self) -> &Self::Target {{\n self.parsed.expect(\"message body shorter than current layout; use accessor methods\")\n }}\n}}\n\n",
msg.name, msg.name
));
code.push_str(&format!(
"impl<'a> {}Body<'a> {{\n fn parsed(&self) -> Option<&'a {}> {{\n self.parsed\n }}\n fn bytes(&self) -> &[u8] {{\n self.raw\n }}\n}}\n\n",
msg.name, msg.name
));
code.push_str(&format!(
"#[derive(Debug, Clone)]\npub struct {}View<'a> {{\n pub body: {}Body<'a>,\n pub acting_block_length: usize,\n pub acting_version: u16,\n}}\n\n",
msg.name, msg.name
));
code.push_str(&format!("impl<'a> {}View<'a> {{\n", msg.name));
code.push_str(&format!(
" #[inline]\n pub fn is_fixed_layout(&self) -> bool {{\n self.acting_version >= {name}::SCHEMA_VERSION\n && self.acting_block_length >= core::mem::size_of::<{name}>()\n }}\n",
name = msg.name
));
for field in &fields {
let fname = field.name.to_snake_case();
let since = field.since_version.unwrap_or(0);
if field_is_constant(field, schema) {
code.push_str(&format!(
" #[inline]\n pub fn has_{fname}(&self) -> bool {{\n self.acting_version >= {since} as u16\n }}\n",
fname = fname,
since = since
));
if let Some((ty, _)) = constant_field_value_expr(field, schema, opts) {
code.push_str(&format!(
" #[inline]\n pub fn {fname}(&self) -> Option<{ty}> {{\n if !self.has_{fname}() {{ return None; }}\n Some({msg_name}::{const_name})\n }}\n",
fname = fname,
ty = ty,
msg_name = msg.name,
const_name = fname.to_uppercase()
));
}
continue;
}
let Some(layout) = field_layouts
.iter()
.find(|layout| std::ptr::eq(layout.field, *field))
else {
continue;
};
code.push_str(&format!(
" #[inline]\n pub fn has_{fname}(&self) -> bool {{\n if self.acting_version < {since} as u16 {{ return false; }}\n {offset} + {size} <= self.acting_block_length\n }}\n",
fname = fname,
since = since,
offset = layout.offset,
size = layout.size
));
if let Some(resolved) = resolve_type(&field.ty, schema, opts, field.byte_order.as_deref()) {
code.push_str(&format!(
" #[inline]\n pub fn {fname}(&self) -> Option<&{ty}> {{\n if !self.has_{fname}() {{ return None; }}\n if let Some(msg) = self.body.parsed() {{ return Some(&msg.{field_name}); }}\n let bytes = &self.body.bytes()[{offset}..{offset_plus}];\n let (r, _) = Ref::<_, {ty}>::from_prefix(bytes).ok()?;\n Some(Ref::into_ref(r))\n }}\n",
fname = fname,
field_name = fname,
ty = resolved,
offset = layout.offset,
offset_plus = layout.offset + layout.size
));
emit_view_field_helpers(&mut code, field, schema, opts);
}
}
if !data_fields.is_empty() {
for d in &data_fields {
let fn_name = d.name.to_snake_case();
let kind = length_kind_token(length_kind_for_data(&d.ty, schema, opts));
code.push_str(&format!(
" #[inline]\n pub fn parse_{name}<'b>(&self, buf: &'b [u8]) -> Option<(VarData<'b>, &'b [u8])> {{\n parse_var_data(buf, {kind})\n }}\n",
name = fn_name,
kind = kind
));
}
}
code.push_str("}\n\n");
code.push_str(&format!(
"pub fn parse_with_header<'a>(body: &'a [u8], header: &MessageHeader) -> Option<({name}View<'a>, &'a [u8])> {{\n let mut acting_block_length = header.block_length.get() as usize;\n if acting_block_length == 0 {{ acting_block_length = {name}::BLOCK_LENGTH as usize; }}\n let acting_version = header.version.get();\n if body.len() < acting_block_length {{ return None; }}\n let needed = core::mem::size_of::<{name}>();\n let (prefix, rest) = body.split_at(acting_block_length);\n let (parsed, raw) = if acting_block_length >= needed {{\n let raw = &prefix[..needed];\n let (msg, _) = Ref::<_, {name}>::from_prefix(raw).ok()?;\n (Some(Ref::into_ref(msg)), raw)\n }} else {{\n (None, prefix)\n }};\n let view = {name}View {{ body: {name}Body {{ parsed, raw }}, acting_block_length, acting_version }};\n Some((view, rest))\n}}\n",
name = msg.name
));
code
}
/// Map an SBE primitive to a Rust zero‑copy type. Returns `None` if
/// the primitive is not recognised.
fn primitive_to_rust(prim: &str, _opts: &GeneratorOptions) -> Option<String> {
match prim {
// boolean represented as u8
"boolean" | "uint8" => Some("u8".into()),
"int8" => Some("i8".into()),
"char" => Some("u8".into()),
"uint16" => Some("U16".into()),
"int16" => Some("I16".into()),
"uint32" => Some("U32".into()),
"int32" => Some("I32".into()),
"uint64" => Some("U64".into()),
"int64" => Some("I64".into()),
"float" => Some("F32".into()),
"double" => Some("F64".into()),
"varStringEncoding" | "varAsciiEncoding" | "varDataEncoding" => None,
_ => None,
}
}
fn primitive_to_rust_endian(
prim: &str,
schema_endian: &str,
override_endian: Option<&str>,
) -> Option<String> {
let endian = override_endian.unwrap_or(schema_endian);
let short = override_endian.is_none() || override_endian == Some(schema_endian);
let ty = match prim {
"boolean" | "uint8" => "u8",
"int8" => "i8",
"char" => "u8",
"uint16" => "U16",
"int16" => "I16",
"uint32" => "U32",
"int32" => "I32",
"uint64" => "U64",
"int64" => "I64",
"float" => "F32",
"double" => "F64",
"varStringEncoding" | "varAsciiEncoding" | "varDataEncoding" => return None,
_ => return None,
};
if short {
Some(ty.into())
} else {
Some(format!("zerocopy::byteorder::{}_endian::{}", endian, ty))
}
}
/// Provide a default constant value for a primitive type.
fn primitive_default_value(prim: &str) -> &'static str {
match prim {
"int8" | "uint8" | "char" | "boolean" => "0",
"int16" | "uint16" => "0",
"int32" | "uint32" => "0",
"int64" | "uint64" => "0",
"float" => "0.0",
"double" => "0.0",
_ => "0",
}
}
fn const_scalar_expr(prim: &str, rust_type: &str, raw: &str) -> Option<String> {
let trimmed = raw.trim();
let value = match prim {
"char" => {
let byte = trimmed.as_bytes().first().copied().unwrap_or(0);
format!("{}u8", byte)
}
"boolean" => match trimmed {
"true" => "1u8".into(),
"false" => "0u8".into(),
other => other.to_string(),
},
_ => trimmed.to_string(),
};
let base = rust_type.rsplit("::").next().unwrap_or(rust_type);
Some(match base {
"U16" | "I16" | "U32" | "I32" | "U64" | "I64" | "F32" | "F64" => {
format!("{}::new({})", rust_type, value)
}
_ => value,
})
}
fn const_array_expr(
prim: &str,
rust_type: &str,
raw: &str,
len: usize,
) -> Option<(String, String)> {
if len == 0 {
return None;
}
if prim == "char" {
let mut bytes = raw.as_bytes().to_vec();
while bytes.len() < len {
bytes.push(0u8);
}
if bytes.len() > len {
bytes.truncate(len);
}
let expr = format!(
"[{}]",
bytes
.iter()
.map(|b| format!("{}u8", b))
.collect::<Vec<_>>()
.join(", ")
);
let ty = format!("[{}; {}]", rust_type, len);
return Some((ty, expr));
}
let mut parts: Vec<&str> = raw
.split(|c: char| c.is_ascii_whitespace() || c == ',')
.filter(|s| !s.is_empty())
.collect();
if parts.is_empty() {
parts.push(primitive_default_value(prim));
}
let mut exprs = Vec::new();
for part in &parts {
exprs.push(const_scalar_expr(prim, rust_type, part)?);
}
if exprs.len() < len {
let fill = exprs.last().cloned()?;
exprs.resize(len, fill);
} else if exprs.len() > len {
exprs.truncate(len);
}
let expr = format!("[{}]", exprs.join(", "));
let ty = format!("[{}; {}]", rust_type, len);
Some((ty, expr))
}
/// Resolve a field's type name to the appropriate Rust type. This
/// consults the type map for user defined types or falls back to
/// primitive mapping. Unsupported types return `None`, causing the
/// field to be skipped.
fn resolve_type(
name: &str,
schema: &Schema,
opts: &GeneratorOptions,
override_endian: Option<&str>,
) -> Option<String> {
// first check if it's a primitive built‑in
if let Some(rust) = primitive_to_rust_endian(name, &opts.endian, override_endian) {
return Some(rust);
}
// check user defined types
if let Some(td) = schema.types.get(name) {
match td {
TypeDef::Primitive {
name: tname,
primitive,
length,
..
} => {
let rust = primitive_to_rust_endian(primitive, &opts.endian, override_endian)?;
if let Some(len) = length {
Some(format!("[{}; {}]", rust, len))
} else if override_endian.is_some() && override_endian != Some(opts.endian.as_str())
{
Some(rust)
} else {
Some(tname.clone())
}
}
TypeDef::Enum { name: tname, .. } => Some(tname.clone()),
TypeDef::Set { name: tname, .. } => Some(tname.clone()),
TypeDef::Composite { name: tname, .. } => Some(tname.clone()),
}
} else {
None
}
}
#[allow(dead_code)]
fn resolved_size_bytes(rust_type: &str) -> Option<usize> {
let ty = rust_type.trim();
if let Some(rest) = ty.strip_prefix('[')
&& let Some((inner, len_part)) = rest.split_once(';')
{
let inner = inner.trim();
let len_part = len_part.trim().trim_end_matches(']');
if let Ok(n) = len_part.parse::<usize>()
&& let Some(sz) = resolved_size_bytes(inner)
{
return Some(sz * n);
}
}
let base = ty.rsplit("::").next().unwrap_or(ty);
match base {
"u8" | "i8" | "char" => Some(1),
"U16" | "I16" | "u16" | "i16" => Some(2),
"U32" | "I32" | "u32" | "i32" | "F32" | "f32" => Some(4),
"U64" | "I64" | "u64" | "i64" | "F64" | "f64" => Some(8),
_ => None,
}
}
fn primitive_size_bytes(prim: &str) -> Option<usize> {
match prim {
"boolean" | "uint8" | "int8" | "char" => Some(1),
"uint16" | "int16" => Some(2),
"uint32" | "int32" | "float" => Some(4),
"uint64" | "int64" | "double" => Some(8),
_ => None,
}
}
fn type_size_bytes(
ty: &str,
schema: &Schema,
_opts: &GeneratorOptions,
depth: usize,
respect_constant: bool,
) -> Option<usize> {
if depth > 8 {
return None;
}
if let Some(sz) = primitive_size_bytes(ty) {
return Some(sz);
}
if let Some(td) = schema.types.get(ty) {
match td {
TypeDef::Primitive {
primitive,
length,
presence,
..
} => {
if respect_constant && presence.as_deref() == Some("constant") {
return Some(0);
}
let base = primitive_size_bytes(primitive)?;
Some(base * length.unwrap_or(1))
}
TypeDef::Enum { encoding, .. } | TypeDef::Set { encoding, .. } => {
encoding_primitive(encoding, &schema.types).and_then(|p| primitive_size_bytes(&p))
}
TypeDef::Composite { fields, .. } => {
let mut total = 0usize;
for f in fields {
match f {
CompositeField::Type {
primitive,
length,
presence,
..
} => {
if presence.as_deref() == Some("constant") {
continue;
}
let base = primitive_size_bytes(primitive)?;
total += base * length.unwrap_or(1);
}
CompositeField::Ref { ty, .. } => {
total +=
type_size_bytes(ty, schema, _opts, depth + 1, respect_constant)?;
}
}
}
Some(total)
}
}
} else {
resolve_type(ty, schema, _opts, None).and_then(|resolved| resolved_size_bytes(&resolved))
}
}
fn field_size_bytes(field: &Field, schema: &Schema, opts: &GeneratorOptions) -> Option<usize> {
// constant fields are not encoded
if field_is_constant(field, schema) {
return Some(0);
}
if field.presence.is_some()
&& let Some(TypeDef::Primitive {
primitive,
length,
presence: Some(p),
..
}) = schema.types.get(&field.ty)
&& p == "constant"
{
let base = primitive_size_bytes(primitive)?;
return Some(base * length.unwrap_or(1));
}
type_size_bytes(&field.ty, schema, opts, 0, true)
}
fn write_fields_with_offsets(
code: &mut String,
fields: &[&Field],
schema: &Schema,
opts: &GeneratorOptions,
) {
let mut cur_offset: Option<usize> = Some(0);
let mut pad_idx = 0;
for field in fields {
if field_is_constant(field, schema) {
continue;
}
let ty_name = &field.ty;
let field_name = field.name.to_snake_case();
if let Some(rust_type) = resolve_type(ty_name, schema, opts, field.byte_order.as_deref()) {
let field_sz = field_size_bytes(field, schema, opts);
if let (Some(target), Some(cur)) = (field.offset.map(|o| o as usize), cur_offset)
&& target > cur
{
let pad = target - cur;
code.push_str(&format!(" __padding{}: [u8; {}],\n", pad_idx, pad));
pad_idx += 1;
cur_offset = Some(target);
}
maybe_doc_comment(code, &field.description);
code.push_str(&format!(" pub {}: {},\n", field_name, rust_type));
if let Some(sz) = field_sz {
if let Some(cur) = cur_offset {
cur_offset = Some(cur + sz);
}
} else {
cur_offset = None;
}
}
}
}
#[derive(Clone, Copy)]
enum LengthKind {
U8,
U16,
U32,
U64,
}
fn primitive_length_kind(prim: &str) -> Option<LengthKind> {
match prim {
"boolean" | "uint8" | "int8" | "char" => Some(LengthKind::U8),
"uint16" | "int16" => Some(LengthKind::U16),
"uint32" | "int32" => Some(LengthKind::U32),
"uint64" | "int64" => Some(LengthKind::U64),
_ => None,
}
}
fn length_kind_for_data(ty: &str, schema: &Schema, opts: &GeneratorOptions) -> LengthKind {
if let Some(kind) = primitive_length_kind(ty) {
return kind;
}
if let Some(td) = schema.types.get(ty) {
match td {
TypeDef::Primitive { primitive, .. } => {
if let Some(kind) = primitive_length_kind(primitive) {
return kind;
}
}
TypeDef::Enum { encoding, .. } | TypeDef::Set { encoding, .. } => {
if let Some(kind) = primitive_length_kind(encoding) {
return kind;
}
}
TypeDef::Composite { fields, .. } => {
for f in fields {
if let CompositeField::Type {
primitive,
presence,
..
} = f
{
if presence.as_deref() == Some("constant") {
continue;
}
if let Some(kind) = primitive_length_kind(primitive) {
return kind;
}
}
}
}
}
}
// fall back to little length prefix if unknown
let _ = opts;
LengthKind::U8
}
fn length_kind_token(kind: LengthKind) -> &'static str {
match kind {
LengthKind::U8 => "LengthKind::U8",
LengthKind::U16 => "LengthKind::U16",
LengthKind::U32 => "LengthKind::U32",
LengthKind::U64 => "LengthKind::U64",
}
}
fn emit_group(g: &Group, schema: &Schema, opts: &GeneratorOptions, code: &mut String) {
let group_name = &g.name;
let group_snake = g.name.to_snake_case();
let entry_struct = format!("{}Entry", group_name);
let entry_body = format!("{}Body", entry_struct);
let entry_view = format!("{}EntryView", group_name);
let parse_entry_body_fn = format!("parse_{}_entry_body", group_snake);
let group_struct = format!("{}Group", group_name);
let g_block_length = group_block_length(g, schema, opts);
let iter_name = format!("{}Iter", group_name);
let group_builder = format!("{}GroupBuilder", group_name);
let entry_builder = format!("{}EntryBuilder", group_name);
let group_encoder = format!("{}GroupEncoder", group_name);
let entry_encoder = format!("{}EntryEncoder", group_name);
let g_fields = group_fields(g);
let entry_layout = layout_fields(&g_fields, schema, opts);
let entry_fixed_required = g_block_length.max(
entry_layout
.iter()
.map(|layout| layout.offset + layout.size)
.max()
.unwrap_or(0),
);
let g_data = group_data(g);
let g_nested = group_groups(g);
let has_data = !g_data.is_empty();
let has_nested = !g_nested.is_empty();
let has_any_var = has_data || has_nested;
let dim = dimension_fields(schema, &g.dimension_type, opts);
let block_len_expr = if let Some(bl) = g.block_length {
bl.to_string()
} else {
format!("core::mem::size_of::<{}>()", entry_struct)
};
let dim_block_offset =
composite_field_offset(&g.dimension_type, &dim.block_field, schema, opts).unwrap_or(0);
let dim_count_offset =
composite_field_offset(&g.dimension_type, &dim.count_field, schema, opts).unwrap_or(0);
let dim_size = type_size_bytes(&g.dimension_type, schema, opts, 0, true).unwrap_or(0);
code.push_str(&format!(
"pub fn parse_{}<'a>(buf: &'a [u8]) -> Option<{}<'a>> {{\n",
group_snake, group_struct
));
code.push_str(&format!(
" let (header, payload) = {}::parse_prefix(buf)?;\n",
g.dimension_type
));
code.push_str(&format!(
" let header_block_len = {};\n",
to_usize_expr(&format!("header.{}", dim.block_field), &dim.block_field_ty)
));
code.push_str(&format!(
" let declared = {} as usize;\n",
block_len_expr
));
code.push_str(
" let block_length = if header_block_len == 0 { declared } else { header_block_len };\n",
);
code.push_str(&format!(
" Some({} {{ header, payload, block_length }})\n",
group_struct
));
code.push_str("}\n\n");
code.push_str(&format!(
"#[derive(Debug, Clone, Copy)]\npub struct {}<'a> {{\n pub header: &'a {},\n payload: &'a [u8],\n block_length: usize,\n}}\n\n",
group_struct, g.dimension_type
));
code.push_str(&format!("impl<'a> {}<'a> {{\n", group_struct));
let g_since = g.since_version.unwrap_or(0);
let g_sem = g.semantic_type.clone().unwrap_or_default();
code.push_str(&format!(
" pub const SINCE_VERSION: u32 = {};\n",
g_since
));
code.push_str(&format!(
" pub const SEMANTIC_TYPE: &'static str = \"{}\";\n",
g_sem
));
code.push_str(&format!(
" pub fn count(&self) -> usize {{ {} }}\n",
to_usize_expr(
&format!("self.header.{}", dim.count_field),
&dim.count_field_ty
)
));
code.push_str(&format!(" pub fn iter(&self) -> {}<'a> {{\n", iter_name));
if has_data {
code.push_str(&format!(
" static DATA_KINDS: [LengthKind; {}] = [{}];\n",
g_data.len(),
g_data
.iter()
.map(|d| length_kind_token(length_kind_for_data(&d.ty, schema, opts)))
.collect::<Vec<_>>()
.join(", ")
));
code.push_str(&format!(
" {} {{ remaining: self.payload, entries_left: self.count(), block_length: self.block_length, data_kinds: &DATA_KINDS }}\n",
iter_name
));
} else {
code.push_str(&format!(
" {} {{ remaining: self.payload, entries_left: self.count(), block_length: self.block_length }}\n",
iter_name
));
}
code.push_str(" }\n}\n\n");
if has_data {
code.push_str(&format!(
"pub struct {}<'a> {{\n remaining: &'a [u8],\n entries_left: usize,\n block_length: usize,\n data_kinds: &'static [LengthKind],\n}}\n\n",
iter_name
));
} else {
code.push_str(&format!(
"pub struct {}<'a> {{\n remaining: &'a [u8],\n entries_left: usize,\n block_length: usize,\n}}\n\n",
iter_name
));
}
code.push_str(&format!(
"impl<'a> {}<'a> {{\n pub fn remainder(&self) -> &'a [u8] {{ self.remaining }}\n}}\n\n",
iter_name
));
code.push_str(&format!(
"#[derive(Debug, Clone, Copy)]\npub struct {}<'a> {{\n parsed: Option<&'a {}>,\n raw: &'a [u8],\n}}\n\n",
entry_body, entry_struct
));
code.push_str(&format!(
"impl<'a> core::ops::Deref for {}<'a> {{\n type Target = {};\n fn deref(&self) -> &Self::Target {{\n self.parsed.expect(\"group entry shorter than current layout; use accessor methods\")\n }}\n}}\n\n",
entry_body, entry_struct
));
code.push_str(&format!(
"impl<'a> {}<'a> {{\n fn parsed(&self) -> Option<&'a {}> {{\n self.parsed\n }}\n fn bytes(&self) -> &[u8] {{\n self.raw\n }}\n}}\n\n",
entry_body, entry_struct
));
code.push_str(&format!(
"#[derive(Debug, Clone)]\npub struct {}<'a> {{\n pub body: {}<'a>,\n pub acting_block_length: usize,\n",
entry_view, entry_body
));
for nested in &g_nested {
code.push_str(&format!(
" pub {}: {}Group<'a>,\n",
nested.name.to_snake_case(),
nested.name
));
}
for d in &g_data {
code.push_str(&format!(
" pub {}: VarData<'a>,\n",
d.name.to_snake_case()
));
}
code.push_str("}\n\n");
code.push_str("#[repr(C)]\n");
code.push_str(
"#[derive(Debug, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned, Clone, Copy)]\n",
);
if let Some(desc) = &g.description {
let doc = format!("Group: {}", desc);
push_doc_comment(code, &doc);
}
code.push_str(&format!("pub struct {} {{\n", entry_struct));
write_fields_with_offsets(code, &g_fields, schema, opts);
code.push_str("}\n\n");
code.push_str(&format!("impl {} {{\n", entry_struct));
code.push_str(PARSE_PREFIX_METHOD);
code.push_str("}\n\n");
code.push_str(&format!(
"fn {parse_fn}<'a>(entry: &'a [u8], block_length: usize) -> Option<{entry_body}<'a>> {{\n if entry.len() < block_length {{ return None; }}\n let needed = core::mem::size_of::<{entry_struct}>();\n let (parsed, raw) = if block_length >= needed {{\n let raw = &entry[..needed];\n let (body, _) = Ref::<_, {entry_struct}>::from_prefix(raw).ok()?;\n (Some(Ref::into_ref(body)), raw)\n }} else {{\n (None, &entry[..block_length])\n }};\n Some({entry_body} {{ parsed, raw }})\n}}\n\n",
parse_fn = parse_entry_body_fn,
entry_body = entry_body,
entry_struct = entry_struct
));
push_constant_field_impl(code, &entry_struct, &g_fields, schema, opts);
if !g_fields.is_empty() {
code.push_str(&format!("impl<'a> {}<'a> {{\n", entry_view));
for field in &g_fields {
let fname = field.name.to_snake_case();
if field_is_constant(field, schema) {
code.push_str(&format!(
" #[inline]\n pub fn has_{fname}(&self) -> bool {{\n true\n }}\n",
fname = fname
));
if let Some((ty, _)) = constant_field_value_expr(field, schema, opts) {
code.push_str(&format!(
" #[inline]\n pub fn {fname}(&self) -> Option<{ty}> {{\n if !self.has_{fname}() {{ return None; }}\n Some({entry_struct}::{const_name})\n }}\n",
fname = fname,
ty = ty,
entry_struct = entry_struct,
const_name = fname.to_uppercase()
));
}
continue;
}
let Some(layout) = entry_layout
.iter()
.find(|layout| std::ptr::eq(layout.field, *field))
else {
continue;
};
code.push_str(&format!(
" #[inline]\n pub fn has_{fname}(&self) -> bool {{\n {offset} + {size} <= self.acting_block_length\n }}\n",
fname = fname,
offset = layout.offset,
size = layout.size
));
if let Some(resolved) =
resolve_type(&field.ty, schema, opts, field.byte_order.as_deref())
{
code.push_str(&format!(
" #[inline]\n pub fn {fname}(&self) -> Option<&{ty}> {{\n if !self.has_{fname}() {{ return None; }}\n if let Some(entry) = self.body.parsed() {{ return Some(&entry.{field_name}); }}\n let bytes = &self.body.bytes()[{offset}..{offset_plus}];\n let (r, _) = Ref::<_, {ty}>::from_prefix(bytes).ok()?;\n Some(Ref::into_ref(r))\n }}\n",
fname = fname,
field_name = fname,
ty = resolved,
offset = layout.offset,
offset_plus = layout.offset + layout.size
));
}
}
code.push_str("}\n\n");
}
if !g_fields.is_empty() {
code.push_str(&format!("impl {} {{\n", entry_struct));
optional_methods_for_fields(code, &g_fields, schema, opts, "self");
for f in &g_fields {
if let Some(off) = f.offset {
code.push_str(&format!(
" pub const {}_OFFSET: u32 = {};\n",
f.name.to_uppercase(),
off
));
}
let sv = f.since_version.unwrap_or(0);
let sem = f.semantic_type.clone().unwrap_or_default();
code.push_str(&format!(
" pub const {}_SINCE_VERSION: u32 = {};\n",
f.name.to_uppercase(),
sv
));
code.push_str(&format!(
" pub const {}_SEMANTIC_TYPE: &'static str = \"{}\";\n",
f.name.to_uppercase(),
sem
));
}
code.push_str("}\n\n");
}
code.push_str(&format!(
"pub struct {}<'a> {{\n buf: &'a mut Vec<u8>,\n header_start: usize,\n count: usize,\n}}\n\n",
group_builder
));
code.push_str(&format!(
"pub struct {}<'a> {{\n buf: &'a mut Vec<u8>,\n start: usize,\n}}\n\n",
entry_builder
));
code.push_str(&format!("impl<'a> {}<'a> {{\n", group_builder));
code.push_str(&format!(
" pub const BLOCK_LENGTH: u16 = {};\n",
g_block_length
));
code.push_str(&format!(
" pub const HEADER_SIZE: usize = {};\n",
dim_size
));
let block_expr = builder_value_expr("Self::BLOCK_LENGTH", &dim.block_field_ty);
let count_expr = builder_value_expr("0", &dim.count_field_ty);
code.push_str(&format!(
" pub fn new(buf: &'a mut Vec<u8>) -> Self {{\n let header_start = buf.len();\n buf.resize(header_start + Self::HEADER_SIZE, 0);\n let block_len = {block_expr};\n write_bytes_at(buf, header_start + {block_off}, &block_len);\n let count = {count_expr};\n write_bytes_at(buf, header_start + {count_off}, &count);\n Self {{ buf, header_start, count: 0 }}\n }}\n",
block_expr = block_expr,
block_off = dim_block_offset,
count_expr = count_expr,
count_off = dim_count_offset
));
code.push_str(&format!(
" pub fn entry<F>(&mut self, f: F) -> &mut Self\n where\n F: FnOnce(&mut {entry_builder}),\n {{\n let start = self.buf.len();\n self.buf.resize(start + Self::BLOCK_LENGTH as usize, 0);\n let mut entry = {entry_builder} {{ buf: self.buf, start }};\n f(&mut entry);\n self.count += 1;\n self\n }}\n",
entry_builder = entry_builder
));
let count_finish_expr = count_value_expr("self.count", &dim.count_field_ty);
code.push_str(&format!(
" pub fn finish(self) {{\n let count = {count_expr};\n write_bytes_at(self.buf, self.header_start + {count_off}, &count);\n }}\n",
count_expr = count_finish_expr,
count_off = dim_count_offset
));
code.push_str("}\n\n");
code.push_str(&format!("impl<'a> {}<'a> {{\n", entry_builder));
for layout in &entry_layout {
let field = layout.field;
if field_is_constant(field, schema) {
continue;
}
let offset = layout.offset;
if let Some(resolved_type) =
resolve_type(&field.ty, schema, opts, field.byte_order.as_deref())
{
let param_ty = builder_param_type(&resolved_type);
let encoded_expr = builder_value_expr("value", &resolved_type);
let needs_checks = field.min_value.is_some()
|| field.max_value.is_some()
|| (field.presence.as_deref() != Some("optional") && field.null_value.is_some());
if needs_checks {
if let (Some(host_ty), Some(raw_expr)) = (
host_type_for_field(field, schema),
raw_expr_for_value(field, "value", schema, opts),
) {
code.push_str(&format!(" pub fn {name}(&mut self, value: {param}) -> &mut Self {{\n let raw: {host} = {raw};\n", name = field.name.to_snake_case(), param = param_ty, host = host_ty, raw = raw_expr));
push_validation_asserts(code, field, schema, &host_ty);
code.push_str(&format!(
" let encoded = {expr};\n write_bytes_at(self.buf, self.start + {offset}usize, &encoded);\n self\n }}\n",
expr = encoded_expr,
offset = offset
));
} else {
code.push_str(&format!(
" pub fn {name}(&mut self, value: {param}) -> &mut Self {{\n let encoded = {expr};\n write_bytes_at(self.buf, self.start + {offset}usize, &encoded);\n self\n }}\n",
name = field.name.to_snake_case(),
param = param_ty,
expr = encoded_expr,
offset = offset
));
}
} else {
code.push_str(&format!(
" pub fn {name}(&mut self, value: {param}) -> &mut Self {{\n let encoded = {expr};\n write_bytes_at(self.buf, self.start + {offset}usize, &encoded);\n self\n }}\n",
name = field.name.to_snake_case(),
param = param_ty,
expr = encoded_expr,
offset = offset
));
}
}
}
for nested in &g_nested {
let nested_builder = format!("{}GroupBuilder", nested.name);
let name = nested.name.to_snake_case();
code.push_str(&format!(
" pub fn {name}<F>(&mut self, f: F) -> &mut Self\n where\n F: FnOnce(&mut {builder}),\n {{\n let mut builder = {builder}::new(self.buf);\n f(&mut builder);\n builder.finish();\n self\n }}\n",
name = name,
builder = nested_builder
));
}
for d in &g_data {
let name = d.name.to_snake_case();
let kind = length_kind_token(length_kind_for_data(&d.ty, schema, opts));
code.push_str(&format!(
" pub fn {name}(&mut self, bytes: &[u8]) -> Result<&mut Self, EncodeError> {{\n write_var_data(self.buf, bytes, {kind}, ENDIAN)?;\n Ok(self)\n }}\n",
name = name,
kind = kind
));
}
code.push_str("}\n\n");
code.push_str(&format!(
"pub struct {}<'a> {{\n buf: &'a mut [u8],\n cursor: usize,\n header_start: usize,\n count: usize,\n}}\n\n",
group_encoder
));
code.push_str(&format!(
"pub struct {}<'a> {{\n buf: &'a mut [u8],\n cursor: usize,\n start: usize,\n}}\n\n",
entry_encoder
));
code.push_str(&format!("impl<'a> {}<'a> {{\n", group_encoder));
code.push_str(&format!(
" pub const BLOCK_LENGTH: u16 = {};\n",
g_block_length
));
code.push_str(&format!(
" pub const HEADER_SIZE: usize = {};\n",
dim_size
));
code.push_str(&format!(
" pub const ENTRY_FIXED_LEN: usize = {};\n",
entry_fixed_required
));
code.push_str(&format!(
" pub fn new(buf: &'a mut [u8], cursor: usize) -> Result<Self, EncodeIntoError> {{\n let header_end = cursor.checked_add(Self::HEADER_SIZE).ok_or(EncodeIntoError::InvalidState(\"group header offset overflow\"))?;\n if header_end > buf.len() {{\n return Err(EncodeIntoError::BufferTooSmall {{ required: header_end, available: buf.len() }});\n }}\n if header_end > cursor {{\n buf[cursor..header_end].fill(0);\n }}\n let block_len = {block_expr};\n write_bytes_into(buf, cursor + {block_off}, &block_len)?;\n let count = {count_expr};\n write_bytes_into(buf, cursor + {count_off}, &count)?;\n Ok(Self {{ buf, cursor: header_end, header_start: cursor, count: 0 }})\n }}\n",
block_expr = block_expr,
block_off = dim_block_offset,
count_expr = count_expr,
count_off = dim_count_offset
));
code.push_str(&format!(
" pub fn entry<F>(&mut self, f: F) -> Result<&mut Self, EncodeIntoError>\n where\n F: FnOnce(&mut {entry_encoder}<'_>) -> Result<(), EncodeIntoError>,\n {{\n let mut entry = {entry_encoder}::new(self.buf, self.cursor)?;\n f(&mut entry)?;\n self.cursor = entry.finish();\n self.count = self.count.checked_add(1).ok_or(EncodeIntoError::InvalidState(\"group entry count overflow\"))?;\n Ok(self)\n }}\n",
entry_encoder = entry_encoder
));
code.push_str(&format!(
" pub fn finish(self) -> Result<usize, EncodeIntoError> {{\n let count = {count_expr};\n write_bytes_into(self.buf, self.header_start + {count_off}, &count)?;\n Ok(self.cursor)\n }}\n",
count_expr = count_finish_expr,
count_off = dim_count_offset
));
code.push_str("}\n\n");
code.push_str(&format!("impl<'a> {}<'a> {{\n", entry_encoder));
code.push_str(&format!(
" pub fn new(buf: &'a mut [u8], cursor: usize) -> Result<Self, EncodeIntoError> {{\n let start = cursor;\n let end = start.checked_add({entry_fixed_len}).ok_or(EncodeIntoError::InvalidState(\"group entry offset overflow\"))?;\n if end > buf.len() {{\n return Err(EncodeIntoError::BufferTooSmall {{ required: end, available: buf.len() }});\n }}\n if end > start {{\n buf[start..end].fill(0);\n }}\n Ok(Self {{ buf, cursor: end, start }})\n }}\n",
entry_fixed_len = entry_fixed_required
));
for layout in &entry_layout {
let field = layout.field;
if field_is_constant(field, schema) {
continue;
}
let offset = layout.offset;
if let Some(resolved_type) =
resolve_type(&field.ty, schema, opts, field.byte_order.as_deref())
{
let param_ty = builder_param_type(&resolved_type);
let encoded_expr = builder_value_expr("value", &resolved_type);
let needs_checks = field.min_value.is_some()
|| field.max_value.is_some()
|| (field.presence.as_deref() != Some("optional") && field.null_value.is_some());
if needs_checks {
if let (Some(host_ty), Some(raw_expr)) = (
host_type_for_field(field, schema),
raw_expr_for_value(field, "value", schema, opts),
) {
code.push_str(&format!(" pub fn {name}(&mut self, value: {param}) -> &mut Self {{\n let raw: {host} = {raw};\n", name = field.name.to_snake_case(), param = param_ty, host = host_ty, raw = raw_expr));
push_validation_asserts(code, field, schema, &host_ty);
code.push_str(&format!(
" let encoded = {expr};\n write_bytes_into_in_bounds(self.buf, self.start + {offset}usize, &encoded);\n self\n }}\n",
expr = encoded_expr,
offset = offset
));
} else {
code.push_str(&format!(
" pub fn {name}(&mut self, value: {param}) -> &mut Self {{\n let encoded = {expr};\n write_bytes_into_in_bounds(self.buf, self.start + {offset}usize, &encoded);\n self\n }}\n",
name = field.name.to_snake_case(),
param = param_ty,
expr = encoded_expr,
offset = offset
));
}
} else {
code.push_str(&format!(
" pub fn {name}(&mut self, value: {param}) -> &mut Self {{\n let encoded = {expr};\n write_bytes_into_in_bounds(self.buf, self.start + {offset}usize, &encoded);\n self\n }}\n",
name = field.name.to_snake_case(),
param = param_ty,
expr = encoded_expr,
offset = offset
));
}
}
}
for nested in &g_nested {
let nested_encoder = format!("{}GroupEncoder", nested.name);
let name = nested.name.to_snake_case();
code.push_str(&format!(
" pub fn {name}<F>(&mut self, f: F) -> Result<&mut Self, EncodeIntoError>\n where\n F: FnOnce(&mut {encoder}<'_>) -> Result<(), EncodeIntoError>,\n {{\n let mut encoder = {encoder}::new(self.buf, self.cursor)?;\n f(&mut encoder)?;\n self.cursor = encoder.finish()?;\n Ok(self)\n }}\n",
name = name,
encoder = nested_encoder
));
}
for d in &g_data {
let name = d.name.to_snake_case();
let kind = length_kind_token(length_kind_for_data(&d.ty, schema, opts));
code.push_str(&format!(
" pub fn {name}(&mut self, bytes: &[u8]) -> Result<&mut Self, EncodeIntoError> {{\n let written = write_var_data_into(self.buf, self.cursor, bytes, {kind}, ENDIAN)?;\n self.cursor = self.cursor.checked_add(written).ok_or(EncodeIntoError::InvalidState(\"group var data length overflow\"))?;\n Ok(self)\n }}\n",
name = name,
kind = kind
));
}
code.push_str(" pub fn finish(self) -> usize {\n self.cursor\n }\n");
code.push_str("}\n\n");
if has_any_var {
code.push_str(&format!(
"impl<'a> Iterator for {}<'a> {{\n type Item = {}<'a>;\n fn next(&mut self) -> Option<Self::Item> {{\n if self.entries_left == 0 {{\n return None;\n }}\n let blen = self.block_length;\n if blen > self.remaining.len() {{\n return None;\n }}\n let (entry, mut tail) = self.remaining.split_at(blen);\n let body = {}(entry, blen)?;\n",
iter_name, entry_view, parse_entry_body_fn
));
let mut data_idx = 0;
for member in &g.members {
match member {
GroupMember::Group(nested) => {
code.push_str(&format!(
" let nested_{name} = parse_{snake}(tail)?;\n",
name = nested.name.to_snake_case(),
snake = nested.name.to_snake_case()
));
code.push_str(&format!(
" let after_{name} = skip_{snake}(nested_{name}.payload, nested_{name}.count(), nested_{name}.block_length)?;\n tail = after_{name};\n",
name = nested.name.to_snake_case(),
snake = nested.name.to_snake_case()
));
}
GroupMember::Data(_) => {
code.push_str(&format!(
" let (data_{idx}, after_{idx}) = parse_var_data(tail, *self.data_kinds.get({idx})?)?;\n tail = after_{idx};\n",
idx = data_idx
));
data_idx += 1;
}
GroupMember::Field(_) => {}
}
}
code.push_str(
" self.remaining = tail;\n self.entries_left -= 1;\n Some(",
);
code.push_str(&format!(
"{} {{ body, acting_block_length: blen",
entry_view
));
data_idx = 0;
for member in &g.members {
match member {
GroupMember::Group(nested) => {
code.push_str(&format!(
", {}: nested_{}",
nested.name.to_snake_case(),
nested.name.to_snake_case()
));
}
GroupMember::Data(d) => {
code.push_str(&format!(", {}: data_{}", d.name.to_snake_case(), data_idx));
data_idx += 1;
}
GroupMember::Field(_) => {}
}
}
code.push_str(" })\n }\n}\n\n");
} else {
code.push_str(&format!("impl<'a> Iterator for {}<'a> {{\n", iter_name));
code.push_str(&format!(" type Item = {}<'a>;\n", entry_view));
code.push_str(
" fn next(&mut self) -> Option<Self::Item> {\n if self.entries_left == 0 {\n return None;\n }\n let blen = self.block_length;\n if blen > self.remaining.len() {\n return None;\n }\n",
);
code.push_str(&format!(
" let (entry, tail) = self.remaining.split_at(blen);\n let body = {}(entry, blen)?;\n",
parse_entry_body_fn
));
code.push_str(
" self.remaining = tail;\n self.entries_left -= 1;\n Some(",
);
code.push_str(&format!(
"{} {{ body, acting_block_length: blen }}",
entry_view
));
code.push_str(")\n }\n}\n\n");
}
let has_var_members = g
.members
.iter()
.any(|m| !matches!(m, GroupMember::Field(_)));
code.push_str(&format!(
"fn skip_{}<'a>(mut cursor: &'a [u8], count: usize, block_length: usize) -> Option<&'a [u8]> {{\n for _ in 0..count {{\n",
group_snake
));
if !has_var_members {
code.push_str(" if block_length == 0 { return None; }\n");
}
code.push_str(
" let mut tail = if block_length <= cursor.len() { &cursor[block_length..] } else { return None; };\n",
);
for member in g
.members
.iter()
.filter(|m| !matches!(m, GroupMember::Field(_)))
{
match member {
GroupMember::Group(nested) => {
code.push_str(&format!(
" let nested = parse_{}(tail)?;\n tail = skip_{}(nested.payload, nested.count(), nested.block_length)?;\n",
nested.name.to_snake_case(),
nested.name.to_snake_case()
));
}
GroupMember::Data(d) => {
let kind = length_kind_token(length_kind_for_data(&d.ty, schema, opts));
code.push_str(&format!(
" let (_, after_data) = parse_var_data(tail, {})?;\n tail = after_data;\n",
kind
));
}
GroupMember::Field(_) => {}
}
}
code.push_str(" cursor = tail;\n }\n Some(cursor)\n}\n\n");
for nested in &g_nested {
emit_group(nested, schema, opts, code);
}
}
struct DimensionFields {
block_field: String,
block_field_ty: String,
count_field: String,
count_field_ty: String,
}
fn dimension_field_rust_type(ty: &str, schema: &Schema, opts: &GeneratorOptions) -> Option<String> {
if let Some(td) = schema.types.get(ty) {
match td {
TypeDef::Primitive {
primitive,
presence,
..
} => {
if presence.as_deref() == Some("constant") {
return None;
}
primitive_to_rust(primitive, opts)
}
TypeDef::Enum { encoding, .. } | TypeDef::Set { encoding, .. } => {
primitive_to_rust(encoding, opts)
}
TypeDef::Composite { .. } => None,
}
} else {
primitive_to_rust(ty, opts)
}
}
fn dimension_fields(schema: &Schema, dim_type: &str, opts: &GeneratorOptions) -> DimensionFields {
if let Some(TypeDef::Composite { fields, .. }) = schema.types.get(dim_type) {
let mut dim_fields = Vec::new();
for f in fields {
match f {
CompositeField::Type {
name,
primitive,
presence,
..
} => {
if presence.as_deref() == Some("constant") {
continue;
}
if let Some(rust) = primitive_to_rust(primitive, opts) {
dim_fields.push((name.to_snake_case(), rust));
}
}
CompositeField::Ref { name, ty } => {
if let Some(rust) = dimension_field_rust_type(ty, schema, opts) {
dim_fields.push((name.to_snake_case(), rust));
}
}
}
}
let norm = |name: &str| name.replace('_', "").to_lowercase();
let block = dim_fields
.iter()
.find(|(n, _)| norm(n) == "blocklength")
.cloned()
.or_else(|| {
dim_fields
.iter()
.find(|(n, _)| norm(n).contains("blocklength"))
.cloned()
})
.or_else(|| dim_fields.first().cloned())
.unwrap_or_else(|| ("block_length".into(), "U16".into()));
let mut count = dim_fields
.iter()
.find(|(n, _)| {
*n != block.0 && {
let nn = norm(n);
nn == "numingroup" || nn == "count"
}
})
.cloned()
.or_else(|| {
dim_fields
.iter()
.find(|(n, _)| {
*n != block.0 && {
let nn = norm(n);
nn.contains("numingroup") || nn.contains("count")
}
})
.cloned()
})
.or_else(|| dim_fields.iter().find(|(n, _)| *n != block.0).cloned())
.unwrap_or_else(|| ("num_in_group".into(), "U16".into()));
// allow block and count to be the same when only one field exists: duplicate
if block.0 == count.0 && dim_fields.len() <= 1 {
count = ("num_in_group".into(), block.1.clone());
}
return DimensionFields {
block_field: block.0,
block_field_ty: block.1,
count_field: count.0,
count_field_ty: count.1,
};
}
DimensionFields {
block_field: "block_length".into(),
block_field_ty: "U16".into(),
count_field: "num_in_group".into(),
count_field_ty: "U16".into(),
}
}
fn to_usize_expr(field_name: &str, ty: &str) -> String {
match ty {
"u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "u64" | "i64" => {
format!("{} as usize", field_name)
}
_ => format!("{}.get() as usize", field_name),
}
}
fn field_primitive(field: &Field, schema: &Schema) -> Option<String> {
if let Some(td) = schema.types.get(&field.ty) {
match td {
TypeDef::Primitive { primitive, .. } => Some(primitive.clone()),
TypeDef::Enum { encoding, .. } | TypeDef::Set { encoding, .. } => {
Some(encoding.clone())
}
TypeDef::Composite { .. } => None,
}
} else {
Some(field.ty.clone())
}
}
fn encoding_primitive(
encoding: &str,
types: &std::collections::HashMap<String, TypeDef>,
) -> Option<String> {
if let Some(td) = types.get(encoding) {
match td {
TypeDef::Primitive { primitive, .. } => Some(primitive.clone()),
TypeDef::Enum { encoding, .. } | TypeDef::Set { encoding, .. } => {
encoding_primitive(encoding, types)
}
TypeDef::Composite { .. } => None,
}
} else {
Some(encoding.to_string())
}
}
fn optional_null_literal(prim: &str) -> Option<&'static str> {
match prim {
"boolean" | "uint8" | "char" => Some("u8::MAX"),
"int8" => Some("i8::MIN"),
"uint16" => Some("u16::MAX"),
"int16" => Some("i16::MIN"),
"uint32" => Some("u32::MAX"),
"int32" => Some("i32::MIN"),
"uint64" => Some("u64::MAX"),
"int64" => Some("i64::MIN"),
"float" => Some("f32::NAN"),
"double" => Some("f64::NAN"),
_ => None,
}
}
fn optional_host_type(prim: &str) -> Option<&'static str> {
match prim {
"boolean" | "uint8" | "char" => Some("u8"),
"int8" => Some("i8"),
"uint16" => Some("u16"),
"int16" => Some("i16"),
"uint32" => Some("u32"),
"int32" => Some("i32"),
"uint64" => Some("u64"),
"int64" => Some("i64"),
"float" => Some("f32"),
"double" => Some("f64"),
_ => None,
}
}
fn scalar_expr(expr: &str, rust_type: &str) -> Option<String> {
let base = rust_type.rsplit("::").next().unwrap_or(rust_type);
match base {
"u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "u64" | "i64" | "f32" | "f64" => {
Some(expr.to_string())
}
"U16" | "I16" | "U32" | "I32" | "U64" | "I64" | "F32" | "F64" => {
Some(format!("{}.get()", expr))
}
_ => None,
}
}
fn type_is_optional(ty: &str, schema: &Schema) -> bool {
matches!(
schema.types.get(ty),
Some(TypeDef::Primitive {
presence: Some(p),
..
}) if p == "optional"
) || matches!(
schema.types.get(ty),
Some(TypeDef::Primitive {
null_value: Some(_),
..
})
)
}
fn type_null_value<'a>(ty: &str, schema: &'a Schema) -> Option<&'a str> {
match schema.types.get(ty) {
Some(TypeDef::Primitive { null_value, .. }) => null_value.as_deref(),
_ => None,
}
}
fn type_length(ty: &str, schema: &Schema) -> Option<usize> {
match schema.types.get(ty) {
Some(TypeDef::Primitive { length, .. }) => *length,
_ => None,
}
}
fn type_primitive<'a>(ty: &str, schema: &'a Schema) -> Option<&'a str> {
match schema.types.get(ty) {
Some(TypeDef::Primitive { primitive, .. }) => Some(primitive.as_str()),
_ => None,
}
}
fn field_is_constant(field: &Field, schema: &Schema) -> bool {
if field.presence.as_deref() == Some("constant") {
return true;
}
if field.value_ref.is_some() {
return true;
}
if field.presence.is_some() {
return false;
}
matches!(
schema.types.get(&field.ty),
Some(TypeDef::Primitive {
presence: Some(p),
..
}) if p == "constant"
)
}
fn constant_field_value_expr(
field: &Field,
schema: &Schema,
opts: &GeneratorOptions,
) -> Option<(String, String)> {
if let Some(value_ref) = &field.value_ref
&& let Some((type_name, variant)) = value_ref.split_once('.')
{
return Some((type_name.to_string(), format!("{type_name}::{variant}")));
}
if let Some(val) = field.constant.as_deref() {
let const_ty = resolve_type(&field.ty, schema, opts, field.byte_order.as_deref())
.unwrap_or_else(|| field.ty.clone());
if let Some(td) = schema.types.get(&field.ty) {
match td {
TypeDef::Primitive {
primitive, length, ..
} => {
let rust_type = primitive_to_rust(primitive, opts)?;
if let Some(len) = length {
let (_, expr) = const_array_expr(primitive, &rust_type, val, *len)?;
return Some((const_ty, expr));
}
let raw_expr = const_scalar_expr(primitive, &rust_type, val)?;
return Some((const_ty, raw_expr));
}
TypeDef::Enum { encoding, .. } | TypeDef::Set { encoding, .. } => {
let rust_type = primitive_to_rust(encoding, opts)?;
let raw_expr = const_scalar_expr(encoding, &rust_type, val)?;
return Some((field.ty.clone(), format!("{}({})", field.ty, raw_expr)));
}
TypeDef::Composite { .. } => {}
}
} else if let Some(rust_type) = primitive_to_rust(&field.ty, opts) {
let raw_expr = const_scalar_expr(&field.ty, &rust_type, val)?;
return Some((const_ty, raw_expr));
}
}
if let Some(TypeDef::Primitive {
constant: Some(val),
primitive,
length,
..
}) = schema.types.get(&field.ty)
{
let rust_type = primitive_to_rust(primitive, opts)?;
if let Some(len) = length {
let (_, expr) = const_array_expr(primitive, &rust_type, val, *len)?;
return Some((field.ty.clone(), expr));
}
let raw_expr = const_scalar_expr(primitive, &rust_type, val)?;
let alias_target = opts
.constant_type_aliases
.get(&field.ty)
.cloned()
.or_else(|| constant_type_alias_from_schema(&field.ty, schema))
.unwrap_or_else(|| rust_type.clone());
let expr = if matches!(
schema.types.get(&alias_target),
Some(TypeDef::Enum { .. } | TypeDef::Set { .. })
) {
format!("{alias_target}({raw_expr})")
} else {
raw_expr
};
return Some((field.ty.clone(), expr));
}
None
}
fn push_constant_field_impl(
code: &mut String,
struct_name: &str,
fields: &[&Field],
schema: &Schema,
opts: &GeneratorOptions,
) {
let const_fields: Vec<_> = fields
.iter()
.copied()
.filter(|f| field_is_constant(f, schema))
.collect();
if const_fields.is_empty() {
return;
}
code.push_str(&format!("impl {} {{\n", struct_name));
for f in const_fields {
let field_name = f.name.to_snake_case();
let const_name = field_name.to_uppercase();
if let Some((ty, expr)) = constant_field_value_expr(f, schema, opts) {
code.push_str(&format!(
" pub const {const_name}: {ty} = {expr};\n",
const_name = const_name,
ty = ty,
expr = expr
));
code.push_str(&format!(
" #[inline]\n pub fn {field_name}(&self) -> {ty} {{\n Self::{const_name}\n }}\n",
field_name = field_name,
ty = ty,
const_name = const_name
));
}
}
code.push_str("}\n\n");
}
fn field_is_optional(field: &Field, schema: &Schema) -> bool {
match field.presence.as_deref() {
Some("optional") => true,
Some("constant") | Some("required") => false,
_ => field.null_value.is_some() || type_is_optional(&field.ty, schema),
}
}
fn field_null_value<'a>(field: &'a Field, schema: &'a Schema) -> Option<&'a str> {
field
.null_value
.as_deref()
.or_else(|| type_null_value(&field.ty, schema))
}
fn const_host_expr(prim: &str, raw: &str) -> Option<String> {
let trimmed = raw.trim();
let expr = match prim {
"char" => {
let byte = trimmed.as_bytes().first().copied().unwrap_or(0);
format!("{}u8", byte)
}
"boolean" => match trimmed {
"true" => "1u8".to_string(),
"false" => "0u8".to_string(),
_ => trimmed.to_string(),
},
"float" => {
if trimmed.eq_ignore_ascii_case("nan") {
"f32::NAN".to_string()
} else {
trimmed.to_string()
}
}
"double" => {
if trimmed.eq_ignore_ascii_case("nan") {
"f64::NAN".to_string()
} else {
trimmed.to_string()
}
}
_ => trimmed.to_string(),
};
Some(expr)
}
fn null_cond_for_primitive(
prim: &str,
host_type: &str,
null_value: Option<&str>,
use_default: bool,
) -> Option<String> {
let is_float = host_type == "f32" || host_type == "f64";
if is_float {
if null_value.is_none() && use_default {
return Some("raw.is_nan()".to_string());
}
if let Some(val) = null_value
&& val.trim().eq_ignore_ascii_case("nan")
{
return Some("raw.is_nan()".to_string());
}
}
let raw = if let Some(val) = null_value {
Some(val)
} else if use_default {
optional_null_literal(prim)
} else {
None
}?;
let expr = const_host_expr(prim, raw)?;
Some(format!("raw == {}", expr))
}
fn validation_literal_expr(field: &Field, schema: &Schema, raw: &str) -> Option<String> {
let prim = field_primitive(field, schema)?;
const_host_expr(&prim, raw)
}
fn push_validation_asserts(code: &mut String, field: &Field, schema: &Schema, host_ty: &str) {
if let Some(ref minv) = field.min_value {
if let Some(min_expr) = validation_literal_expr(field, schema, minv) {
code.push_str(&format!(
" let min: {host} = {min_expr};\n assert!(raw >= min, \"{field} below minValue\");\n",
host = host_ty,
min_expr = min_expr,
field = field.name
));
} else {
code.push_str(&format!(
" let min: {host} = \"{minv}\".parse().expect(\"minValue parse\");\n assert!(raw >= min, \"{field} below minValue\");\n",
host = host_ty,
minv = minv,
field = field.name
));
}
}
if let Some(ref maxv) = field.max_value {
if let Some(max_expr) = validation_literal_expr(field, schema, maxv) {
code.push_str(&format!(
" let max: {host} = {max_expr};\n assert!(raw <= max, \"{field} above maxValue\");\n",
host = host_ty,
max_expr = max_expr,
field = field.name
));
} else {
code.push_str(&format!(
" let max: {host} = \"{maxv}\".parse().expect(\"maxValue parse\");\n assert!(raw <= max, \"{field} above maxValue\");\n",
host = host_ty,
maxv = maxv,
field = field.name
));
}
}
if field.presence.as_deref() != Some("optional")
&& let Some(ref nullv) = field.null_value
{
let cond = field_primitive(field, schema)
.and_then(|prim| null_cond_for_primitive(&prim, host_ty, Some(nullv), false));
if let Some(cond) = cond {
code.push_str(&format!(
" assert!(!({cond}), \"{field} uses nullValue but field is required\");\n",
cond = cond,
field = field.name
));
} else {
code.push_str(&format!(
" let null_val: {host} = \"{nullv}\".parse().expect(\"nullValue parse\");\n assert!(raw != null_val, \"{field} uses nullValue but field is required\");\n",
host = host_ty,
nullv = nullv,
field = field.name
));
}
}
}
struct ViewFieldValueInfo {
value_ty: String,
value_expr: String,
nullable_inner_ty: Option<String>,
}
fn parse_u8_array_len(resolved_type: &str) -> Option<usize> {
let trimmed = resolved_type.trim();
let rest = trimmed.strip_prefix('[')?;
let (inner, len_part) = rest.split_once(';')?;
if inner.trim() != "u8" {
return None;
}
len_part.trim().trim_end_matches(']').parse::<usize>().ok()
}
fn field_is_required(field: &Field, schema: &Schema) -> bool {
match field.presence.as_deref() {
Some("required") => true,
Some("optional") | Some("constant") => false,
_ => !field_is_optional(field, schema),
}
}
fn view_field_value_info(
field: &Field,
schema: &Schema,
opts: &GeneratorOptions,
) -> Option<ViewFieldValueInfo> {
let resolved = resolve_type(&field.ty, schema, opts, field.byte_order.as_deref())?;
if resolved.trim().starts_with('[') {
return Some(ViewFieldValueInfo {
value_ty: resolved,
value_expr: "*value".to_string(),
nullable_inner_ty: None,
});
}
if let Some(TypeDef::Enum { encoding, .. } | TypeDef::Set { encoding, .. }) =
schema.types.get(&field.ty)
{
let prim = encoding_primitive(encoding, &schema.types).unwrap_or_else(|| encoding.clone());
let explicit_null = field_null_value(field, schema);
let is_optional = field_is_optional(field, schema);
let use_default = explicit_null.is_none() && is_optional;
if (is_optional || explicit_null.is_some())
&& let Some(inner_rust) =
primitive_to_rust_endian(&prim, &opts.endian, field.byte_order.as_deref())
&& let Some(raw_expr) = scalar_expr("value.0", &inner_rust)
&& let Some(host_ty) = optional_host_type(&prim)
&& let Some(cond) = null_cond_for_primitive(&prim, host_ty, explicit_null, use_default)
{
return Some(ViewFieldValueInfo {
value_ty: format!("Option<{}>", field.ty),
value_expr: format!(
"{{ let raw = {raw}; if {cond} {{ None }} else {{ Some(*value) }} }}",
raw = raw_expr,
cond = cond
),
nullable_inner_ty: Some(field.ty.clone()),
});
}
return Some(ViewFieldValueInfo {
value_ty: field.ty.clone(),
value_expr: "*value".to_string(),
nullable_inner_ty: None,
});
}
if let Some(prim) = field_primitive(field, schema)
&& let Some(inner_rust) =
primitive_to_rust_endian(&prim, &opts.endian, field.byte_order.as_deref())
&& let Some(raw_expr) = scalar_expr("(*value)", &inner_rust)
&& let Some(host_ty_raw) = optional_host_type(&prim)
{
let host_ty = host_ty_raw.to_string();
let explicit_null = field_null_value(field, schema);
let is_optional = field_is_optional(field, schema);
let use_default = explicit_null.is_none() && is_optional;
if (is_optional || explicit_null.is_some())
&& let Some(cond) = null_cond_for_primitive(&prim, &host_ty, explicit_null, use_default)
{
return Some(ViewFieldValueInfo {
value_ty: format!("Option<{}>", host_ty),
value_expr: format!(
"{{ let raw = {raw}; if {cond} {{ None }} else {{ Some(raw) }} }}",
raw = raw_expr,
cond = cond
),
nullable_inner_ty: Some(host_ty),
});
}
return Some(ViewFieldValueInfo {
value_ty: host_ty,
value_expr: raw_expr,
nullable_inner_ty: None,
});
}
Some(ViewFieldValueInfo {
value_ty: resolved,
value_expr: "*value".to_string(),
nullable_inner_ty: None,
})
}
fn composite_optional_view_helpers(
ty: &str,
schema: &Schema,
_opts: &GeneratorOptions,
) -> Vec<(String, String)> {
let mut out = Vec::new();
let Some(TypeDef::Composite { fields, .. }) = schema.types.get(ty) else {
return out;
};
for field in fields {
match field {
CompositeField::Type {
name,
primitive,
length,
presence,
null_value,
..
} => {
if presence.as_deref() == Some("constant") {
continue;
}
let is_optional = match presence.as_deref() {
Some("optional") => true,
Some("required") => false,
_ => null_value.is_some(),
};
if !is_optional {
continue;
}
if let Some(len) = length
&& *len > 1
{
continue;
}
let Some(host_ty) = optional_host_type(primitive) else {
continue;
};
let Some(_) =
null_cond_for_primitive(primitive, host_ty, null_value.as_deref(), true)
else {
continue;
};
out.push((format!("{}_opt", name.to_snake_case()), host_ty.to_string()));
}
CompositeField::Ref { name, ty } => {
if !type_is_optional(ty, schema) {
continue;
}
if let Some(len) = type_length(ty, schema)
&& len > 1
{
continue;
}
let Some(prim) = type_primitive(ty, schema) else {
continue;
};
let Some(host_ty) = optional_host_type(prim) else {
continue;
};
let Some(_) =
null_cond_for_primitive(prim, host_ty, type_null_value(ty, schema), true)
else {
continue;
};
out.push((format!("{}_opt", name.to_snake_case()), host_ty.to_string()));
}
}
}
out
}
fn emit_view_field_helpers(
code: &mut String,
field: &Field,
schema: &Schema,
opts: &GeneratorOptions,
) {
let fname = field.name.to_snake_case();
let Some(value_info) = view_field_value_info(field, schema, opts) else {
return;
};
code.push_str(&format!(
" #[inline]\n pub fn {fname}_value(&self) -> Option<{ty}> {{\n let value = self.{fname}()?;\n Some({expr})\n }}\n",
fname = fname,
ty = value_info.value_ty,
expr = value_info.value_expr,
));
if field_is_required(field, schema) {
if let Some(inner_ty) = value_info.nullable_inner_ty.as_deref() {
code.push_str(&format!(
" #[inline]\n pub fn {fname}_required(&self) -> Result<{inner_ty}, DecodeFieldError> {{\n let value = self.{fname}_value().ok_or(DecodeFieldError::MissingField(\"{fname}\"))?;\n value.ok_or(DecodeFieldError::NullValue(\"{fname}\"))\n }}\n",
fname = fname,
inner_ty = inner_ty
));
} else {
code.push_str(&format!(
" #[inline]\n pub fn {fname}_required(&self) -> Result<{ty}, DecodeFieldError> {{\n self.{fname}_value().ok_or(DecodeFieldError::MissingField(\"{fname}\"))\n }}\n",
fname = fname,
ty = value_info.value_ty
));
}
}
if let Some(TypeDef::Enum { values, .. }) = schema.types.get(&field.ty)
&& !values.is_empty()
{
let enum_name = format!("{}Enum", field.ty);
if value_info.nullable_inner_ty.as_deref() == Some(field.ty.as_str()) {
code.push_str(&format!(
" #[inline]\n pub fn {fname}_enum(&self) -> Option<{enum_name}> {{\n let value = self.{fname}_value()?;\n value.and_then(|raw| raw.as_enum())\n }}\n",
fname = fname,
enum_name = enum_name
));
} else {
code.push_str(&format!(
" #[inline]\n pub fn {fname}_enum(&self) -> Option<{enum_name}> {{\n self.{fname}_value().and_then(|raw| raw.as_enum())\n }}\n",
fname = fname,
enum_name = enum_name
));
}
}
for (sub_method, ret_ty) in composite_optional_view_helpers(&field.ty, schema, opts) {
code.push_str(&format!(
" #[inline]\n pub fn {fname}_{sub_method}(&self) -> Option<{ret_ty}> {{\n self.{fname}().and_then(|value| value.{sub_method}())\n }}\n",
fname = fname,
sub_method = sub_method,
ret_ty = ret_ty
));
}
if let Some(resolved) = resolve_type(&field.ty, schema, opts, field.byte_order.as_deref())
&& let Some(len) = parse_u8_array_len(&resolved)
{
code.push_str(&format!(
" #[inline(always)]\n pub fn {fname}_bytes(&self) -> Option<&[u8; {len}]> {{\n self.{fname}()\n }}\n",
fname = fname,
len = len
));
code.push_str(&format!(
" #[inline]\n pub fn {fname}_str(&self) -> Option<&str> {{\n let bytes = self.{fname}_bytes()?;\n str::from_utf8(bytes).ok()\n }}\n",
fname = fname
));
code.push_str(&format!(
" #[inline]\n pub fn {fname}_str_trimmed(&self) -> Option<&str> {{\n let bytes = self.{fname}_bytes()?;\n let trimmed = trim_ascii_space_and_nul_right(bytes);\n str::from_utf8(trimmed).ok()\n }}\n",
fname = fname
));
}
}
fn optional_methods_for_fields(
code: &mut String,
fields: &[&Field],
schema: &Schema,
opts: &GeneratorOptions,
self_expr: &str,
) {
for field in fields {
if !field_is_optional(field, schema) {
continue;
}
let field_name = field.name.to_snake_case();
let method_name = format!("{}_opt", field_name);
if let Some(len) = type_length(&field.ty, schema)
&& len > 1
{
continue;
}
// Enum/set optional: return Option<EnumName> by inspecting the inner primitive
if let Some(TypeDef::Enum { encoding, .. } | TypeDef::Set { encoding, .. }) =
schema.types.get(&field.ty)
{
let inner_rust =
match primitive_to_rust_endian(encoding, &opts.endian, field.byte_order.as_deref())
{
Some(t) => t,
None => continue,
};
if let Some(val_expr) =
scalar_expr(&format!("{}.{}.0", self_expr, field_name), &inner_rust)
{
let host_type = match optional_host_type(encoding) {
Some(t) => t,
None => continue,
};
let cond = match null_cond_for_primitive(
encoding,
host_type,
field_null_value(field, schema),
true,
) {
Some(c) => c,
None => continue,
};
code.push_str(&format!(
" #[inline]\n pub fn {method}(&self) -> Option<{ty}> {{\n let v = {self_expr}.{field};\n let raw = {val};\n if {cond} {{ None }} else {{ Some(v) }}\n }}\n",
method = method_name,
ty = field.ty,
self_expr = self_expr,
field = field_name,
val = val_expr,
cond = cond,
));
}
continue;
}
let prim = match field_primitive(field, schema) {
Some(p) => p,
None => continue,
};
let host_type = match optional_host_type(&prim) {
Some(t) => t,
None => continue,
};
let inner_rust =
match primitive_to_rust_endian(&prim, &opts.endian, field.byte_order.as_deref()) {
Some(t) => t,
None => continue,
};
let scalar = scalar_expr(&format!("{}.{}", self_expr, field_name), &inner_rust);
if let Some(val_expr) = scalar {
let cond = match null_cond_for_primitive(
&prim,
host_type,
field_null_value(field, schema),
true,
) {
Some(c) => c,
None => continue,
};
code.push_str(&format!(
" #[inline]\n pub fn {method}(&self) -> Option<{ty}> {{\n let raw = {val};\n if {cond} {{ None }} else {{ Some(raw) }}\n }}\n",
method = method_name,
ty = host_type,
val = val_expr,
cond = cond,
));
}
}
}
fn optional_methods_for_composite_fields(
code: &mut String,
fields: &[CompositeField],
schema: &Schema,
opts: &GeneratorOptions,
self_expr: &str,
) {
for field in fields {
match field {
CompositeField::Type {
name,
primitive,
length,
presence,
null_value,
..
} => {
if presence.as_deref() == Some("constant") {
continue;
}
let is_optional = match presence.as_deref() {
Some("optional") => true,
Some("required") => false,
_ => null_value.is_some(),
};
if !is_optional {
continue;
}
if let Some(len) = length
&& *len > 1
{
continue;
}
let rust_type = match primitive_to_rust(primitive, opts) {
Some(t) => t,
None => continue,
};
let val_expr = match scalar_expr(
&format!("{}.{}", self_expr, name.to_snake_case()),
&rust_type,
) {
Some(v) => v,
None => continue,
};
let host_type = match optional_host_type(primitive) {
Some(t) => t,
None => continue,
};
let cond = match null_cond_for_primitive(
primitive,
host_type,
null_value.as_deref(),
true,
) {
Some(c) => c,
None => continue,
};
let method_name = format!("{}_opt", name.to_snake_case());
code.push_str(&format!(
" #[inline]\n pub fn {method}(&self) -> Option<{ty}> {{\n let raw = {val};\n if {cond} {{ None }} else {{ Some(raw) }}\n }}\n",
method = method_name,
ty = host_type,
val = val_expr,
cond = cond,
));
}
CompositeField::Ref { name, ty } => {
if !type_is_optional(ty, schema) {
continue;
}
if let Some(len) = type_length(ty, schema)
&& len > 1
{
continue;
}
let prim = match type_primitive(ty, schema) {
Some(p) => p,
None => continue,
};
let rust_type = match primitive_to_rust(prim, opts) {
Some(t) => t,
None => continue,
};
let val_expr = match scalar_expr(
&format!("{}.{}", self_expr, name.to_snake_case()),
&rust_type,
) {
Some(v) => v,
None => continue,
};
let host_type = match optional_host_type(prim) {
Some(t) => t,
None => continue,
};
let cond = match null_cond_for_primitive(
prim,
host_type,
type_null_value(ty, schema),
true,
) {
Some(c) => c,
None => continue,
};
let method_name = format!("{}_opt", name.to_snake_case());
code.push_str(&format!(
" #[inline]\n pub fn {method}(&self) -> Option<{ty}> {{\n let raw = {val};\n if {cond} {{ None }} else {{ Some(raw) }}\n }}\n",
method = method_name,
ty = host_type,
val = val_expr,
cond = cond,
));
}
}
}
}
fn composite_null_constants(
code: &mut String,
fields: &[CompositeField],
schema: &Schema,
opts: &GeneratorOptions,
) {
for field in fields {
match field {
CompositeField::Type {
name,
primitive,
length,
presence,
null_value,
..
} => {
if presence.as_deref() == Some("constant") {
continue;
}
let null_raw = null_value.as_deref().or_else(|| {
if presence.as_deref() == Some("optional") && length.is_none() {
optional_null_literal(primitive)
} else {
None
}
});
let null_raw = match null_raw {
Some(v) => v,
None => continue,
};
let rust_type = match primitive_to_rust(primitive, opts) {
Some(t) => t,
None => continue,
};
let cname = format!("{}_NULL", name.to_uppercase());
if let Some(len) = length {
if null_value.is_none() {
continue;
}
if let Some((ty, expr)) =
const_array_expr(primitive, &rust_type, null_raw, *len)
{
code.push_str(&format!(" pub const {}: {} = {};\n", cname, ty, expr));
}
} else if let Some(expr) = const_scalar_expr(primitive, &rust_type, null_raw) {
code.push_str(&format!(
" pub const {}: {} = {};\n",
cname, rust_type, expr
));
}
}
CompositeField::Ref { name, ty } => {
let (prim, length, presence, null_value) = if let Some(TypeDef::Primitive {
primitive,
length,
presence,
null_value,
..
}) = schema.types.get(ty)
{
(
primitive.as_str(),
*length,
presence.as_deref(),
null_value.as_deref(),
)
} else {
continue;
};
let is_optional = presence == Some("optional") || null_value.is_some();
if !is_optional {
continue;
}
let null_raw = null_value.or_else(|| {
if presence == Some("optional") && length.is_none() {
optional_null_literal(prim)
} else {
None
}
});
let null_raw = match null_raw {
Some(v) => v,
None => continue,
};
let rust_type = match primitive_to_rust(prim, opts) {
Some(t) => t,
None => continue,
};
let cname = format!("{}_NULL", name.to_uppercase());
if let Some(len) = length {
if null_value.is_none() {
continue;
}
if let Some((_, expr)) = const_array_expr(prim, &rust_type, null_raw, len) {
code.push_str(&format!(" pub const {}: {} = {};\n", cname, ty, expr));
}
} else if let Some(expr) = const_scalar_expr(prim, &rust_type, null_raw) {
code.push_str(&format!(" pub const {}: {} = {};\n", cname, ty, expr));
}
}
}
}
}
fn block_length_from_fields(
fields: &[&Field],
schema: &Schema,
opts: &GeneratorOptions,
) -> Option<usize> {
let mut cur = 0usize;
let mut max_end = 0usize;
for field in fields {
if field_is_constant(field, schema) {
continue;
}
let sz = field_size_bytes(field, schema, opts)?;
let start = field.offset.map(|o| o as usize).unwrap_or(cur);
let end = start + sz;
max_end = max_end.max(end);
cur = end;
}
Some(max_end)
}
fn message_block_length(msg: &Message, schema: &Schema, opts: &GeneratorOptions) -> usize {
msg.block_length
.map(|b| b as usize)
.or_else(|| block_length_from_fields(&message_fields(msg), schema, opts))
.unwrap_or(0)
}
fn group_block_length(g: &Group, schema: &Schema, opts: &GeneratorOptions) -> usize {
g.block_length
.map(|b| b as usize)
.or_else(|| block_length_from_fields(&group_fields(g), schema, opts))
.unwrap_or(0)
}
struct FieldLayout<'a> {
field: &'a Field,
offset: usize,
size: usize,
}
fn layout_fields<'a>(
fields: &[&'a Field],
schema: &Schema,
opts: &GeneratorOptions,
) -> Vec<FieldLayout<'a>> {
let mut cur = 0usize;
let mut layout = Vec::new();
for f in fields {
if field_is_constant(f, schema) {
continue;
}
if resolve_type(&f.ty, schema, opts, f.byte_order.as_deref()).is_none() {
continue;
}
if let Some(sz) = field_size_bytes(f, schema, opts) {
let start = f.offset.map(|o| o as usize).unwrap_or(cur);
layout.push(FieldLayout {
field: f,
offset: start,
size: sz,
});
cur = start + sz;
}
}
layout
}
fn composite_field_offset(
ty: &str,
field_name: &str,
schema: &Schema,
opts: &GeneratorOptions,
) -> Option<usize> {
let target = field_name.to_snake_case();
if let Some(TypeDef::Composite { fields, .. }) = schema.types.get(ty) {
let mut cur = 0usize;
for f in fields {
match f {
CompositeField::Type {
name,
primitive,
length,
presence,
..
} => {
if name.to_snake_case() == target {
return Some(cur);
}
if presence.as_deref() == Some("constant") {
continue;
}
if let Some(sz) = primitive_size_bytes(primitive) {
cur += sz * length.unwrap_or(1);
} else {
return None;
}
}
CompositeField::Ref { name, ty } => {
if name.to_snake_case() == target {
return Some(cur);
}
cur += type_size_bytes(ty, schema, opts, 0, true)?;
}
}
}
}
None
}
fn builder_param_type(resolved_type: &str) -> String {
let trimmed = resolved_type.trim();
if trimmed.starts_with('[') {
return trimmed.to_string();
}
let base = trimmed.rsplit("::").next().unwrap_or(trimmed);
match base {
"U16" => "u16".into(),
"I16" => "i16".into(),
"U32" => "u32".into(),
"I32" => "i32".into(),
"U64" => "u64".into(),
"I64" => "i64".into(),
"F32" => "f32".into(),
"F64" => "f64".into(),
"u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "u64" | "i64" | "f32" | "f64" => {
trimmed.to_string()
}
_ => trimmed.to_string(),
}
}
fn builder_value_expr(param_name: &str, resolved_type: &str) -> String {
let trimmed = resolved_type.trim();
if trimmed.starts_with('[') {
return param_name.to_string();
}
let base = trimmed.rsplit("::").next().unwrap_or(trimmed);
match base {
"U16" | "I16" | "U32" | "I32" | "U64" | "I64" | "F32" | "F64" => {
format!("{}::new({})", trimmed, param_name)
}
"u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "u64" | "i64" | "f32" | "f64" => {
format!("({}) as {}", param_name, base)
}
_ => param_name.to_string(),
}
}
fn set_bit_value_expr(bit_idx: u32, rust_type: &str) -> String {
let base = rust_type.rsplit("::").next().unwrap_or(rust_type);
match base {
"U16" => format!("U16::new({})", bit_cast_expr(bit_idx, "u16")),
"I16" => format!("I16::new({})", bit_cast_expr(bit_idx, "i16")),
"U32" => format!("U32::new({})", bit_cast_expr(bit_idx, "u32")),
"I32" => format!("I32::new({})", bit_cast_expr(bit_idx, "i32")),
"U64" => format!("U64::new({})", bit_cast_expr(bit_idx, "u64")),
"I64" => format!("I64::new({})", bit_cast_expr(bit_idx, "i64")),
"u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "u64" | "i64" => bit_cast_expr(bit_idx, base),
_ => format!("(1u64 << {})", bit_idx),
}
}
fn bit_cast_expr(bit_idx: u32, base: &str) -> String {
let shift = format!("(1u64 << {})", bit_idx);
match base {
"u8" => format!("{} as u8", shift),
"i8" => format!("{} as i8", shift),
"u16" => format!("{} as u16", shift),
"i16" => format!("{} as i16", shift),
"u32" => format!("{} as u32", shift),
"i32" => format!("{} as i32", shift),
"u64" => shift,
"i64" => format!("{} as i64", shift),
_ => shift,
}
}
fn count_value_expr(count_name: &str, ty: &str) -> String {
let trimmed = ty.trim();
let base = trimmed.rsplit("::").next().unwrap_or(trimmed);
match base {
"U16" => format!(
"{}::new(u16::try_from({}).expect(\"count fits in u16\"))",
trimmed, count_name
),
"I16" => format!(
"{}::new(i16::try_from({}).expect(\"count fits in i16\"))",
trimmed, count_name
),
"U32" => format!(
"{}::new(u32::try_from({}).expect(\"count fits in u32\"))",
trimmed, count_name
),
"I32" => format!(
"{}::new(i32::try_from({}).expect(\"count fits in i32\"))",
trimmed, count_name
),
"U64" => format!(
"{}::new(u64::try_from({}).expect(\"count fits in u64\"))",
trimmed, count_name
),
"I64" => format!(
"{}::new(i64::try_from({}).expect(\"count fits in i64\"))",
trimmed, count_name
),
"u8" => format!("u8::try_from({}).expect(\"count fits in u8\")", count_name),
"i8" => format!("i8::try_from({}).expect(\"count fits in i8\")", count_name),
"u16" => format!(
"u16::try_from({}).expect(\"count fits in u16\")",
count_name
),
"i16" => format!(
"i16::try_from({}).expect(\"count fits in i16\")",
count_name
),
"u32" => format!(
"u32::try_from({}).expect(\"count fits in u32\")",
count_name
),
"i32" => format!(
"i32::try_from({}).expect(\"count fits in i32\")",
count_name
),
"u64" => format!(
"u64::try_from({}).expect(\"count fits in u64\")",
count_name
),
"i64" => format!(
"i64::try_from({}).expect(\"count fits in i64\")",
count_name
),
_ => format!("{}.try_into().expect(\"count fits\")", count_name),
}
}
fn host_type_for_field(field: &Field, schema: &Schema) -> Option<String> {
let prim = field_primitive(field, schema)?;
optional_host_type(&prim).map(|s| s.to_string())
}
fn raw_expr_for_value(
field: &Field,
val_ident: &str,
schema: &Schema,
opts: &GeneratorOptions,
) -> Option<String> {
if let Some(TypeDef::Enum { encoding, .. } | TypeDef::Set { encoding, .. }) =
schema.types.get(&field.ty)
{
let inner_rust =
primitive_to_rust_endian(encoding, &opts.endian, field.byte_order.as_deref())?;
scalar_expr(&format!("{}.0", val_ident), &inner_rust)
} else {
Some(val_ident.to_string())
}
}