1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// legacy codegen module still contains unused helpers
// legacy codegen is being tightened incrementally
// experimental lints on stable code
// codegen uses panic/expect for irrecoverable states
// codegen functions need many params
// explicit loop in codegen is intentional
// codegen uses index-based loops
// schema value casting
// codegen generates format strings
// codegen structure
// codegen uses counter loops
// legacy string-template code
// legacy test helpers and config
// intentional readability in codegen
// schema constants with specific bit patterns
// SBE signal dispatch with matching bodies
// explicit in generated code patterns
// codegen uses concrete type names
// runtime buffer ops cannot be const
// error types carry context
// codegen variable naming
// intentional clarity in codegen
// SBE terms like blockLength are schema identifiers
// legacy IR model API
// legacy resolver pattern
// expect() is intentional in codegen
// generated code
// codegen uses unwrap_or pattern
// u8/u32 -> u64 in IR is intentional
// checked by schema validation
// schema validation ensures valid ranges
// float conversions are explicit
// SBE signal dispatch patterns
// test patterns
// codegen.rs is inherently large
// codegen uses descriptive names
// legacy control-flow patterns
// exhaustive match
// semantic intent
// codegen parameter style
// SBE value construction
// pointer casts are explicit
// domain_types threaded through recursive codegen
//! Opinionated, idiomatic Rust code generation for Simple Binary Encoding (SBE).
//!
//! [Simple Binary Encoding][sbe-spec] describes messages in XML; ergo-sbe
//! parses those schemas and emits safe, version-aware Rust codecs for
//! low-latency trading.
//!
//! ## In a few words
//!
//! - **Compile-time wire order** — calling `asks` before `bids` is a type error
//! - **Closure-based groups** — nested shape mirrors the schema, no `.parent()` hopscotch
//! - **Exact buffer sizing** — no oversize scratch buffers; works directly with
//! Aeron `try_claim`
//! - **Checked entry points** — `try_from` / `try_wrap` for untrusted input;
//! `wrap` for trusted — explicit in the type system
//! - **Zero heap allocation** on generated hot paths; zero runtime dependencies
//! - **Domain types** — map wire `Decimal` to `rust_decimal::Decimal` with one
//! line of config
//!
//! Full feature walkthrough: [crate README](https://github.com/mimran1980/ergon/blob/main/sbe/README.md).
//!
//! # Architecture
//!
//! | Layer | Module | Responsibility |
//! |-------|--------|----------------|
//! | Schema input | [`xml`], [`xsd`], [`schema`] | Parse SBE XML, optional XSD shape check, [`Schema`] |
//! | Intermediate | [`ir`], [`resolve`] | Token stream + offsets / block lengths |
//! | Options | [`config`] | Module name, conversions, domain objects, … |
//! | Codegen | [`codegen`] | Rust source modules |
//!
//! # Quick-start (`build.rs`)
//!
//! ```rust
//! use ergo_sbe::{parse, Generator, GenerationConfig, Schema};
//!
//! let schema_xml = r#"<?xml version="1.0" encoding="UTF-8"?>
//! <messageSchema package="example.sbe" id="1" version="0"
//! byteOrder="littleEndian">
//! <types>
//! <composite name="messageHeader">
//! <type name="blockLength" primitiveType="uint16"/>
//! <type name="templateId" primitiveType="uint16"/>
//! <type name="schemaId" primitiveType="uint16"/>
//! <type name="version" primitiveType="uint16"/>
//! </composite>
//! </types>
//! <message name="Car" id="1">
//! <field name="serialNumber" id="1" type="uint64" offset="0"/>
//! <field name="modelYear" id="2" type="uint16" offset="8"/>
//! </message>
//! </messageSchema>"#;
//!
//! let ir = parse(schema_xml).unwrap();
//! let schema = Schema::from_ir(ir);
//! let output = Generator::new(GenerationConfig::new("my_messages"))
//! .generate(&schema)
//! .unwrap();
//! assert!(output.modules().any(|m| m.path == "my_messages.rs"));
//! // write module.source to OUT_DIR and `include!` it from your crate
//! ```
//!
//! # What gets generated (how to use it)
//!
//! Names depend on your schema (`Car` below is illustrative). Examples use
//! `ignore` because the types only exist after codegen.
//!
//! ## Composites = wire images (not `repr(C)` overlays)
//!
//! Generated composites are `#[repr(transparent)] struct Engine(pub [u8; N])`:
//! the value **is** the on-wire byte block. Accessors use explicit
//! `from_le_bytes` / `to_le_bytes` at schema offsets (portable; free on LE).
//! Default decode is a flyweight (`EngineDecoder { buf, pos }`) — zero-copy
//! into the message. Do **not** transmute the buffer to a padded `#[repr(C)]`
//! field struct: SBE is packed and may be unaligned. See the crate README
//! section *Composite layout & little-endian*.
//!
//! ## Decode flyweight
//!
//! → [`samples/sbe-feature-tour`](https://github.com/mimran1980/ergon/blob/main/samples/sbe-feature-tour/src/lib.rs)
//!
//! ## Encode + type-state tails (buffer sizing)
//!
//! **Size the buffer first** with the staged `*EncodedLength` builder —
//! never guess with a large `vec![0u8; 4096]`. For fixed-only messages
//! use the const `ENCODED_LENGTH`.
//!
//! → [`samples/sbe-feature-tour`](https://github.com/mimran1980/ergon/blob/main/samples/sbe-feature-tour/src/lib.rs)
//!
//! ## Buffer sizing
//!
//! Schema-aware helpers size the buffer **before** you write — including
//! nested groups and ragged var-data — so you do not hand-calculate wire length
//! for complicated messages. Prefer a **stack array** when the length is
//! `const` (`ENCODED_LENGTH` / `compute_encoded_length_*`); for runtime
//! lengths, size first then claim or encode into an exact-length slot.
//!
//! | Shape | API | Prefer |
//! |-------|-----|--------|
//! | Fixed-only | `HeartbeatEncoder::ENCODED_LENGTH` (const) | `[0u8; N]` stack |
//! | Flat / known tails | `compute_encoded_length_with_message_header(...)` | stack when const |
//! | Groups / nested / ragged | `CarEncodedLength::new()…encoded_length_with_header()` | exact claim / slot |
//!
//! ## Why wire order is compile-time
//!
//! SBE is positional. Two adjacent identical groups (e.g. bids then asks) are
//! common in market data; swapping them still produces “valid” bytes and only
//! fails in production. Named stage structs make the wrong order a type error.
//!
//! ## Field metadata (Java parity)
//!
//! → [`sbe/tests/java_parity_features_test.rs`](https://github.com/mimran1980/ergon/blob/main/sbe/tests/java_parity_features_test.rs)
//!
//! ## Conversion: `with_conversion` vs `with_domain_type`
//!
//! **Pick one style per selector** — `with_domain_type` already enables conversion.
//!
//! | Config | Generated decode | Generated encode |
//! |--------|------------------|------------------|
//! | [`GenerationConfig::with_conversion`] | `dec.price_as::<T>()?` | `enc.price_from(&t)?` |
//! | [`GenerationConfig::with_domain_type`] | `dec.price() -> path::Type` | `enc.price(value)` |
//!
//! ```rust
//! use ergo_sbe::{ConversionSelector, GenerationConfig};
//!
//! // A — pluggable: you implement TryFromSbe / TryToSbe
//! let _a = GenerationConfig::new("msgs")
//! .with_conversion(ConversionSelector::named_type("Decimal"));
//!
//! // B — concrete Rust type (implies conversion for the same selector)
//! let _b = GenerationConfig::new("msgs")
//! .with_domain_type(
//! ConversionSelector::named_type("Decimal"),
//! "rust_decimal::Decimal",
//! );
//! ```
//!
//! See [`sbe/tests/comprehensive_test.rs`](https://github.com/mimran1980/ergon/blob/main/sbe/tests/comprehensive_test.rs)
//! (conversion and domain-object coverage).
//!
//! ## Domain objects
//!
//! [`GenerationConfig::with_domain_objects`]`(`[`DomainVarData`]`)` emits
//! owned structs. Use [`DomainVarData::LossyStrings`] for text (`String`;
//! invalid UTF-8 → `""`) or [`DomainVarData::Bytes`] for `Vec<u8>`.
//!
//! See [`sbe/tests/domain_objects_test.rs`](https://github.com/mimran1980/ergon/blob/main/sbe/tests/domain_objects_test.rs)
//!
//! ## Multi-message dispatch
//!
//! See [`sbe/fuzz/fuzz_targets/any_message_frame_cursor.rs`](https://github.com/mimran1980/ergon/blob/main/sbe/fuzz/fuzz_targets/any_message_frame_cursor.rs)
//!
//! ## Fixed arrays / char fields
//!
//! See [`sbe/tests/java_parity_features_test.rs`](https://github.com/mimran1980/ergon/blob/main/sbe/tests/java_parity_features_test.rs)
//!
//! ## Keywords in schema names
//!
//! Field `type` becomes `type_` (default append `"_"`). Override with
//! [`GenerationConfig::with_keyword_append_token`].
//!
//! ## XSD structural check
//!
//! Optional CI gate: [`validate_against_sbe_xsd`] or [`parse_with_xsd_validation`].
//! Official XSD text is embedded as [`SBE_XSD`].
//!
//! # Design
//!
//! - Wire-compatible with official SBE / sbe-tool baselines where tested
//! - Idiomatic Rust (type-state tails, borrow flyweights) — not a Java port
//! - Zero allocation on decode hot paths by default
//! - Version-aware accessors (`sinceVersion` / acting version)
//! - Unsafe only on explicit `_unchecked` / documented paths
//!
//! Book: [ergon book](https://mimran1980.github.io/ergon/).
//! Benchmarks: [benchmarks chapter](https://mimran1980.github.io/ergon/sbe/benchmarks.html).
//!
//! [sbe-spec]: https://www.fixtrading.org/standards/sbe/
/// Re-exported so `build.rs` can return [`miette::Result`] without an extra
/// dependency. Enable the crate's `fancy` feature for graphical rendering
/// (source snippet + span) instead of the plain fallback.
pub use miette;
/// Cargo `build.rs` helpers ([`generate_to_out_dir`], [`sbe_mod!`]).
/// Codec generation ([`Generator`]).
/// [`GenerationConfig`] — conversions, domain objects, keywords, etc.
/// Token [`Ir`] (usually via [`Schema`]).
/// Offset resolution ([`resolve_schema`]; called by parse).
/// [`Schema`] handle for codegen.
/// Structured IR for codegen (internal).
pub
/// XML parse ([`parse`], [`parse_file`]).
/// Optional XSD-shaped validation ([`validate_against_sbe_xsd`], [`SBE_XSD`]).
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use Schema;
pub use ;
pub use ;
// Header-state markers (`HeaderPresent` / `HeaderAbsent`) live in each
// generated module's `sbe_rt` (see `generate_sbe_rt_src`). They are not
// re-exported here: generated codecs seal against their own `sbe_rt::HeaderState`,
// so a shared `ergo_sbe::header_state` type would not unify with `H`.