| Crate | Version | Docs | |
|---|---|---|---|
asdf-rs |
the idiomatic Rust API | ||
libasdf-rs |
the C ABI | ||
asdf-cli |
the asdf command |
||
asdf-core |
the engine | ||
asdf-yaml |
the document model |
A Rust implementation of ASDF (Advanced Scientific Data Format), providing two things from one engine:
- a drop-in replacement for libasdf, exposing the same C ABI so existing C code recompiles and relinks unchanged;
- an idiomatic Rust library, with borrowed data,
Result, iterators and nounsafe.
ASDF is a hybrid format: a YAML tree describing the data, followed by binary blocks holding it. It is the native format of the Nancy Grace Roman Space Telescope.
| Crate | What it is |
|---|---|
asdf-core |
The engine. File layout, blocks, compression, ndarray, rendering. All the behaviour lives here. |
asdf-yaml |
The ASDF YAML layer: document model, parser and emitter. |
libasdf-rs |
The C ABI. Builds libasdf.so; every entry point is panic-guarded. |
asdf-rs |
The idiomatic Rust API. Published as asdf-rs; the library is asdf, so you write use asdf::.... |
asdf-cli |
The asdf command-line tool. |
libasdf-rs and asdf-rs are both thin projections of asdf-core, so the two
public faces cannot drift apart in semantics.
[dependencies]
asdf-rs = "0.1"use asdf::{AsdfBuilder, AsdfFile};
let mut builder = AsdfBuilder::new();
builder.set_str("name", "Dennis Richie")?;
let squares: Vec<u64> = (0..100).map(|i| i * i).collect();
builder.set_array("powers/squares", &squares)?;
builder.write_to_path("out.asdf")?;
let file = AsdfFile::open("out.asdf")?;
let tree = file.tree()?.expect("a tree");
println!("{:?}", tree.get("name").and_then(|v| v.as_str()));
// Arrays read back as whatever scalar type they fit.
let values: Vec<u64> = file.read_array_of("powers/squares")?;Editing an existing file goes through edit, which carries the tree and the
blocks over so every source: N still points where it did:
use asdf::{AsdfFile, Compression};
let file = AsdfFile::open("observation.asdf")?;
let mut edited = file.edit()?;
edited.set_str("meta/observer", "M. Curie")?;
edited.recompress(Compression::Zlib).write_to_path("observation.asdf")?;The public headers are vendored from upstream, so C source compiles against them unchanged:
#include <asdf.h>
asdf_file_t *file = asdf_open(NULL);
asdf_set_string0(file, "name", "Dennis Richie");
asdf_set_int64(file, "foo", 42);
asdf_write_to(file, "out.asdf");
asdf_close(file);Extensions are separate shared libraries that register themselves with
libasdf before main and teach it new tags. They link against the C ABI, so
they work against this implementation as they do against upstream's --
libasdf-gwcs, which adds GWCS
reading, writing and evaluation, passes its whole suite here.
Their build systems find libasdf through pkg-config, which cargo does not
produce, so assemble a prefix once. One piece is generated rather than
vendored: asdf/config.h, which records the build's capabilities.
$ cargo build --release
$ PREFIX=$PWD/prefix
$ mkdir -p $PREFIX/lib/pkgconfig $PREFIX/include
$ cp -r crates/libasdf-rs/include/asdf crates/libasdf-rs/include/asdf.h $PREFIX/include/
$ cp "$(find target/release/build -name config.h -path '*asdf*' | head -1)" $PREFIX/include/asdf/
$ cp target/release/libasdf.so $PREFIX/lib/
$ sed -e "s|@PREFIX@|$PREFIX|" > $PREFIX/lib/pkgconfig/libasdf.pc <<'EOF'
prefix=@PREFIX@
libdir=${prefix}/lib
includedir=${prefix}/include
Name: libasdf
Description: ASDF C library
Version: 0.2.0
Libs: -L${libdir} -lasdf
Cflags: -I${includedir}
EOFThen build the extension against it exactly as its own README says:
$ cmake .. -DCMAKE_PREFIX_PATH=$PREFIX -DENABLE_TESTING=YES # or:
$ ./configure PKG_CONFIG_PATH=$PREFIX/lib/pkgconfigThe Version above is the upstream ABI this library implements, recorded in
SYNC_COMMIT.md -- not the crate's own version. An
extension that asks for a minimum libasdf version is asking about that one.
$ cargo build --release # builds target/release/libasdf.so
$ cargo test --workspaceTests read two external corpora when they are present, and skip with a note
when they are not. Point them somewhere other than ~/code with:
$ ASDF_STANDARD_DIR=/path/to/asdf-standard LIBASDF_DIR=/path/to/libasdf cargo testThree independent oracles already exist for this format, and all three are wired into the test suite rather than reasoned about:
- libasdf's own C test suite — the tests upstream wrote for its own
implementation, compiled against the vendored headers and linked against
the built
libasdf.so. 498 of 501 pass, across eleven of its twenty-one suites; the other ten reach into libasdf's private headers and cannot run against a different implementation by construction. This is the strongest evidence the project can produce, and every suite's pass count is pinned so it cannot drift. - The ASDF Standard's reference corpus — 105
.asdffiles paired with the YAML they should read as, across seven standard versions. Following the corpus README's procedure (inline every array, dereference aliases, compare at the value level), all 105 match exactly. - libasdf's committed command-line captures — 17 for
asdf info, 4 forasdf eventsand 3 forasdf verify-checksums, all reproduced byte for byte, ANSI styling included. - A C ABI conformance harness — real C programs compiled against the
vendored headers and linked against the built library, covering the
_Genericmacros, struct layouts, enum discriminants, a third-party extension registering beforemain, and the exported symbol namespace. Every symbol the headers declare is checked to exist: 379 of 379, read out of the preprocessed headers rather than a list kept by hand. - Differential tests against Python asdf — files written here are read by the reference implementation and vice versa, across every compression method, so the two are checked against each other rather than only against themselves. They skip when Python asdf is not installed.
YAML output is compared at the value level, never byte for byte — YAML
admits many spellings of the same value, and the standard's own corpus says
as much. The binary layer is the exception and is byte-exact by
specification. See KNOWN-DIVERGENCES.md for the deliberate differences from
upstream, each with a test pinning it.
Feature-complete against upstream libasdf, and past it in a few places.
- Every symbol upstream's headers declare is exported and implemented.
- All seven core-schema extensions, and the extension registry third-party
extensions register into before
main. - Both the read and the write path, including compression (zlib, bzip2, lz4), block indices, checksums and inline array storage.
- The
asdfcommand-line tool:info,dd,eventsandverify-checksums, with upstream's options and output. - Past parity, in
asdf-coreand the idiomatic API rather than the C surface: external array sources (exploded form), thecore/complextag, and reading inline array data back out.
Not implemented: schema validation against the ASDF Standard's JSON schemas, which upstream libasdf does not do either.
docs/DEVELOPING.md |
How the crates fit together, the gates, and the conventions of the C ABI layer. |
CONTRIBUTING.md |
What a change needs before it can land. |
CONFORMANCE.md |
The ABI baseline, every gate, and what Miri found. |
KNOWN-DIVERGENCES.md |
Deliberate differences from upstream libasdf and from Python asdf, each with the test that pins it. |
SYNC_COMMIT.md |
The libasdf commit this implementation is synchronised with. |
docs/UPSTREAM-SYNC.md |
What moving that commit involves. |
docs/SECURITY-REVIEW.md |
How untrusted files are handled, and the seven findings that changed it. |
docs/FUZZING.md |
The cargo-fuzz targets, and what they found. |
docs/PERFORMANCE.md |
Benchmarks, how they are run, and how this compares to the reference implementation. |
CHANGELOG.md |
What has changed. |
MIT, for everything written here — see LICENSE.
One directory is third-party: crates/libasdf-rs/include/ holds libasdf's public
headers, copied verbatim, and stays on its own BSD-3-Clause terms. That licence
travels with the files in crates/libasdf-rs/include/LICENSE, and the
libasdf-rs crate declares itself MIT AND BSD-3-Clause because it ships them.
No upstream libasdf source is used; the implementation is independent.