Skip to main content

Crate en16931_formats

Crate en16931_formats 

Source
Expand description

European e-invoicing formats, on top of the EN 16931 semantic model.

        ┌──────────────┐                       ┌──────────────────────┐
        │   billing    │                       │  inbound documents   │
        │ calculations │                       │  UBL / CII / ZUGFeRD │
        └──────┬───────┘                       └──────────┬───────────┘
               │  adapter (optional feature)              │
               └──────────────┬───────────────────────────┘
                              ▼
                    ┌─────────────────────┐
                    │      en16931        │  semantic model, 317 rules,
                    │  proof of validity  │  no XML, no PDF, no I/O
                    └──────────┬──────────┘
                               ▼
                    ┌─────────────────────┐
                    │  en16931-formats    │  ← you are here
                    │   UBL · CII · PDF   │
                    └─────────────────────┘

en16931 decides whether an invoice is correct. This crate decides what it looks like on the wire, and re-implements not one of the 317 rules.

§Why one crate, not one per format

XRechnung is carried in UBL and CII; every ZUGFeRD payload is CII. A crate per format would need the CII binding twice, and two bindings drift. Cargo features already express which syntax a consumer wants, so a crate boundary here would be solving with a package what --no-default-features solves for free.

What is a separate crate is en16931, and that boundary is load-bearing: this crate depends on it, so rustc forbids the reverse. “The semantic rules do not depend on a syntax” is enforced rather than asked for, and en16931’s dependency graph stays at ten crates and builds for wasm32.

§Features, and what each one costs

FeatureDefaultGraphWhat
ubl13 cratesUBL 2.1, both directions
cii13 cratesUN/CEFACT CII D16B, both directions
zugferd57 cratesZUGFeRD / Factur-X hybrid PDFs — reading only, see zugferd

zugferd is off by default and that matters: lopdf brings AES, ChaCha20, SHA-2, getrandom and libc, and the result does not build for wasm32-unknown-unknown. Nobody reading a UBL invoice should pay for that.

§The 91 % that costs a writer nothing

CEN’s artefacts carry 1 339 syntax rules, and 1 218 of them (91 %) say some element “shall not be used” — they fence off the parts of UBL 2.1 and CII D16B that EN 16931 does not use. That inverts the usual expectation:

  • A writer driven from the semantic model cannot violate them. It has no way to express cbc:UUID, because the model has no term for it. They are unreachable, not cheaply satisfied — the same shape as InvoiceAmount making BR-DEC-* unrepresentable. So the writer answers to roughly 119 real rules.
  • A reader must cope with all 1 339, because the document came from somewhere else.

Unreachability is a claim, so the serialiser enforces it against the prohibitions extracted from CEN’s own Schematron, and tests/subset.rs asserts the writer never needs that safety net. See ubl::prohibitions.

§Quick start

use en16931::Invoice;

let xml = en16931_formats::ubl::to_string(&Invoice::default());
let read = en16931_formats::ubl::from_str(&xml).expect("readable");
assert!(read.unmapped.is_empty(), "nothing was silently dropped");

§Reading a document somebody else wrote

Which is the only kind worth reading. Three ways an inbound document can attack the reader rather than inform it, and what happens to each:

entity expansion (billion laughs)needs a DTD; the parser rejects every document carrying one
external entities (XXE, file disclosure)the same DTD refusal, for the same reason
nestingrefused past ubl::MAX_DEPTH before parsing — see below

The third is the one that had teeth. roxmltree recurses once per level of nesting and overflows the stack a few hundred levels in, and a stack overflow is not a panic: Rust cannot unwind it and cannot catch it, so the process aborts. Two lines of XML took down the caller, with no report and nothing for ? to catch.

It cannot be handled afterwards, so it is refused before: one linear scan of the bytes, then ubl::Error::TooDeep or its CII twin. The limit is 64 and the deepest of the 487 published instances in the artefact tree is nine, which tests/corpus.rs measures rather than assumes.

What this crate does not do is bound input size or time — a caller who reads from a socket owns that decision, and a library that quietly capped it would be wrong for the batch job and useless for the endpoint.

§Attribution

The bindings are derived from CEN’s EUPL-1.2 validation artefacts and the element order from the authorities’ published instances. The notice is a licence condition, not decoration, and it has a test.

Re-exports§

pub use xrechnung::Flavour;
pub use xrechnung::detect;

Modules§

ciicii
The UN/CEFACT Cross Industry Invoice D16B binding, both directions.
ublubl
The UBL 2.1 binding, both directions.
xrechnung
XRechnung — which specification a document claims, and writing one that claims it.
zugferdzugferd
ZUGFeRD and Factur-X — invoices that are a PDF and machine-readable data.

Structs§

NotValid
A document was not written, because it did not pass the profile it was asked to be written for.

Enums§

Syntax
Which syntax a document is written in.

Constants§

ATTRIBUTION
The CEN attribution notice, as en16931 carries it.

Functions§

sniff
Guess the syntax from the document element, without parsing.