use std::fs;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
use sbe_gen::{GeneratorOptions, generate};
fn write_generated(out_dir: &Path, xml: &str) -> TempDir {
let temp = tempfile::tempdir_in(out_dir).expect("tempdir");
let src_dir = temp.path().join("src");
fs::create_dir_all(&src_dir).expect("create src");
let modules = generate(xml, &GeneratorOptions::default()).expect("generate");
for (name, contents) in modules {
fs::write(src_dir.join(name), contents).expect("write module");
}
let main_rs = r#"
#![allow(dead_code, non_camel_case_types, unused_imports, unused_variables, unused_mut)]
mod types;
mod message_header;
mod heartbeat;
mod negotiate500;
mod trade;
use heartbeat::*;
use message_header::MessageHeader;
use negotiate500::*;
use trade::*;
use zerocopy::byteorder::little_endian::U16;
fn main() {
// fixed-only borrowed path
let mut fixed_dst = [0u8; 64];
let heartbeat_len = Heartbeat::encode_body_into(&mut fixed_dst, |enc| {
enc.seq(11);
enc.sending_time(99);
Ok(())
}).expect("heartbeat encode");
assert_eq!(heartbeat_len, Heartbeat::BLOCK_LENGTH as usize);
let (hb, tail) = Heartbeat::parse_prefix(&fixed_dst[..heartbeat_len]).expect("heartbeat parse");
assert_eq!(hb.seq.get(), 11);
assert_eq!(hb.sending_time.get(), 99);
assert!(tail.is_empty());
// borrowed header+body equals owned builder framing
let mut builder = Negotiate500Builder::new();
builder.seq(42).firm(7);
builder.credentials(b"secret").expect("credentials");
let owned_framed = builder.finish_with_header();
let header = MessageHeader {
block_length: U16::new(Negotiate500::BLOCK_LENGTH),
template_id: U16::new(Negotiate500::TEMPLATE_ID),
schema_id: U16::new(Negotiate500::SCHEMA_ID),
version: U16::new(Negotiate500::SCHEMA_VERSION),
};
let mut framed_dst = [0u8; 128];
let framed_len = Negotiate500::encode_with_header_into(&mut framed_dst, header, |enc| {
enc.seq(42).firm(7);
enc.credentials(b"secret")?;
Ok(())
}).expect("framed encode");
assert_eq!(&framed_dst[..framed_len], owned_framed.as_slice());
// borrowed body with var-data
let mut body_dst = [0u8; 128];
let body_len = Negotiate500::encode_body_into(&mut body_dst, |enc| {
enc.seq(100).firm(200);
enc.credentials(b"abc")?;
Ok(())
}).expect("body encode");
let (msg, rest) = Negotiate500::parse_prefix(&body_dst[..body_len]).expect("body parse");
assert_eq!(msg.seq.get(), 100);
assert_eq!(msg.firm.get(), 200);
let (creds, tail) = msg.parse_credentials(rest).expect("credentials parse");
assert_eq!(creds.as_str().unwrap(), "abc");
assert!(tail.is_empty());
// precise buffer-too-small error
let mut short_dst = [0u8; 8];
let short_err = Negotiate500::encode_body_into(&mut short_dst, |enc| {
enc.seq(1).firm(2);
Ok(())
}).expect_err("expected buffer-too-small");
match short_err {
negotiate500::EncodeIntoError::BufferTooSmall { required, available } => {
assert!(required > available);
assert_eq!(available, 8);
}
other => panic!("unexpected error: {:?}", other),
}
// var-data length overflow error (uint8 length prefix)
let mut overflow_dst = [0u8; 512];
let payload = [9u8; 300];
let overflow_err = Negotiate500::encode_body_into(&mut overflow_dst, |enc| {
enc.seq(1).firm(2);
enc.credentials(&payload)?;
Ok(())
}).expect_err("expected length-overflow");
match overflow_err {
negotiate500::EncodeIntoError::LengthOverflow { len, max } => {
assert_eq!(len, 300);
assert_eq!(max, 255);
}
other => panic!("unexpected error: {:?}", other),
}
// borrowed groups+var-data parity with owned builder framing
let mut trade_builder = TradeBuilder::new();
trade_builder.seq(9).price(555);
trade_builder.legs(|legs| {
legs.entry(|entry| {
entry.qty(10).side(1);
entry.note(b"bid").expect("note");
});
legs.entry(|entry| {
entry.qty(20).side(2);
entry.note(b"ask").expect("note");
});
});
trade_builder.comment(b"ok").expect("comment");
let owned_trade = trade_builder.finish_with_header();
let trade_header = MessageHeader {
block_length: U16::new(Trade::BLOCK_LENGTH),
template_id: U16::new(Trade::TEMPLATE_ID),
schema_id: U16::new(Trade::SCHEMA_ID),
version: U16::new(Trade::SCHEMA_VERSION),
};
let mut trade_dst = [0u8; 256];
let trade_len = Trade::encode_with_header_into(&mut trade_dst, trade_header, |enc| {
enc.seq(9).price(555);
enc.legs(|legs| {
legs.entry(|entry| {
entry.qty(10).side(1);
entry.note(b"bid")?;
Ok(())
})?;
legs.entry(|entry| {
entry.qty(20).side(2);
entry.note(b"ask")?;
Ok(())
})?;
Ok(())
})?;
enc.comment(b"ok")?;
Ok(())
}).expect("trade encode");
assert_eq!(&trade_dst[..trade_len], owned_trade.as_slice());
}
"#;
fs::write(src_dir.join("main.rs"), main_rs).expect("write main");
let cargo_toml = r#"[package]
name = "encode_into_example"
version = "0.0.0"
edition = "2024"
[dependencies]
zerocopy = { version = "0.8", features = ["derive"] }
"#;
fs::write(temp.path().join("Cargo.toml"), cargo_toml).expect("write Cargo.toml");
temp
}
#[test]
fn encode_into_roundtrip_and_parity() {
let xml = r#"
<messageSchema package="test" schemaId="1" version="1">
<types>
<composite name="groupSize">
<type name="blockLength" primitiveType="uint16"/>
<type name="numInGroup" primitiveType="uint16"/>
</composite>
<type name="varStringEncoding" primitiveType="uint8"/>
<type name="ClientFlowType" presence="constant" length="10" primitiveType="char">IDEMPOTENT</type>
</types>
<message name="Heartbeat" id="1" blockLength="12">
<field name="seq" id="1" type="uint32" offset="0"/>
<field name="sending_time" id="2" type="uint64" offset="4"/>
</message>
<message name="Negotiate500" id="500" blockLength="12">
<field name="CustomerFlow" id="1" type="ClientFlowType"/>
<field name="seq" id="2" type="uint32" offset="0"/>
<field name="firm" id="3" type="uint64" offset="4"/>
<data name="credentials" id="4" type="varStringEncoding"/>
</message>
<message name="Trade" id="2" blockLength="12">
<field name="seq" id="1" type="uint32" offset="0"/>
<field name="price" id="2" type="int64" offset="4"/>
<group name="Legs" id="3" blockLength="5" dimensionType="groupSize">
<field name="qty" id="1" type="int32" offset="0"/>
<field name="side" id="2" type="uint8" offset="4"/>
<data name="note" id="3" type="varStringEncoding"/>
</group>
<data name="comment" id="4" type="varStringEncoding"/>
</message>
</messageSchema>
"#;
let workspace = Path::new(env!("CARGO_MANIFEST_DIR"));
let temp = write_generated(workspace, xml);
let status = Command::new("cargo")
.args(["run", "--quiet", "--offline"])
.current_dir(temp.path())
.status()
.expect("spawn cargo");
assert!(
status.success(),
"encode-into example should compile and run"
);
}