edifact-rs โก
EDIFACT (ISO 9735) for Rust โ zero-copy parsing, streaming deserialization, typed derive macros, and composable validation.
๐ Guides ยท ๐ฆ API reference ยท ๐ฆ crates.io
Install
derive is on by default. Optional features:
Quick start
Parsing borrows straight from the input โ segment tags and component values are
&str slices into your buffer:
use from_bytes;
let input = b"UNA:+.? 'UNH+1+ORDERS:D:11A:UN'BGM+220+PO-4711+9'UNT+3+1'";
let segments: = from_bytes.?;
let bgm = &segments;
assert_eq!;
assert_eq!; // document code
assert_eq!; // document number
# Ok::
Map segments and whole messages onto structs, dispatching repeated segments by their qualifier:
use ;
let input = b"UNH+1+ORDERS:D:11A:UN'BGM+220+PO-4711+9'\
NAD+BY+4000001000002::9'NAD+SU+4000001000001::9'UNT+5+1'";
let segments: = from_bytes.?;
let msg = edifact_deserialize?;
assert_eq!;
# Ok::
Write it back out with delimiters escaped for you:
use to_edifact_string;
# use EdifactSerialize;
#
#
#
let wire = to_edifact_string?;
assert_eq!;
# Ok::
What makes it different
EDIFACT is deceptively simple โ flat text, a handful of delimiters โ which is
why hand-rolled parsers are common and quietly wrong. The delimiters are
redefinable per interchange, any of them may appear inside a value when
release-escaped, and syntax version 4 adds a repetition separator that changes
an element's shape rather than its text. edifact-rs takes a position on each
of the places that usually goes wrong:
| Zero-copy by default | Tags and values borrow from the input slice. The only per-segment allocation is the element vector; an owned string appears solely where a release escape had to be resolved. |
| Constant-memory streaming | Reader iterators yield one segment at a time; message windows group them into UNHโฆUNT units. A multi-gigabyte interchange costs one message of peak memory. |
| Identifiers, not indices | Address a field by its UN/EDIFACT data element identifier. The derive resolves it during const evaluation, so a stale identifier fails the build instead of reading the element next door. |
| Repetitions are parsed | A declared repetition separator splits an element into real occurrences (ISO 9735-4 ยง3.1) instead of leaving 1*ON in the value as literal text. |
| Limits report, never truncate | Segment, message, and byte budgets raise an error. A budget that quietly ended iteration is indistinguishable from clean end-of-input, so a caller would accept a truncated interchange as a whole one. |
| Layered validation | Envelope, structure, code-list, and profile checks write into one report carrying stable error codes, byte spans, and filterable rule identifiers. |
No unsafe |
#![deny(unsafe_code)], with property and fuzz tests over parse, write, and validate on every commit. |
Addressing a field by identifier
Transpose one positional index and you read the wrong data element โ and it still validates clean. Given a segment definition, address the value by its identifier instead, and a wrong reference becomes a lookup error:
use ;
static C082: & = &;
static NAD_ELEMENTS: & = &;
static NAD: SegmentDefinition = new;
let segs: = from_bytes.?;
assert_eq!;
assert_eq!;
assert!; // DE 2380 belongs to DTM
# Ok::
The derive performs the same resolution at compile time โ see Typed Derive.
Documentation
Full guides live at hupe1980.github.io/edifact-rs. Every Rust snippet on the site is compiled and run as part of the test suite, so none of it can drift from the crate.
| Guide | |
|---|---|
| Getting Started | Install, first parse, feature flags |
| Core Concepts | Wire format, UNA, release characters, repetitions, Rust type mapping |
| Parsing | Entry points, byte spans, and the ReaderConfig budgets |
| Writing | Writer, escaping, custom UNA, repeating elements |
| Typed Derive | Every derive attribute, including identifier-addressed fields |
| Streaming | Reader iterators, message windows, typed extraction |
| Validation | Validator, ValidationContext, the four layers |
| Profile Packs | Authoring, composing, and filtering business rules |
| Diagnostics | miette integration |
| Async Integration | Bridging to tokio |
| Error Reference | Every stable code E001โE037 |
| Performance | Allocation budgets, benchmarks, tuning |
Runnable cookbooks live in
crates/edifact-rs/examples/ โ try one with
cargo run --example cookbook_parse_map_validate_write.
Scope
edifact-rs is the engine, not a directory distribution. It ships the
parser, writer, validation pipeline, and the table types for segment
definitions โ but no UN/EDIFACT directory data, which is versioned, large, and
licensed separately. Supply your own definitions as static tables at compile
time, or load them at startup with DirectoryValidatorBuilder.
Development
The justfile mirrors CI, so a green just ci means a green build:
MSRV and edition
Rust 1.85, edition 2024. The MSRV is enforced by CI on every push and a raise is treated as a breaking change.
License
Dual-licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work shall be dual-licensed as above, without any additional terms or conditions.