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

The noyalib-serde-yaml Manual

The drop-in serde_yaml replacement: rename the package, change zero source lines. noyalib-serde-yaml is one of the six crates of the noyalib family and releases in lockstep with the core library at the identical =0.0.X.

This manual is the rendered form of the Markdown in the repository’s docs/ directory; the repository copy is canonical. The family manual for the core library, with the user guide, migrations, and policies, is at https://sebastienrousseau.github.io/noyalib/manual/.

Pages

Install, quick start, and the full surface are in the README. The CHANGELOG is the complete release record.

The serde_yaml behavioural contract

“Drop-in replacement” is a behavioural claim, so it is held by a test suite, not a README sentence.

Provenance

The corpus began life as an evaluation harness built by a prospective adopter to decide whether noyalib could replace serde_yaml in their build (zudo-front-builder#2787). noyalib 0.0.28 diverged on 11 of its 18 cases and was rejected — the most rigorous public specification of what serde_yaml compatibility actually means. noyalib v0.0.29 vendors that corpus as its own contract suite (crates/noyalib/tests/serde_yaml_contract.rs), with every expectation captured live from serde_yaml 0.9.34+deprecated: the JSON value produced, the error Display string, and the location() line/column/index. All 18 pass.

Enforcement

  • The suite runs in the core’s cargo test --all-features — every PR, every platform in the test matrix.
  • A differential fuzzer (fuzz_serde_yaml_compat) runs the shim against the real archived serde_yaml on arbitrary inputs, hunting for divergences nobody has written down yet; confirmed legitimate differences graduate into its documented allowlist, everything else is a bug.
  • no_std compatibility of the shim is CI-enforced on wasm32 and three bare-metal targets.

Migrating from serde_yaml — the one-line version

# Cargo.toml — the whole migration:
serde_yaml = { package = "noyalib-serde-yaml", version = "=0.0.58" }

Every use serde_yaml::… in your codebase keeps compiling and behaving. That is the entire recipe; the rest of this page is what “behaving” means and where the edges are.

What is guaranteed

The shim parses under the core’s ParserConfig::serde_yaml_compat() profile and renders errors upstream-style. The 18-case contract suite in the core repository (crates/noyalib/tests/serde_yaml_contract.rs) pins — with expectations captured live from serde_yaml 0.9.34+deprecated — values, error Display text, and location() line/column/index for: anchors and aliases, merge keys, scalar and composite keys, YAML 1.1 boolean spellings, octal/binary/sexagesimal numbers, null and date scalars, Unicode/CRLF/emoji, malformed-input locations, built-in and custom tags, duplicate keys, non-finite and overflowing numbers, integer boundaries, and alias resource limits.

The edges

  • Custom tags under deserialize_any refuse with upstream’s message, but anchor the location at the value rather than the tag.
  • Error wording beyond the pinned classes may use noyalib’s own phrasing — upstream’s message set is unbounded; the classes real code pins (per the contract corpus) are matched.
  • Spec-vs-libyaml acceptance gaps exist in both directions (libyaml rejects spec-legal anchor names like &\L; empty implicit keys; etc.). noyalib follows the spec — it passes 406/406 of the official YAML test suite.

When you outgrow drop-in

The same engine offers YAML 1.2 strict resolution, byte-exact lossless editing (noyalib::cst), source spans, streaming deserialisation, and schema validation. Depend on noyalib directly (optionally with features = ["compat-serde-yaml"] to keep the shim during a gradual migration), and see the core’s docs/MIGRATION-FROM-SERDE-YAML.md for the full function-by-function mapping.

serde_yaml migration comparison

OptionSource changesMaintained engineLegacy behaviour retained
noyalib-serde-yamlNoYesYes, by documented contract
Direct noyalibYesYesOpt-in compatibility profile
Archived serde_yamlNoNoYes

Use this facade when a reversible package rename and known legacy behaviour are more important than adopting the native noyalib API immediately. Prefer direct noyalib for greenfield code. The detailed behavioural boundary and captured evidence are in the contract.

Benchmarks

The shim_overhead Criterion harness compares the compatibility profile with a direct noyalib path using the same documents.

cargo bench --bench shim_overhead

Record the CPU, operating system, Rust version, commit, input corpus, and command with results. The compatibility profile may materialize data required by legacy merge-key semantics, so compare equivalent behaviour. CI builds benchmark targets but does not gate releases on shared-runner timing.

Architecture

noyalib-serde-yaml is deliberately the smallest crate in the family. Its whole source is one line:

#![allow(unused)]
fn main() {
pub use noyalib::compat::serde_yaml::*;
}

Everything else is contract.

What the re-export gives you

noyalib::compat::serde_yaml is the core library’s behavioural serde_yaml 0.9 shim: the same function names, types, and error phrasing, pinned by an 18-case contract suite captured live from serde_yaml 0.9.34 and kept in the core repository (tests/serde_yaml_contract.rs). Publishing that module under a package name lets Cargo’s package = rename substitute it for serde_yaml without a single source change downstream.

Why a separate crate

A dependency rename can only target a crate on the registry with the right name. The core cannot be renamed, so a one-line crate is the smallest artefact that makes the migration a Cargo.toml edit. Keeping it empty of logic means there is nothing here to drift from the core.

Features

The crate enables exactly std and compat-serde-yaml on the core, with default features off, so it pulls nothing the shim does not need.

Testing

tests/drop_in.rs exercises the surface as a renamed dependency would see it. docs/CONTRACT.md records the behavioural guarantees. Two libFuzzer targets in fuzz/ feed arbitrary input through the shim’s from_str and a to_string round trip, and CI replays the seed corpus on every push. The README’s Rust block is compiled in CI against this crate under the rename.

Lockstep

The crate pins noyalib at the identical =0.0.X and releases with it (core ADR-0005). The exact pin is the compatibility contract: the behaviour a user gets is the behaviour that version of the core tested.

Engineering policies

Version and dependency policy

noyalib-serde-yaml releases in strict lockstep with noyalib. Version 0.0.58 pins the core at exactly =0.0.58. Release work uses feat/v0.0.58; each subsequent iteration increments exactly 0.0.1.

Minimum Rust version

The minimum supported Rust version is 1.89.0, declared in the manifest and verified by CI. A floor increase is a breaking-axis change and requires a changelog entry.

Compatibility

The documented serde_yaml behaviour, error display and location shapes, package-rename path, and exported names are public. Production manifests must not contain a local path override. During 0.0.x, the patch component is the breaking axis.

Family-wide policies live in the core POLICIES.md.

Repository standard compliance grade

Assessment date: 2026-09-20. Source rubric: /Users/seb/Code/REPO-STANDARD.md. This is a conservative static assessment of checked-in evidence, not a substitute for successful CI runs.

Result

14/24 signals (58%). README template: pass. Strict candidate tier: L1.

CategorySignalsSummary
Identity and README3/3Canonical structure and compatibility policy present
Documentation2/3Manual, architecture, migration, and contract docs present
Build and install UX1/3Native Cargo build present; CLI-generation requirements are not applicable evidence
Releases and binaries2/3Registry automation present; complete L3 evidence remains open
Packaging1/3Registry distribution only; wider tracking and reproducibility are open
CI quality gates2/3Matrix and coverage signals present
Supply chain2/3Foundation controls present; advisory-audit detection remains open
Community1/3Foundation governance present; L2 template and docs-lint signals are open

The cumulative tier remains L1. Priority work is distribution tracking, reproducible-build evidence, explicit advisory-audit CI, and audit-visible community templates and markdown lint enforcement.

Detailed README reference

Noyalib logo

noyalib-serde-yaml

Drop-in serde_yaml replacement backed by noyalib — rename the package in Cargo.toml, change zero source lines, keep the behaviour your code depends on.

Build Crates.io Docs.rs lib.rs OpenSSF Scorecard OpenSSF Best Practices


Contents


Install

# Cargo.toml — the whole migration:
serde_yaml = { package = "noyalib-serde-yaml", version = "=0.0.58" }

Every use serde_yaml::… in your codebase keeps compiling — Cargo’s package rename substitutes this crate under the old name, so the diff is one manifest line.

MSRV: Rust 1.89.0, the floor shared across the noyalib family and the maintainer’s other Rust crates (noyalib ADR-0014). Releases ship in strict lockstep with the core at the identical =0.0.X (ADR-0005); the exact pin is the compatibility contract.

Requirements

  • Rust 1.89.0 or newer to build from source: rust-version in the manifest, enforced by the msrv-core CI job on every push.
  • Any tier-1 platform. CI runs the tests on Linux, macOS, and Windows with the stable, beta, and nightly toolchains; stable is the gate, beta and nightly are early warning.
  • The matching core. This crate pins noyalib at the identical =0.0.X and releases in lockstep with it; Cargo resolves that pin for you.
  • serde 1, as with serde_yaml; nothing else changes in your manifest beyond the package rename.

Quick Start

This is serde_yaml 0.9 code, byte for byte — only the Cargo.toml line above has changed:

#![allow(unused)]
fn main() {
use serde_yaml::{from_str, to_string};

#[derive(serde::Serialize, serde::Deserialize)]
struct Config { name: String, port: u16 }

let cfg: Config = from_str("name: gateway\nport: 8443\n")?;
let out = to_string(&cfg)?;

let err = from_str::<serde_yaml::Value>("a: [unclosed").unwrap_err();
let loc = err.location().unwrap(); // 1-based line/column, 0-based index
}

Why this approach?

serde_yaml 0.9 was archived in March 2024; advisories and spec corrections no longer flow into it. Every successor asks you to edit call sites — and then behaves differently anyway. This crate takes the opposite contract on both counts:

  • Zero source changes. The migration is a package rename in one manifest line, reversible with the same line.
  • Behaviour, pinned. Since noyalib v0.0.29 the shim this crate re-exports is behavioural: it parses under the core’s serde_yaml_compat() profile and renders errors upstream-style, held by an 18-case contract suite whose expectations were captured live from serde_yaml 0.9.34+deprecated — values, error Display text, and location() pins.
  • No dead branch. The archived crate and its advisory chain are never dependencies — every type is noyalib-native under the serde_yaml names, pure Rust, #![forbid(unsafe_code)].

Behavioural parity

serde_yaml 0.9 behaviourthis crate
<<: stays a literal key (alias value resolved)✅
0123 is a string, 0b11 is 3✅
1e999 stays the string "1e999"✅
u64::MAX keeps full precision; one past it errors JSON number out of range✅
[a, b]: v errors invalid type: sequence, expected a string key at 1:1:0✅
alias bombs error repetition limit exceeded (jumps ≤ events × 100)✅
libyaml error phrasing and end-of-input location convention✅ pinned classes
Error::location() → 1-based line/column, 0-based index✅

One documented partial: a custom tag refused under deserialize_any anchors its location at the value where upstream anchors at the tag — message and refusal semantics are exact. Full provenance and enforcement: docs/CONTRACT.md.

Examples

cargo run --example drop_in             # the rename migration end to end
cargo run --example behavioural_parity  # every headline quirk, live

Benchmarks

cargo bench   # shim path vs direct noyalib on the same documents

The shim’s profile deliberately bypasses noyalib’s streaming fast path (merge keys must materialise), so the number worth watching is how small that gap is on your documents.

Repository layout

PathWhat lives there
src/lib.rsThe whole crate: a re-export of noyalib::compat::serde_yaml
tests/drop_in.rsPre-migration serde_yaml code, compiled and run verbatim
examples/The migration and the parity quirks, runnable
benches/shim_overhead.rsShim path vs direct noyalib
docs/MIGRATION.mdThe one-line migration, its guarantees, and its edges
docs/CONTRACT.mdWhere the 18-case behavioural contract comes from

Developing against an unpublished core

Between lockstep releases the =0.0.X pin can point at a core version crates.io does not have yet. During that window a [patch.crates-io] section resolving it against the core’s release branch is committed so CI and local builds work; the patch is removed at release time (the release validation refuses to tag while it is present). To develop against your local checkout instead:

cargo test --config 'patch.crates-io.noyalib.path="../noyalib/crates/noyalib"'

When not to use noyalib-serde-yaml

  • Greenfield code. Depend on noyalib directly — YAML 1.2 strict resolution, byte-exact lossless editing (noyalib::cst), source spans, streaming deserialisation, and schema validation, without the legacy quirks this crate exists to reproduce.
  • You want the quirks gone. The same engine minus the profile is one use noyalib::… away; migrate gradually with the core’s compat-serde-yaml feature.

Documentation

The four entry points, identical across every repo in the family:

Conformance

Every push runs the official yaml-test-suite through this shim’s from_str, from the same vendored suite and the same core commit as the noyalib core: 367 of 367 cases inside the serde_yaml contract (24 multi-document cases and 15 non-scalar-key cases are refused the way the original refused them). A configuration that uses most of YAML at once (tests/fixtures/ultra-complex/) parses to exactly its expected JSON. Details and the family table: noyalib.com/conformance.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.