Contents

Getting started

The noyalib ecosystem (library + four satellite crates)

Library reference

Operational


Install

As a Rust library (crates.io)

[dependencies]
noyalib = "0.0.15"

As a CLI tool

The noyafmt and noyavalidate binaries ship from the noya-cli companion crate (the noyalib library crate itself contains no binaries — the split keeps clap + miette + validate-schema out of the library's dependency graph for downstream embedders).

Channel Install
Cargo (crates.io) cargo install noya-cli --locked
Cargo (from source) cargo install --locked --path crates/noya-cli
Homebrew (personal tap) brew tap sebastienrousseau/tap && brew install noyalib
Arch Linux (AUR) yay -S noyalib-bin (binary) or yay -S noyalib (source)
Scoop (Windows) scoop bucket add sebastienrousseau https://github.com/sebastienrousseau/scoop-bucket && scoop install noyalib
Nix / NixOS nix run github:sebastienrousseau/noyalib
Container (GHCR) docker run --rm ghcr.io/sebastienrousseau/noyafmt:latest --version
npm (WASM) npm install @sebastienrousseau/noyalib-wasm
npm (MCP) npx @sebastienrousseau/noyalib-mcp (no Rust toolchain needed)
VS Code search noyalib in the Marketplace
Open VSX search noyalib in open-vsx.org

cargo install noya-cli --locked builds both binaries by default (via the noyavalidate Cargo feature). To install only the formatter and skip the schema-validation toolchain, use cargo install noya-cli --locked --no-default-features --features noyafmt.

GitHub Releases additionally publish pre-built tarballs for Linux (gnu + musl), macOS (Intel + Apple Silicon + universal), and Windows (x86_64, i686, aarch64). Each archive ships with the binaries, man pages, shell completions, license bundle, and a cosign keyless signature + SLSA L3 attestation.

See pkg/VERIFY.md for verification commands and pkg/PUBLISH.md for the per-channel maintainer runbook.

no_std support

[dependencies]
noyalib = { version = "0.0.15", default-features = false }

Requires alloc. Core data binding (from_str, to_string, Value, schemas) and the streaming deserializer all compile and run without the standard library. from_reader, to_writer, the Spanned<T> deserialization helper (which uses thread-local storage), and the CST module require the std feature, which is enabled by default.

Build from source

git clone https://github.com/sebastienrousseau/noyalib.git
cd noyalib
make          # check + clippy + test

MSRV by crate. Each workspace crate carries its own rust-version; CI's msrv-per-crate job (Phase 7) gates each crate independently so a satellite never silently breaks downstream users pinned to the core's floor.

Crate MSRV Why
noyalib (core lib) 1.85.0 The committed floor since v0.0.5 (edition 2024). Enforced by the dedicated MSRV CI job.
noyalib-mcp 1.85.0 Same floor; small dep tree, no transitives requiring a higher edition.
noya-cli (binaries) 1.85.0 clap_builder 4.6 (a transitive of clap = "4.5") ships in edition 2024.
noyalib-lsp 1.85.0 LSP transport-stack transitives (litemap, uuid) require recent stables.

Optional core-lib features pull in ergonomics deps that have themselves bumped past 1.85 — miette → backtrace 1.82+, garde → 1.84+, validate-schema / figment → ICU chain 1.86+, parallel → rayon-core 1.80+. Use those with a current stable toolchain; the core lib stays buildable on the Ubuntu 24.04 LTS rustc-1.85 floor.

rust-toolchain.toml itself selects stable for local development; the 1.85.0 floor on the core surface is enforced by the dedicated msrv-1-85-core CI job (Ubuntu, no-default-features + default-features build paths).

Cargo features

All optional integrations are off by default. Enable only what the application needs.

Feature Pulls in Adds Documented in
std (default) from_reader, to_writer, Spanned<T>, CST module Install
miette miette 7 Rich terminal diagnostics with source spans Library Usage, examples/diagnostic.rs
ariadne ariadne Alternative ariadne-rendered diagnostics examples/ariadne_diagnostic.rs
include $include directive resolution via an in-memory resolver examples/include_directive.rs
include_fs include + std Filesystem include resolver (SafeFileResolver) examples/include_directive.rs
schema schemars, serde_json JsonSchema derive + schema_for::<T>(). Downstream callers that derive JsonSchema must add schemars = "1.2" to their own Cargo.toml — the proc-macro emits ::schemars::* paths that need to resolve in the call-site dep graph. Capabilities in 0.0.1
validate-schema schema + jsonschema validate_against_schema, coerce_to_schema Governance: schema-driven autofix
figment figment 0.10 noyalib::figment::Yaml provider examples/figment.rs
garde garde 0.22 Validated<T> wrapper examples/validation_garde.rs
validator validator 0.19 ValidatedValidator<T> wrapper examples/validation_validator.rs
robotics Degrees, Radians, StrictFloat newtypes examples/robotics_polymorphism.rs
parallel rayon 1.10 noyalib::parallel::parse<T> for ----separated streams Benchmarks
recovery noyalib::recovery::parse_lenient — best-effort tree + error list for LSP / IDE half-typed documents examples/recovery_lenient.rs, benches/v006_features.rs
sval sval 2 impl sval::Value for Value / Number / Mapping / MappingAny / TaggedValue, noyalib::sval_adapter::to_sval_writer examples/sval_streaming.rs, benches/v006_features.rs
tokio tokio, tokio-util, bytes noyalib::tokio_async::from_async_reader / from_async_reader_multi and YamlDecoder codec for tokio_util::codec::Framed pipelines examples/tokio_async_reader.rs, benches/v006_features.rs
simd Forward-compat no-op — noyalib::simd::* is always available and the parser hot path uses it unconditionally Benchmarks
nightly-simd simd (nightly toolchain) core::simd-backed StructuralIter (32-byte chunks) Benchmarks
compat-serde-yaml noyalib::compat::serde_yaml shim for migration When not to use noyalib
lossless-u64 Number::Unsigned(u64) plus opt-in parser/serializer config for scalars in (i64::MAX, u64::MAX] doc/adr/0004-lossless-u64-integers.md, examples/lossless_u64.rs, benches/lossless_u64.rs
compare-saphyr serde-saphyr (dev only) Cross-library bench comparison arms benches/comparison.rs
wasm-opt Build-time flag opting the noyalib-wasm bundle into wasm-opt size passes (no API surface) sebastienrousseau/noyalib-wasm
noyavalidate std + miette + validate-schema Enables the schema-validation surface the noyavalidate CLI is built on doc/USER-GUIDE.md §11
# Example: rich diagnostics + schema validation
[dependencies]
noyalib = { version = "0.0.15", features = ["miette", "validate-schema"] }

Optional features: lossless-u64 preserves YAML integer scalars above i64::MAX as Number::Unsigned(u64) instead of lossy f64 widening — useful for distributed-system IDs, content hashes, and timestamp fields. Enable the Cargo feature, then opt in at runtime with ParserConfig::lossless_u64_integers(true) and SerializerConfig::lossless_u64_integers(true). See doc/adr/0004-lossless-u64-integers.md for rationale and migration notes.


Quick Start

use noyalib::{from_str, to_string};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Config {
    name: String,
    port: u16,
    features: Vec<String>,
}

fn main() -> Result<(), noyalib::Error> {
    let yaml = "
name: myapp
port: 8080
features:
  - auth
  - api
";

    let config: Config = from_str(yaml)?;
    let output = to_string(&config)?;
    let roundtrip: Config = from_str(&output)?;
    assert_eq!(config, roundtrip);

    Ok(())
}

The noyalib ecosystem

Five crates ship from this workspace. The library is the core; the four satellites wrap it for specific delivery surfaces.

Crate What it is Use case
noyalib Library — YAML 1.2 parser, serializer, lossless CST, JSON Schema validator Embed YAML support in any Rust binary or library.
noya-cli (own repo) Two binaries: noyafmt (formatter), noyavalidate (schema validator + autofixer) CI gates, pre-commit hooks, ad-hoc command-line use.
noyalib-lsp (own repo) Language Server Protocol server Editor integration — VS Code, Neovim, Helix, Emacs, Zed, Sublime, IntelliJ.
noyalib-mcp (own repo) Model Context Protocol server LLM agent tooling — Claude Desktop, Cursor, Continue.dev, Zed assistant, mcp.run.
noyalib-wasm (own repo) wasm-bindgen wrapper around the library Browser, Node, Cloudflare Workers, Deno, any WASM-capable host.

Install the binaries

# CLI tools (noyafmt + noyavalidate)
cargo install noya-cli

# LSP server
cargo install noyalib-lsp

# MCP server
cargo install noyalib-mcp

# WASM bundle
npm install @sebastienrousseau/noyalib-wasm

Per-crate READMEs cover the surface specific to each artifact:

Per-host quick links

If you use… Drop-in config
VS Code / JetBrains / Neovim / Helix / Emacs / Zed / Sublime editor configs in noyalib-lsp/examples/
Claude Desktop / Cursor / Continue.dev / Zed assistant / hosted MCP client configs (noyalib-mcp repo)
GitHub Actions / pre-commit / Helm / Compose / pyproject-adjacent YAML validation gates in noya-cli/examples/
Vite / Webpack / Next.js / Cloudflare Workers / Deno / Bun bundling guide (noyalib-wasm repo)

The rest of this README covers the library surface (noyalib itself). For the satellite crates, jump straight to their READMEs above.


One-minute migration from serde_yaml (and the wider ecosystem)

Most call sites are mechanical to update. The full guide — covering serde_yaml 0.9 plus every actively-published fork and adjacent crate — is doc/MIGRATION-FROM-SERDE-YAML.md. The headline mapping for serde_yaml 0.9 is below; the same guide has per-crate sections for serde_yml, yaml_serde, serde-yaml-ng, serde-norway, serde-yaml-bw, serde-saphyr, and yaml-spanned with verified function tables for each.

-[dependencies]
-serde_yaml = "0.9"
+[dependencies]
+noyalib = "0.0.15"
-use serde_yaml::Value;
-let v: Value = serde_yaml::from_str(input)?;
-let s        = serde_yaml::to_string(&v)?;
+use noyalib::Value;
+let v: Value = noyalib::from_str(input)?;
+let s        = noyalib::to_string(&v)?;
serde_yaml 0.9 noyalib
serde_yaml::from_str::<T> noyalib::from_str::<T>
serde_yaml::from_slice::<T> noyalib::from_slice::<T>
serde_yaml::from_reader::<R, T> noyalib::from_reader::<R, T>
serde_yaml::to_string noyalib::to_string
serde_yaml::to_writer noyalib::to_writer
serde_yaml::to_value noyalib::to_value
serde_yaml::Value noyalib::Value (adds a 7th Tagged variant)
serde_yaml::Mapping noyalib::Mapping
serde_yaml::Number noyalib::Number
serde_yaml::Error noyalib::Error
serde_yaml::with::singleton_map* noyalib::with::singleton_map*
(n/a) noyalib::from_str_strict::<T> — error on unknown keys
(n/a) noyalib::Spanned<T> — source-location wrapper
(n/a) noyalib::cst::Document — lossless byte-faithful edits

If your call sites can't change at all, enable features = ["compat-serde-yaml"] and replace use serde_yaml with use noyalib::compat::serde_yaml — every type is noyalib-native, no transitive dep on the archived upstream.

Coming from a different YAML crate?

Each crate has a standalone migration guide with TL;DR diff, function-mapping table, behavioural notes, and a checklist. Crates.io state verified 2026-05-08:

Crate Version Drop-in for serde_yaml? Migration guide
serde_yml 0.0.12 (archived 2025-09) mostly MIGRATION-FROM-SERDE-YML.md
yaml_serde 0.10.4 yes (Cargo package = rename) MIGRATION-FROM-YAML-SERDE.md
serde-yaml-ng 0.10.0 yes MIGRATION-FROM-SERDE-YAML-NG.md
serde-norway 0.9.42 yes MIGRATION-FROM-SERDE-NORWAY.md
serde-yaml-bw 2.5.6 no (breaking 2.x; 8-variant Value with Alias) MIGRATION-FROM-SERDE-YAML-BW.md
serde-saphyr 0.0.26 no (no Value DOM, streaming-only) MIGRATION-FROM-SERDE-SAPHYR.md
yaml-spanned 0.0.3 no (parser-only, no to_string) MIGRATION-FROM-YAML-SPANNED.md

The umbrella index is doc/MIGRATION.md — start there if you're not sure which guide applies, or pick the row above.

The three behavioural differences worth knowing about (YAML 1.2 strict booleans, Tagged variant, multi-doc API): MIGRATION-FROM-SERDE-YAML.md covers each in detail.


Why this approach?

noyalib targets the niche serde_yaml / serde_yml / libyml occupy — read YAML into typed Rust structs, write Rust structs back as YAML — and is written from scratch against the YAML 1.2 spec. The implementation runs the official YAML test suite to 100% strict compliance — 406/406 attempted cases pass, 0 failures, 0 skips (rebuilt on every CI run via tests/yaml_compliance_report.rs). It is not a fork of serde_yaml; the parser, scanner, serialiser, and CST are independent code.

Two architectural choices motivate the rewrite:

  1. Streaming-first deserialise. The default from_str<T> path walks parser events directly into the typed target. The serde_yaml-shaped pattern is parse → Value → T::deserialize(&Value), which allocates every key, scalar, and nested mapping into an intermediate AST that the typed target then throws away. noyalib bypasses that AST when the caller asked for a typed T. The dynamic Value tree is still available when callers want it, but it is no longer in the hot path.

  2. #![forbid(unsafe_code)] at the workspace root. There is no FFI to a C library, no raw-pointer dereferences, and no unsafe blocks in the parser, scanner, formatter, or CST. CI enforces the attribute on every push. Most popular Rust YAML crates wrap libyaml via C-FFI; the unsafe blocks involved are usually well-vetted, but their existence makes a security-conscious downstream audit meaningfully harder.

A few features built on top of those choices:

  • SIMD-accelerated structural discovery. Stable Rust dispatches to memchr (SSE2 / NEON) for arity-1/2/3 needles and SWAR for arity-4+. With nightly-simd on, the structural-bitmask scanner widens to a 32-byte Simd<u8, 32> chunk and walks delimiters via mask.trailing_zeros() — the same shape that powers simdjson.
  • SWAR decimal parsing. The plain-scalar integer resolver folds 8 ASCII digits per u64 cycle via three pair-wise multiply-add phases. ~2× faster than <i64 as FromStr>::from_str on big numbers.
  • Lossless CST. A side-table green tree (noyalib::cst::Document) reproduces the source byte-for-byte and supports surgical edits. doc.set("version", "0.0.2") rewrites only the touched span; comments, indentation, and sibling entries are left alone.

The default profile compiles eight crates: five unconditional (serde, indexmap, rustc-hash, memchr, smallvec) plus three default-on but optional (itoa via fast-int, ryu via fast-float, serde_ignored via strict-deserialise). Disabling the default features drops the graph to the five unconditional crates. No archived or unmaintained crate appears in the graphserde_yaml 0.9 (archived), libyaml (C-FFI), and thiserror are all absent. cargo audit, cargo deny, and cargo vet are CI gates on every push.


Capabilities in 0.0.1

The 0.0.1 release covers a complete YAML 1.2 stack. See CHANGELOG.md for the detailed inventory; the table below groups the inventory by capability theme.

Theme Headline deliverables
Spec compliance YAML 1.2 official test suite at 100% strict (406/406 attempted, 0 failures, 0 skips — verified by tests/yaml_compliance_report.rs); YAML 1.1 opt-in compatibility for the "Norway problem"; multi-document streams
Migration from serde_yaml compat-serde-yaml feature with name-for-name re-exports; From/TryFrom parity for Value/Mapping/Number; Document::validate; comment-aware reads via load_comments
Binary scalars First-class !!binary tag; RFC 4648 base64 round-trip with serde_bytes::ByteBuf/Bytes; non-UTF-8 payloads supported
Flatten guard Spanned<Value> in #[serde(flatten)] returns an actionable error pointing at the working alternative
Lossless editing Side-table CST with byte-faithful round-trip; Document::entry(path) chainable mutable handle (12 methods); automatic indent detection (2/3/4-space)
Anchor management Document::anchors() / aliases() / aliases_of(name); materialise_alias_at(byte_pos) and materialise_aliases_of(name) for breaking aliases
Schema codegen schema feature: JsonSchema derive re-export; schema_for::<T>() and schema_for_yaml::<T>(); honours #[doc], #[serde(default)], #[serde(rename)]
Schema validation validate-schema feature: validate_against_schema(value, schema); aggregated violations with RFC 6901 paths
Tooling noyavalidate (with --schema and --fix), noyafmt, noyalib-mcp, noyalib-wasm
Performance noyalib::simd primitives — find_any_of, clean_prefix_len, ByteBitmap; parser hot path integrated; ~58× and ~5.4× over byte-by-byte at arities 3 and 8
Supply chain SLSA L3 provenance, sigstore signing, OpenSSF Scorecard, REUSE.software 3.3 compliance, signed commits, cargo-deny / cargo-vet / cargo-semver-checks gates, differential and soak fuzz

Two APIs, one parser

noyalib exposes two complementary surfaces over the same scanner and strictness rules:

  • Data bindingfrom_str, to_string, Value, StreamingDeserializer, BorrowedValue. Read YAML into typed Rust data, write Rust data back out. The round-trip travels through a Value/struct, so comments and exact whitespace are not preserved. Use this for config loaders, RPC payloads, and the 95% of YAML workloads that just want data.
  • Tooling / automationnoyalib::cst::parse_document, parse_stream, and the Document handle. Read YAML into a side-table CST that reproduces the source byte-for-byte, then run targeted edits like doc.set("version", "0.0.2") — only the touched span is rewritten, every comment and the original indentation is left alone. Use this for Renovate-style version bumps, manifest patchers, formatters, and schema-driven linters. See examples/lossless_edit.rs.

The CST also lets a tool break aliases by inlining the anchored content at every reference site — useful when a manifest needs to become self-contained before being shipped to a system that does not resolve YAML aliases:

use noyalib::cst::parse_document;

let yaml = "a: &shared 7\nb: *shared\nc: *shared\n";
let mut doc = parse_document(yaml).unwrap();

// Replace every `*shared` reference with the bytes of the
// anchored value. The `&shared` declaration stays in place.
let n = doc.materialise_aliases_of("shared").unwrap();
assert_eq!(n, 2);
assert!(!doc.to_string().contains('*'));

Document::materialise_alias_at(byte_pos) is the single-site variant for callers that already know the alias's source position.


Ecosystem comparison

noyalib is the only Rust YAML implementation that passes all 406 active YAML 1.2 Test Suite cases under strict comparison and ships a lossless CST, native LSP, MCP server, WASM bundle, JSON Schema validator, and schema-driven autofix in one workspace.

The full feature matrix — every row, every column, with the reading-the-table notes — lives at doc/COMPARISON.md so the README stays fast to scan.

Quick orientation:

Crate Drop-in for serde_yaml? Key gap vs noyalib
serde_yaml 0.9 (archived 2024-03) unmaintained
serde_yml, serde-yaml-ng, serde-norway, yaml_serde yes (path rename) no streaming deser, no CST, no LSP / MCP / WASM, no JSON Schema
serde-yaml-bw no (breaking 2.x) no LSP / MCP / WASM, no JSON Schema
serde-saphyr no (no Value DOM) no Value DOM, no CST, no LSP / MCP / WASM
yaml-spanned no (read-only) no serializer, no CST, no LSP / MCP / WASM

Per-crate migration guides at doc/MIGRATION.md.


Benchmarks

Headline numbers (Apple M4 / aarch64, Rust 1.95 stable, --release with LTO=fat, codegen-units=1, panic=abort, criterion --warm-up-time 2 --measurement-time 4):

Fixture noyalib vs serde_yaml_ng vs yaml-rust2 vs yaml-spanned vs serde-saphyr
Deserialise simple (3 fields) 1.40 µs 1.84× 1.36× 1.69× 2.00×
Deserialise nested (20 fields) 9.66 µs 1.55× 1.25× 1.60× 1.76×
Deserialise large_list (500 items) 920 µs 1.42× 1.19× 1.38× 1.69×
Deserialise github_actions (deep + comments) 46.4 µs 1.66× 1.25× 1.74× 1.73×
Deserialise k8s multi-document 85.1 µs 1.42× 1.11×
Typed deserialise simple (streaming) 1.22 µs 1.72×
Typed deserialise nested (streaming) 7.08 µs 1.55×
Serialise simple 290 ns 4.34×
Serialise nested 2.25 µs 3.00×
Round-trip nested 12.0 µs 1.83×
Structural-discovery (1 MiB, nightly-SIMD) 311 µs 9.2× over memchr loop
SWAR decimal parse (i64::MAX) 9.75 ns 2.5× over stdlib
Lossless-u64 parser resolution (benches/lossless_u64.rs) see harness knob on vs off for large unsigned scalars

(serde_yml was previously a comparison column — retired in v0.0.6 since RUSTSEC-2025-0068 flagged it unsound + unmaintained; the bench arm was dropped so the OpenSSF Scorecard Vulnerabilities check stays clean. The headline ratios above are unchanged for the remaining four competitors.)

noyalib is faster than every other pure-Rust YAML library on every deserialize fixture measured. Speedup ranges across the four competitors above: 1.69×–2.00× vs serde-saphyr, 1.42×–1.84× vs serde_yaml_ng, 1.38×–1.74× vs yaml-spanned, 1.11×–1.36× vs yaml-rust2. Serialize is 3.00×–4.34× ahead of serde_yaml_ng.

The narrowest gap is yaml-rust2, which doesn't carry the Spanned<T> plumbing, the per-tag Cow<'a, str> propagation, or the Value::Tagged preservation that noyalib does — closing the remaining gap to ≥ 2× over yaml-rust2 is a separate effort because the levers needed (CompactString keys in Mapping, bump-arena event lifetimes, eliminating the Value AST on the typed path) require SemVer-breaking refactors.

cargo xtask pgo-build runs the LLVM profile-guided optimisation pipeline and adds 5–15% on top of the numbers above; recommended for production deployments.

The full breakdown — every workload, every comparison library, the SWAR pipeline explanation, parallel multi-doc scaling, and the project-metrics table — lives at doc/BENCHMARKS.md.

Algorithmic-complexity guarantees (O(n) parser, O(d) stack, etc.) live in POLICIES.md §4.


Features

Serde from_str, from_slice, from_reader, to_string, to_writer, to_fmt_writer -- all with _with_config variants. to_value, from_value for Value conversion. Multi-document: load_all, load_all_as, to_string_multi. Streaming deserializer bypasses Value AST for typed targets.
Values 7-variant Value enum: Null, Bool, Number, String, Sequence, Mapping, Tagged. Path traversal via get_path("server.host"). Path queries via query("items[*].name") with wildcards (*) and recursive descent (..). Deep merge via merge() and merge_concat(). MappingAny for non-string keys. Zero-copy BorrowedValue<'a> borrows strings from input (18% faster).
Spans Spanned<T> tracks line, column, and byte offset for every deserialized field. Serializes transparently as T. Span tracking is opt-in — disabled by default in from_str for zero overhead. For large documents, consider the memory impact of the span HashMap.
Formatting Per-value output control: FlowSeq<T>, FlowMap<T>, LitStr, FoldStr, Commented<T>, SpaceAfter<T>.
Enums singleton_map, singleton_map_optional, singleton_map_recursive, singleton_map_with -- custom key transforms (snake_case, kebab-case, lowercase).
Schemas Validate against YAML schema levels: validate_yaml_failsafe_schema, validate_yaml_json_schema, validate_yaml_core_schema.
Anchors Anchors (&), aliases (*), and merge keys (<<). Smart pointer wrappers: RcAnchor, ArcAnchor, RcWeakAnchor, ArcWeakAnchor.
Security 7 configurable limits in ParserConfig: depth, document size, alias expansions, mapping keys, sequence length, duplicate key policy, strict booleans. ParserConfig::strict() for untrusted input. Billion-laughs safe via max_alias_expansions with saturating_add overflow protection.
Compat YAML 1.1 legacy boolean mode (legacy_booleans): resolves yes/no/on/off/y/n as booleans for Docker Compose, GitHub Actions, and other YAML 1.1 tooling. Solves the "Norway problem".
WASM Compiles to wasm32-unknown-unknown. wasm-bindgen bindings (camelCase per JS conventions): parse(), stringify(), getPath(), validateJson(), merge(), plus the WasmDocument class (toString(), get(), getSource(), set(), setValue(), spanAt(), commentsAt(), replaceSpan()). Browser demo included.
Errors Source locations on all parse errors. format_with_source() renders rustc-style diagnostics with --> pointer. #[track_caller] on all Index panics. miette::Diagnostic integration included (--features miette) for rich terminal reports with error codes, actionable help text, and source spans.
no_std Full #![no_std] support with alloc. Use default-features = false. Core parsing (from_str, to_string, Value, schemas) works without std. I/O functions (from_reader, to_writer), Spanned<T> deserialization (TLS), and the CST module require the std feature. CI enforces cargo check --no-default-features on every push.
CST editing Side-table CST (noyalib::cst) for byte-faithful round-tripping. Document::set("server.port", "9090") rewrites only the touched bytes; comments, blank lines, and sibling formatting survive. Document::entry(path) is the chainable mutable handle (16 methods covering set / set_value / remove / insert / insert_value / push_back / insert_after / and_modify / or_insert / or_insert_with / or_insert_value / get / span_at / comments / exists / nested entry, plus smart items[0] path composition). Document::indent_unit() detects 2-/3-/4-space conventions so inserts conform to the file's existing style.
Anchors v2 Document::anchors() / aliases() / aliases_of(name) enumerate every &name / *name lexeme in source order. Document::materialise_alias_at(byte_pos) and materialise_aliases_of(name) "break" an alias by inlining the anchored scalar's source bytes — leaves the alias site independent of future anchor edits.
Schema codegen schema feature: derive JsonSchema (re-exported from schemars), then schema_for::<T>() -> Result<Value> or schema_for_yaml::<T>() -> Result<String> to emit the JSON Schema 2020-12 document. Honours #[doc], #[serde(default)], #[serde(rename)], integer bounds, nested types via $defs.
Schema validation validate-schema feature (implies schema): validate_against_schema(value, schema) -> Result<()> enforces a JSON Schema 2020-12 contract on parsed YAML. Multiple violations aggregated with RFC 6901 JSON-pointer paths. validate_against_schema_str is the raw-text convenience.
Binary scalars First-class !!binary tag with RFC 4648 base64 round-trip. serde_bytes::ByteBuf / Bytes work end-to-end including non-UTF-8 payloads.
serde_yaml shim compat-serde-yaml feature: name-for-name re-exports backed by noyalib-native types — the unmaintained serde_yaml 0.9 crate is intentionally not a dependency. Migrating in-flight ::serde_yaml::Value from un-migrated modules flows through the Serde bridge: noyalib::to_value(&upstream)?.
SIMD primitives noyalib::simd::find_any_of / clean_prefix_len / SimdScanner / StructuralIter / ByteBitmap / parse_decimal_{u64,i64}. Parser hot path routes through them for free; public for downstream scanner authors. StructuralIter delivers 4.2× stable / 9.2× nightly-simd vs the memchr loop on 1 MiB workloads.
Parallel parsing parallel feature: noyalib::parallel::parse<T> and noyalib::parallel::values deserialise multi-document streams across the Rayon thread pool. Pre-scan in O(input_len); per-document work parallelises naturally. Linear-with-cores on ----separated logs / audit dumps / Kubernetes snapshots.
Pluggable policies noyalib::policy::Policy trait + ParserConfig::with_policy(p). Built-ins: DenyAnchors (rejects &name / *name — billion-laughs guard), DenyTags (rejects custom tags), MaxScalarLength(n) (caps individual scalar size). Custom policies implement the trait.
Schema autofix validate-schema feature: coerce_to_schema(value, schema) -> Result<usize> walks JSON Schema type-mismatch errors and rewrites string-shaped scalars into the schema's expected type when the parse succeeds. Solves the port: "8080" quoting slip-up automatically. Library engine behind noyavalidate --fix.
Key interner noyalib::interner::KeyInterner&str → Arc<str> deduplication for repeated-key workloads. Kubernetes-shaped streams with 20-byte keys × 10 000 records: footprint drops from ~200 KB of fresh allocations to ~20 B + Arc pointers.

Governance: schema-driven autofix

YAML written by hand often has type slips that strict validation catches but humans don't notice — port: "8080" (string) where the schema declares port: integer. noyalib ships a one-call fix-up engine that rewrites these to match the schema:

use noyalib::{coerce_to_schema, from_str, schema_for, validate_against_schema, JsonSchema};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, JsonSchema)]
struct ServerConfig {
    /// Port the server binds on.
    port: u16,
    /// Hostname or IP literal.
    host: String,
}

let schema = schema_for::<ServerConfig>().unwrap();

// Hand-written YAML with the port quoted by mistake.
let mut data = from_str("port: \"8080\"\nhost: api.example.com\n").unwrap();

// Strict pass — fails because `port` is a string.
assert!(validate_against_schema(&data, &schema).is_err());

// Apply schema-driven coercions.
let n = coerce_to_schema(&mut data, &schema).unwrap();
assert_eq!(n, 1, "one fix expected");

// Re-validation passes — port is now an integer.
validate_against_schema(&data, &schema).unwrap();

Library engine behind noyavalidate --fix. Handles String → Integer, String → Number, String → Boolean coercions; the fix-loop iterates until convergence. Unparseable inputs (e.g. port: "abc" against type: integer) are left in place so the caller can surface the residue via a follow-up validate_against_schema call.


Policy enforcement (Safe YAML)

The pluggable [policy::Policy] trait + ParserConfig::with_policy(p) lets you reject documents that violate organisational constraints at parse time, before any data flows to downstream code. Built-in policies cover the most common asks; custom policies implement the trait directly.

use noyalib::policy::DenyAnchors;
use noyalib::{from_str_with_config, ParserConfig, Value};

let cfg = ParserConfig::new().with_policy(DenyAnchors);

// Anchors / aliases are the classical billion-laughs vector and a
// readability hazard in audited configs. With `DenyAnchors`
// registered, any document that defines `&name` or dereferences
// `*name` is rejected before deserialise begins.
let res: Result<Value, _> =
    from_str_with_config("key: &x 1\nval: *x\n", &cfg);
assert!(res.is_err());

// Anchor-free input passes through unchanged.
let v: Value = from_str_with_config("key: 1\nval: 1\n", &cfg).unwrap();
assert!(matches!(v, Value::Mapping(_)));

Built-ins: DenyAnchors, DenyTags (rejects custom tags while preservi