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.
Contents
- Install — the one-line migration
- Requirements — toolchain floor, platforms, the core pin
- Quick Start — pre-migration code, unchanged
- Why this approach? — design rationale
- Behavioural parity — what “drop-in” guarantees
- Examples — runnable demonstrations
- Benchmarks — the cost of compatibility
- Repository layout
- Developing against an unpublished core
- When not to use noyalib-serde-yaml
- Documentation
- License
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-versionin the manifest, enforced by themsrv-coreCI 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
noyalibat the identical=0.0.Xand releases in lockstep with it; Cargo resolves that pin for you. serde1, as withserde_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 fromserde_yaml 0.9.34+deprecated— values, errorDisplaytext, andlocation()pins. - No dead branch. The archived crate and its advisory chain are
never dependencies — every type is noyalib-native under the
serde_yamlnames, pure Rust,#![forbid(unsafe_code)].
Behavioural parity
serde_yaml 0.9 behaviour | this 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
| Path | What lives there |
|---|---|
src/lib.rs | The whole crate: a re-export of noyalib::compat::serde_yaml |
tests/drop_in.rs | Pre-migration serde_yaml code, compiled and run verbatim |
examples/ | The migration and the parity quirks, runnable |
benches/shim_overhead.rs | Shim path vs direct noyalib |
docs/MIGRATION.md | The one-line migration, its guarantees, and its edges |
docs/CONTRACT.md | Where 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
noyalibdirectly — 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’scompat-serde-yamlfeature.
Documentation
The four entry points, identical across every repo in the family:
-
User Manual — this crate’s rendered book: its guides, architecture, and release notes; the family manual for the core library is at https://sebastienrousseau.github.io/noyalib/manual/
-
API reference — rustdoc on docs.rs
-
Developer docs — this repo’s dev entry point, pointing at the family guide
-
Ecosystem map — the six crates, the lockstep model, the scorecard
-
Migration guide —
docs/MIGRATION.md, and the core repository’sMIGRATION-FROM-SERDE-YAML.mdfor the function-by-function mapping. -
Behavioural contract —
docs/CONTRACT.md. -
API reference — docs.rs/noyalib-serde-yaml.
-
Engineering policies — the core repository’s
POLICIES.md.
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.