Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Codecs & Determinism

PurRDF ships first-party parsers and serializers — no wrapped third-party codec — for seven formats:

FormatMedia typeStar-capable
Turtletext/turtleyes
TriGapplication/trigyes
N-Triplesapplication/n-triplesyes
N-Quadsapplication/n-quadsyes
RDF/XMLapplication/rdf+xmlno
JSON-LD (star)application/ld+jsonyes
YAML-LDapplication/ld+yamlyes

They live in purrdf-rdf, one layer above the kernel, and are reachable through the umbrella crate:

use purrdf::{parse_dataset, serialize_dataset, SerializeGraph};

let turtle = br#"
    @prefix ex: <https://example.org/> .
    ex:cat ex:says "meow" .
"#;

// Parse into the frozen, value-interned RDF 1.2 dataset IR.
let ds = parse_dataset(turtle, "text/turtle", None).expect("valid Turtle");
assert_eq!(ds.quad_count(), 1);

// Serialize back out through any native codec — byte-deterministic output.
let nq = serialize_dataset(&ds, "application/n-quads", SerializeGraph::Dataset)
    .expect("serializes");

Open Knowledge Format bundles

The native OKF codec maps caller-profiled RDF 1.2 datasets to agent-facing Markdown files with YAML frontmatter and lifts them back through the RDF event seam. OKF is an in-memory bundle API rather than another media type: callers choose how to store the files, so the same code remains deterministic and wasm-clean.

OkfConfig::new requires the vocabulary namespace, document base IRI, and recognized frontmatter keys. There is no built-in ontology or namespace. Use lift_okf_bundle to drive an RdfEventSink, or write_okf_bundle (backed by OkfWriter, an RdfDatasetVisitor) to project a frozen dataset. Both directions always return a loss ledger. A lossless profile yields an empty ledger; named graphs, non-profile/OWL rows, and unrelated reifier or annotation rows are pinpointed explicitly when writing.

Byte determinism

Every serializer is byte-deterministic: the same dataset always produces the same bytes, on every platform and in every language binding. This is a hard workspace invariant, not a best effort — no iteration-order, time, or RNG dependence is allowed in any output path (hashers are fixed-key ahash for exactly this reason), and golden-file tests pin the emitted bytes.

Determinism is what makes the rest of the toolkit composable: content addressing in GTS and the slice catalog, diffable serializations in review, and cross-language conformance vectors that can be compared byte-for-byte.

Diagnostics, not partial parses

Malformed input is a typed RdfDiagnostic with a source location where the codec can provide one — never a silent partial parse. Parsing can optionally record a source-position span table for richer diagnostics. Diagnostics stay structured (SARIF-free) in the core; render them as byte-deterministic SARIF 2.1.0 for editors and CI with purrdf-validate (see SHACL).

Lossy projections are loud

RDF 1.2 statement-level data (triple terms, reifier bindings, annotations) survives every star-capable round-trip. Serializing into a star-incapable projection drops that layer loudly: the realized drop count is handed to the machine-readable loss ledger (generated/rdf-loss-matrix.json) rather than disappearing. The same discipline applies at the SPARQL results boundary (Result Formats) and the RDF↔GTS boundary.

The succinct pack codec

Alongside the text codecs above, purrdf-core ships a binary codec for a different job: a read-only, query-the-compressed-form encoding of a whole dataset for large-scale reference bundles, not an interchange format with a media type. PackBuilder::build_bytes(&dataset) writes a self-contained, byte-deterministic pack — a value dictionary, graph-partitioned succinct bitmap-triples, and RDF 1.2 side-tables (reifier bindings, statement annotations) — into one Vec<u8>. PackView::from_bytes(&[u8]) opens it zero-copy over a borrowed slice and answers pattern queries directly against the packed bytes, with no decompression or materialization step first.

Reach for a pack when a dataset is done changing and needs to be distributed, archived, or served at a scale where re-parsing text on every load is too slow: RDF 1.2 (named graphs, quoted triples, reifiers, annotations) is fully supported, and verify_pack independently recomputes the dataset’s RDFC-1.0 digest from the pack’s own decoded contents — a certified read-only projection, not merely a compressed file. The library never memory-maps a pack itself (every published crate stays wasm32-unknown-unknown-clean); a native consumer that wants a durable, larger-than-heap tier mmaps the file and hands PackView::from_bytes the resulting borrowed slice. See the “Pack backend” section of the backend contract for the full contract.

The columnar Parquet codec

purrdf::columnar exposes the bidirectional SQL/DataFrame interchange path. It maps any DatasetView plus a content-addressed blob store to five standard Parquet files (terms, quads, reifiers, annotations, and blobs) and reads that exact profile back without Arrow or a general Parquet runtime. The mapping retains RDF 1.2 triple terms, reifiers, annotations, graph scope, directional literals, blank-node scope, and explicitly empty named graphs.

The files are byte-deterministic and readable by engines such as DuckDB. See the normative columnar schema for every field and the deliberately narrow Parquet profile.

Conformance

The codecs are gated by the W3C rdf-tests syntax corpus, vendored and frozen in-repo — 250/250 round-trip cases across N-Quads, N-Triples, RDF/XML, TriG, and Turtle at the time of writing. The live scoreboard is docs/CONFORMANCE.md.

  • Canonicalization & Diff — when you need a canonical serialization rather than just a deterministic one.
  • The Interned Dataset IR — what the text codecs parse into, and the DatasetView read seam the pack codec implements alongside RdfDataset.