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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
// Crate-root allows are deliberately few. Codegen-specific noise lives on
// `codegen` (and other modules) via scoped attributes — not a 40-line blanket
// silence of workspace lint policy.
//
// Justified at crate root (schema/codegen reality, not laziness):
// Generator pipelines thread many schema/config params
// Emit functions are inherently large token builders
// SBE identifiers (blockLength, templateId) trip false positives
// Numeric widths constrained by schema validation
// Same: ranges validated against primitive types
//! 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.
//!
//! # Documentation
//!
//! - **[ergo-sbe book](https://mimran1980.github.io/ergon/)** — getting started,
//! feature tour, core concepts, configuration, recipes, design notes
//! - [Getting started](https://mimran1980.github.io/ergon/sbe/getting-started.html) ·
//! [Feature tour](https://mimran1980.github.io/ergon/sbe/feature-tour.html) ·
//! [Coming from sbe-tool](https://mimran1980.github.io/ergon/sbe/getting-started/from-sbe-tool.html) ·
//! [Type-state design](https://mimran1980.github.io/ergon/sbe/design-notes/type-state.html)
//! - [Crate README](https://github.com/mimran1980/ergon/blob/main/sbe/README.md)
//!
//! ## 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** — `decode` / `try_from` / `wrap` return `Result`
//! and validate extents; zero-check cores stay private until an HFT-008 keep
//! - **Zero heap allocation** on generated hot paths; zero runtime dependencies
//! - **Domain types** — map wire `Decimal` to `rust_decimal::Decimal` with one
//! line of config
//!
//! # 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`) — doctest pins generated idioms
//!
//! Generated *application* types do not exist in this crate at doctest time.
//! The example below **runs the generator** on an inline schema and asserts on
//! the emitted source so chained encode / length-builder names cannot drift
//! unnoticed. For end-to-end encode/decode of real types, see
//! [`samples/sbe-feature-tour`](https://github.com/mimran1980/ergon/tree/main/samples/sbe-feature-tour)
//! and the golden file
//! [`sbe/tests/golden/car_example.rs`](https://github.com/mimran1980/ergon/blob/main/sbe/tests/golden/car_example.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).expect("parse schema");
//! let schema = Schema::from_ir(ir);
//! let output = Generator::new(GenerationConfig::new("my_messages"))
//! .generate(&schema)
//! .expect("generate");
//! let src = &output.modules().next().expect("one module").source;
//! assert!(src.contains("CarDecoder"));
//! assert!(src.contains("CarEncoder"));
//! assert!(src.contains("CarFixedFields"));
//! assert!(src.contains("wrap_and_apply_header"));
//! assert!(src.contains("compute_length_with_header") || src.contains("ENCODED_LENGTH"));
//! // 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). Prefer the
//! feature-tour sample and golden file over prose-only snippets.
//!
//! ## 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.try_price()? -> path::Type` | `enc.try_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`] with a [`DomainVarData`] mode emits
//! owned structs. Use [`DomainVarData::LossyStrings`] for text (`String`;
//! invalid UTF-8 → `InvalidUtf8` error) 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
//!
//! 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`]).
// Scoped: quote!/token emit paths and submodule re-exports trip style lints and
// unused_import on `pub(crate) use` hubs. Prefer fixing real dead code over
// growing this list — do not re-blanket the crate root.
/// [`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`.