Edifact.Structure 1.0.0-preview.1

This is a prerelease version of Edifact.Structure.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package Edifact.Structure --version 1.0.0-preview.1
                    
NuGet\Install-Package Edifact.Structure -Version 1.0.0-preview.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Edifact.Structure" Version="1.0.0-preview.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Edifact.Structure" Version="1.0.0-preview.1" />
                    
Directory.Packages.props
<PackageReference Include="Edifact.Structure" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Edifact.Structure --version 1.0.0-preview.1
                    
#r "nuget: Edifact.Structure, 1.0.0-preview.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Edifact.Structure@1.0.0-preview.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Edifact.Structure&version=1.0.0-preview.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Edifact.Structure&version=1.0.0-preview.1&prerelease
                    
Install as a Cake Tool

UN/EDIFACT

A .NET library that reads, validates, writes, projects and acknowledges UN/EDIFACT interchanges against the published UNECE directories — data-driven, with no generated code and no message types compiled into it.

Targets net8.0 and net10.0. Apache-2.0, with a carve-out for the vendored UN directories; see NOTICE.

The property that matters

Reading an interchange and writing it back gives the same characters. Not "something a parser accepts" — the same characters, including the delimiters it arrived with, the release characters somebody used redundantly, the line ending, the trailing newline, and the fragment of a segment a truncated transmission ended in the middle of.

That is the correctness asset the whole design is built around, because it is the only test that fails when something is quietly dropped rather than misunderstood. A validator that cannot hand back the file it was given has no way to show it understood the file. It holds over 3000 generated interchanges, the committed samples, a message taken apart into a tree and reassembled with its orphans in the order they arrived, and 2000 generated invoices projected to JSON or XML and read back. It holds for a generated CONTRL too — an acknowledgement this library cannot itself read is not an acknowledgement.

You can run it against your own traffic:

dotnet run --project src/Edifact.Cli -- validate your-file.edi --round-trip

When it fails it names the first position where the output diverges, with both sides shown and the whitespace made visible.

What it does

dotnet run --project src/Edifact.Cli -- ingest packs/sources/UNTDID/D96A
dotnet run --project src/Edifact.Cli -- inspect untdid-d96a
dotnet run --project src/Edifact.Cli -- validate samples/invoic-d96a-broken.edi
dotnet run --project src/Edifact.Cli -- validate samples/invoic-d96a.edi --guide packs/overlays/prototype-orders-rules.json
dotnet run --project src/Edifact.Cli -- project samples/invoic-d96a.edi
  • Ingest a UNECE release directory into a committed pack. Both vendored releases ingest with nothing unaccounted for: D.96A gives 462 data elements, 161 composites, 127 segments, 207 code lists and 125 messages; D.01B gives 641, 295, 233, 268 and 208, interactive messages included. A drift test asserts each committed pack is byte-for-byte what a fresh ingest produces.

  • Validate an interchange: envelope, segment table, data element representation, code lists, character repertoire. Nothing on the read path throws — every defect is a finding with a position, a severity, a stable rule identifier and the authority that raised it. A MessageTree always exists, even for a message the directory does not define.

    Severity follows how much authority stands behind a rule. A code the directory does not publish is a warning, because a published list is advisory on its own — D.01B's own entry for 4053 lists two codes and then instructs the reader to use Incoterms it never enumerates. A code an agreement forbids is an error, because somebody signed it. Real EANCOM traffic leans on that distinction constantly, and samples/ holds three anonymized interchanges that do.

  • Layer implementation guides over a release as versioned JSON, repeatably and in precedence order, so a report can say "'DP' is not permitted for 3035 by eancom-2002-invoic; the base untdid-d96a directory does permit it" rather than a bare rule identifier. Guides are documents, never C#.

  • Project a matched message as JSON or XML and read either back. Or project a message type and everything it names — 42 segments, 63 composites, 201 elements and 101 code lists for D.96A INVOIC — so a builder does one fetch instead of walking a graph five levels deep.

  • Acknowledge an interchange with a CONTRL, in syntax version 3 or version 4. Every finding already carries the 1-based segment, data element and component positions a CONTRL is written in.

How it is put together

A directory release and a syntax version are independent axes, and conflating them is the mistake this library was written to avoid. packs/compiled/ holds releases; packs/syntax/ holds what ISO 9735 contributes. A D.01B interchange written in version 3 syntax is legal and common, and it says so in UNB data element 0002 — so the envelope is validated against the version the interchange declares, not against the version its pack was built for.

Project What it holds
Edifact.Core Tokenizer, writer, document model, diagnostics, character repertoires. No project references, enforced by a test
Edifact.Schema Runtime model, pack format, linker, ISO 9735 syntax packs
Edifact.Untdid Dialect detection, ingestion, reconciliation
Edifact.Structure Segment-table compiler, thread-set matcher, bounded repair. About 4 µs per segment
Edifact.Overlays Guide documents, the composer, the closed rule language, the provenance ledger
Edifact.Projection A message as JSON or XML and back
Edifact.Validation Envelope, element and structural checks
Edifact.Acknowledgement CONTRL for syntax versions 3 and 4
Edifact.Cli ingest, inspect, validate, project
Edifact.Api A JSON API over the validator, and the OpenAPI document clients are generated from

The dependency graph is written out in tests/Edifact.Architecture.Tests/LayeringTests.cs and asserted exactly, so widening it is a deliberate edit visible in a diff. Edifact.Core depends on nothing, which is what lets a file nobody has a directory for still be read and shown to somebody.

Outside the solution's layered projects sit two more: web/edifact-web, a Nuxt application that is the page somebody pastes an interchange into, and Edifact.AppHost, which runs it and the API together. Neither is a layer — they are the delivery, not the library — and web/edifact-web/README.md covers both.

Each layer's design notes live beside its code: packs/sources/README.md, packs/syntax/README.md, src/Edifact.Overlays/README.md, src/Edifact.Projection/README.md, src/Edifact.Validation/README.md. ROADMAP.md says what is done, what is not, and what was deliberately left undone.

The web validator

dotnet run --project Edifact.AppHost

Starts the API and the Nuxt frontend together and opens the Aspire dashboard, which is where both resources' URLs, logs and traces are. Paste an interchange into the page and it is read as you type: the offending characters are underlined where they sit, each finding says which authority it offended and which one that authority was layered over, and the message's structure is a tree you can click into. web/edifact-web/README.md has the details, including running either half alone.

Running it

docker compose up --build

The validator on http://localhost:3000, built from this working copy. Or from what has been published, without a checkout at all:

TAG=0.1.0 docker compose up -d

Two images, ghcr.io/wimsuenens/edifact-api and ghcr.io/wimsuenens/edifact-web. Only the second publishes a port: every request the browser makes reaches the API through the front end, which is what keeps it same-origin and is why the API carries no CORS configuration to get wrong. GET /api/version says which build is answering, and is also the API container's readiness probe — the server reads every pack before it serves anything, so a reply there means the packs parsed.

What each image reads from its environment:

Packs__Root Where the compiled packs are. Set in the image; the samples are found as a sibling of its parent.
Build__Version, Build__Commit, Build__BuiltAt What /api/version reports. Set from build arguments.
NUXT_API_URL Where the front end forwards /api/**. Read per request, so one image runs against any API.
WEB_PORT What the front end is published on. Defaults to 3000.

Releasing

Update CHANGELOG.md, then tag:

git tag v0.1.0 && git push --tags

The release workflow stands the built pair up and validates a known-bad interchange through it, and separately installs the packed tool and uses it, before anything is published; then it pushes the images, pushes the nine packages to nuget.org, and cuts the GitHub release from the changelog section. Pushes to main publish edge and sha-<commit> images; nothing goes to nuget.org except from a tag.

One version covers both, and it lives in Directory.Build.props rather than only in the tag — the workflow refuses to publish a tag that disagrees with it. Its major is load-bearing and is not free to move: the ingester records the assembly's major version in every pack manifest it writes, and the committed packs are asserted byte-identical to a fresh ingest, so 1.0.0, 1.4.2 and 1.0.0-preview.1 all leave the packs alone while 0.x or 2.x would rewrite both for no change to their contents. That is why the first published package is a 1.0.0 prerelease rather than a 0.1.0: the prerelease suffix, not the major, is what says the API is still moving.

Publishing needs a NUGET_USER repository variable naming the nuget.org account, and a Trusted Publishing policy on nuget.org pointing at this repository and release.yml. There is no stored API key — the workflow exchanges a GitHub OIDC token for one that lasts about an hour.

Building and testing

dotnet build Edifact.slnx
dotnet test Edifact.slnx

592 tests across 11 projects. The 542 covering the library run twice — once on net8.0, once on net10.0 — so both runtimes must be installed; the older leg is not there for reach alone, it is what stops a .NET 9-only convenience from being written in the first place. Nothing is conditionally compiled. The 50 covering Edifact.Api run once, on net10.0: an application is not consumed by reference, so nothing is served by holding it to the older runtime.

Counts in tests are exact, never "more than a hundred". A directory that came out plausible-looking and short by three records is the failure this library exists to make impossible, and a lower bound would not notice it.

The vendored directories

packs/sources/UNTDID/ is the UN/ECE distribution exactly as downloaded, byte for byte, and CI checks all 31 files against the blobs recorded in the index on both Linux and Windows. It is CP437 fixed-column text in which the column of a box-drawing character carries segment-group nesting depth, so .gitattributes marks the tree -text -diff.

The UN licence permits copying the Directory and passing it on unmodified; it does not permit modifying and distributing it. That is why packs/compiled/ holds structure only — tags, cardinalities, representations, code values — and no descriptive prose. Prose is read on demand from the vendored files at runtime and is never committed in a transformed form.

Using it as a library

dotnet add package Edifact.Validation --prerelease
dotnet tool install --global Edifact.Cli --prerelease

Nine packages, net8.0 and net10.0, no external dependencies. Edifact.Validation is the usual entry point and pulls in what it needs; Edifact.Core, .Schema, .Structure, .Overlays, .Projection, .Untdid and .Acknowledgement are there for anyone who wants one layer of the diagram above without the ones over it.

No directory data ships with them. A compiled pack is a derivative of the UN Directory, and a package would make every consumer a redistributor of it — a stronger claim than a public repository makes, and one this project has not made. So you supply the directory yourself, once:

edifact ingest ~/Downloads/D01B --packs ~/edifact/packs/compiled
export EDIFACT_PACKS=~/edifact/packs/compiled

Downloaded from UNECE, ingested on your machine, and nothing UN-derived crosses nuget.org. The ISO 9735 syntax definitions are in the tool, because they are authored here rather than derived from anything (see packs/syntax/README.md) and no ingestion can produce them.

Status

Published as container images and as prerelease NuGet packages; see Running it and Using it as a library. The packages are 1.0.0-preview.* rather than stable because the public API is still moving — see ROADMAP.md for what is settled and what is not.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Edifact.Structure:

Package Downloads
Edifact.Overlays

Trading partner and subset rules layered over a base directory — tightened cardinalities, restricted code values and conditional requirements — compiled into an effective message and evaluated with a ledger saying which overlay decided what.

Edifact.Validation

Validates an interchange against a directory and any overlays: envelope structure, segment and element cardinality, representation and code values, each finding carrying the rule that produced it and where in the source it applies.

Edifact.Projection

Projects a matched message into JSON or XML, either as the document it describes or as a linked form in which every value still points at the directory definition it was read against.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-preview.6 76 10/11/2026
1.0.0-preview.5 102 9/19/2026
1.0.0-preview.4 89 9/17/2026
1.0.0-preview.3 141 9/7/2026
1.0.0-preview.2 119 9/6/2026
1.0.0-preview.1 103 9/6/2026