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

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.