noyalib/lib.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2026 Noyalib. All rights reserved.
3
4//! # noyalib
5//!
6//! A YAML 1.2 library for Rust. Pure safe code. Full serde integration.
7//!
8//! ## Two APIs, one parser
9//!
10//! noyalib exposes two complementary surfaces over the same scanner
11//! and strictness rules. Pick the one that matches your job:
12//!
13//! - **Data binding** — [`from_str`], [`to_string`], [`Value`],
14//! [`StreamingDeserializer`], [`borrowed::BorrowedValue`]. Read
15//! YAML into typed Rust data, write Rust data back to YAML. The
16//! round-trip travels through a `Value`/struct, so comments,
17//! blank lines, and the original whitespace are not preserved.
18//! Use this for config loaders, RPC payloads, and the 95% of YAML
19//! workloads that just want data.
20//!
21//! - **Tooling / automation** — [`cst::parse_document`],
22//! [`cst::parse_stream`], [`cst::Document`]. Read YAML into a
23//! side-table CST that reproduces the source byte-for-byte,
24//! targeted edits via `doc.set("path", "fragment")` rewrite only
25//! the touched span — comments, formatting, and sibling entries
26//! are left untouched. Use this when *what the user wrote* matters
27//! (Renovate-style version bumps, Kubernetes manifest patchers,
28//! formatters, schema-driven linters). See `examples/lossless_edit.rs`.
29//!
30//! ## Quick Start
31//!
32//! ```rust
33//! use noyalib::{from_str, to_string};
34//!
35//! #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
36//! struct Config {
37//! name: String,
38//! port: u16,
39//! features: Vec<String>,
40//! }
41//!
42//! let yaml = "name: myapp\nport: 8080\nfeatures:\n - auth\n - api\n";
43//! let config: Config = from_str(yaml).unwrap();
44//! assert_eq!(config.name, "myapp");
45//! assert_eq!(config.port, 8080);
46//!
47//! let output = to_string(&config).unwrap();
48//! let roundtrip: Config = from_str(&output).unwrap();
49//! assert_eq!(config, roundtrip);
50//! ```
51//!
52//! ## Deserialization
53//!
54//! ```rust,no_run
55//! # use noyalib::Value;
56//! # let yaml = "key: value";
57//! # let bytes = b"key: value";
58//! # let file = std::io::Cursor::new(yaml);
59//! # let value = Value::Null;
60//! // From string, byte slice, reader, or Value
61//! let v: Value = noyalib::from_str(yaml).unwrap();
62//! let v: Value = noyalib::from_slice(bytes).unwrap();
63//! let v: Value = noyalib::from_reader(file).unwrap();
64//! let v: Value = noyalib::from_value(&value).unwrap();
65//!
66//! // With security limits
67//! let config = noyalib::ParserConfig::strict();
68//! let v: Value = noyalib::from_str_with_config(yaml, &config).unwrap();
69//! ```
70//!
71//! ## Serialization
72//!
73//! ```rust,no_run
74//! # use noyalib::Value;
75//! # let value = Value::Null;
76//! // To string, writer, or fmt::Write
77//! let yaml: String = noyalib::to_string(&value).unwrap();
78//! let mut buf = Vec::new();
79//! noyalib::to_writer(&mut buf, &value).unwrap();
80//! let mut s = String::new();
81//! noyalib::to_fmt_writer(&mut s, &value).unwrap();
82//!
83//! // With custom config
84//! let config = noyalib::SerializerConfig::new()
85//! .indent(4)
86//! .quote_all(true);
87//! let yaml = noyalib::to_string_with_config(&value, &config).unwrap();
88//! ```
89//!
90//! ## Highlights
91//!
92//! - **Pure Rust** — native YAML 1.2 scanner and parser. No C bindings. No FFI.
93//! - **Zero `unsafe`** — `#![forbid(unsafe_code)]` enforced at compile time.
94//! - **Fast** — 75% faster serialization, 50% faster deserialization than
95//! serde\_yaml\_ng. Streaming deserializer bypasses the Value AST.
96//! - **Serde-native** — serialize and deserialize any `Serialize` /
97//! `Deserialize` type.
98//! - **Ordered mappings** — [`IndexMap`](indexmap::IndexMap)-backed. Insertion
99//! order preserved.
100//! - **Source spans** — [`Spanned<T>`] tracks exact line, column, and byte
101//! offset.
102//! - **Hardened** — configurable depth, size, and alias limits. Billion-laughs
103//! safe.
104//! - **100% YAML Test Suite** — 406/406 official test cases pass.
105//! - **Zero-copy** — [`borrowed::BorrowedValue`] borrows strings from input.
106//! - **Path queries** — `value.query("items[*].name")` with wildcards.
107//! - **`no_std`** — works with `alloc` only (`default-features = false`).
108//! - **`miette`** — optional rich terminal diagnostics (`--features miette`).
109//!
110//! ## API stability and SemVer policy
111//!
112//! noyalib follows [Semantic Versioning 2.0.0]. Pre-`1.0`, the
113//! version axis used for breaking changes is the **patch number**
114//! during the `0.0.x` series and the **minor number** during the
115//! `0.x.y` series — patch bumps within a stable line are
116//! source-compatible.
117//!
118//! - **Public surface** = items reachable from the crate root by an
119//! in-scope `pub use` (this file). Items reachable only via a
120//! `pub` module (e.g. helpers in [`borrowed`], [`cst`],
121//! [`policy`]) are also public; everything in a `pub(crate)` /
122//! private module is internal.
123//! - **`#[non_exhaustive]`** is applied to every public
124//! configuration struct ([`ParserConfig`], [`SerializerConfig`],
125//! [`Error`], [`MergeKeyPolicy`], [`DuplicateKeyPolicy`],
126//! [`FlowStyle`], [`ScalarStyle`], [`YamlVersion`]) so adding a
127//! field or variant in a future release is **not** a breaking
128//! change. Construct configuration via the documented
129//! `new` / `default` / `strict` constructors plus the builder
130//! setters; do not use exhaustive struct-literal syntax outside
131//! this crate.
132//! - **What we will not break in patch releases:**
133//! - public function signatures (parameter names, types, return
134//! types);
135//! - the [`Value`] enum's variant set;
136//! - re-exported macro names (none today);
137//! - the YAML 1.2 default-strictness contract.
138//! - **The deserialise-target bound is `T: for<'de>
139//! Deserialize<'de> + 'static`.** The `'static` half is the
140//! contract every real-world `DeserializeOwned` type already
141//! satisfies (the HRTB itself disallows borrowed lifetimes); it
142//! is documented explicitly because a small number of
143//! externally-defined trait signatures (e.g. `figment`'s
144//! `Format::from_str`) drop the `'static` from their own bound
145//! — for those, noyalib provides feature-gated internal entry
146//! points that bypass the [`Value`]-tag-preserving fast path.
147//! - **What may change without a major bump:** non-default
148//! `ParserConfig` semantics under explicit opt-in (e.g. a future
149//! `legacy_*` flag), error *message* wording (variant *names*
150//! are stable), benchmark numbers, internal module layout.
151//! - **Deprecations** ship with `#[deprecated(since = "x.y.z",
152//! note = "...")]` for at least one minor release before
153//! removal. CHANGELOG carries the migration recipe.
154//! - **API drift checks**: `cargo semver-checks` runs in CI on
155//! every PR.
156//!
157//! [Semantic Versioning 2.0.0]: https://semver.org/spec/v2.0.0.html
158//!
159//! ## MSRV policy
160//!
161//! - **Core library (`noyalib`)**: Rust **1.89.0** stable
162//! (edition 2024; raised from 1.86.0 in v0.0.58). CI's
163//! `msrv-core` gate builds the `default-features = false` and
164//! the standard `default` set, and runs clippy on the core
165//! lib, on `rustc 1.89.0` for every PR. The MSRV is treated as
166//! part of the public contract and is always called out in
167//! the CHANGELOG under a `### Changed — MSRV` heading.
168//! - **The number is the floor we verify, not the floor the
169//! library happens to reach.** `criterion 0.8` (dev-dependency)
170//! requires 1.86, so no test, bench or coverage run can
171//! execute below that. An unverifiable MSRV is not a
172//! contract, so we do not publish one. The MSRV moves when
173//! the toolchain we build and test at moves, never
174//! speculatively; the one exception is the v0.0.58 raise to
175//! 1.89.0, which shares one floor with the maintainer's other
176//! Rust crates (ADR-0014). It will never require a rustc newer
177//! than 12 months old at release time.
178//! - **Bumping the MSRV is a patch release while the crate is on
179//! `0.0.x`.** Cargo treats every `0.0.x` as its own
180//! incompatible version, so the patch position is the only one
181//! that moves; there is no minor slot to spend. This becomes a
182//! genuine minor-version event at `1.0`. See
183//! [`docs/POLICIES.md`](https://github.com/sebastienrousseau/noyalib/blob/main/docs/POLICIES.md)
184//! §1, which is the single source of truth for the floor.
185//! - **Companion crates** ([`noya-cli`], [`noyalib-lsp`]) share
186//! the same `1.89.0` floor; as of v0.0.58 the whole lockstep
187//! set declares one number.
188//! - **`compare-saphyr`** is an opt-in, bench-only feature
189//! whose `serde-saphyr` dependency uses let-chains and needs
190//! rustc 1.88+, inside the 1.89.0 floor. The MSRV gate does
191//! not build it, and does not build `--all-features`.
192//! - **`nightly-simd`** is the only feature that requires nightly
193//! rustc (`#![feature(portable_simd)]`); a `build.rs` cfg-detect
194//! probe means stable builds with `--all-features` still
195//! compile by treating `nightly-simd` as a no-op.
196//!
197//! [`noya-cli`]: https://crates.io/crates/noya-cli
198//! [`noyalib-lsp`]: https://crates.io/crates/noyalib-lsp
199//!
200//! ## Feature flag matrix
201//!
202//! All optional integrations are off by default — enable only
203//! what your application needs. Default-on flags can be opted out
204//! via `default-features = false`.
205//!
206//! | Feature | Default | Pulls in | Adds | Implies |
207//! | :--- | :---: | :--- | :--- | :--- |
208//! | `std` | ✅ | — | I/O, [`Spanned<T>`] deserialise, [`cst`] | — |
209//! | `fast-int` | ✅ | `itoa` | branchless integer formatting | `std` recommended |
210//! | `fast-float` | ✅ | `ryu` | branchless float formatting | `std` recommended |
211//! | `strict-deserialise` | ✅ | `serde_ignored` | `from_*_strict` family | `std` |
212//! | `minimal` | ⛔ | — | meta-alias for `std` only (drops the three above) | `std` |
213//! | `miette` | ⛔ | `miette 7` | rich terminal diagnostics | — |
214//! | `schema` | ⛔ | `schemars`, `serde_json` | [`schema_for`] / [`schema_for_yaml`] **+** consumer must also depend on `schemars = "1.2"` to derive [`JsonSchema`] | — |
215//! | `validate-schema` | ⛔ | `schema` + `jsonschema` | [`validate_against_schema`], [`coerce_to_schema`], [`CompiledSchema`] | `schema` |
216//! | `figment` | ⛔ | `figment 0.10` | [`figment::Yaml`](crate::figment) Provider | `std` |
217//! | `garde` | ⛔ | `garde 0.23` | [`Validated<T>`] | — |
218//! | `validator` | ⛔ | `validator 0.21` | [`ValidatedValidator<T>`] | — |
219//! | `lossless-float` | ⛔ | — | [`lossless_float::LosslessFloat`] — refuse-to-lose-precision float, the floating-point sibling of `lossless-u64` | — |
220//! | `parallel` | ⛔ | `rayon 1.10` | [`parallel::parse`], [`parallel::values`] | `std` |
221//! | `simd` | ⛔ | — | forward-compat no-op — `noyalib::simd::*` is always available; the hot path uses it unconditionally | — |
222//! | `nightly-simd` | ⛔ | nightly rustc | 32-byte `StructuralIter` | `simd` |
223//! | `compat-serde-yaml` | ⛔ | — | `noyalib::compat::serde_yaml` shim | — |
224//! | `compare-saphyr` | ⛔ | `serde-saphyr` | comparison-bench arms (dev-only — do **not** ship in release builds) | — |
225//! | `noyavalidate` | ⛔ | — | meta-feature: `validate-schema` + `miette` | `validate-schema`, `miette` |
226//! | `wasm-opt` | ⛔ | — | marker consumed by `noyalib-wasm`'s build.rs to opt into a Binaryen post-build pass | — |
227//!
228//! `docs.rs` builds with `--all-features`; every gated item is
229//! tagged with the feature it requires via the `doc(cfg(...))`
230//! badge.
231//!
232//! ## Concurrency guarantees
233//!
234//! - All public top-level functions ([`from_str`], [`from_slice`],
235//! [`from_reader`], [`to_string`], [`to_writer`], …) are pure
236//! over their inputs and may be called concurrently from any
237//! number of threads.
238//! - [`Value`], [`Mapping`], [`Number`], [`Spanned<T>`],
239//! [`Error`] are `Send + Sync`. Cloning a `Value` is `O(n)` in
240//! the value graph; share ownership via
241//! [`Arc`](std::sync::Arc)`<Value>` when that cost matters.
242//! - [`policy::Policy`] requires `Send + Sync` so policies can be
243//! shared by reference across threads. Stateful policies should
244//! hold their state behind interior mutability
245//! ([`std::sync::Mutex`] or equivalent).
246//! - [`Spanned<T>`] deserialisation uses a thread-local span
247//! context (`std` feature). The TLS guard installs on entry to
248//! [`from_str_with_config`] and clears on return — no leakage
249//! across calls or across threads.
250//! - Anchor and alias state lives in the parser stack frame (one
251//! per call); concurrent calls share no mutable state.
252//! - The Rayon-backed [`parallel`] module validates document limits
253//! before scheduling, then discovers boundaries on demand and
254//! dispatches bounded work to the active Rayon pool. `T: Send` is
255//! required.
256//! - [`anchors::ArcAnchorRegistry`] / [`anchors::ArcAnchor`] use
257//! `Arc` + `Weak` and are explicitly multi-thread-safe; the
258//! `Rc`-backed siblings are single-thread.
259//!
260//! ## Security posture
261//!
262//! - **No `unsafe`** — `#![forbid(unsafe_code)]` enforced at
263//! compile time on every workspace crate.
264//! - **No FFI** — pure Rust scanner / parser / serialiser /
265//! CST. Closes the historical `libyaml` C-FFI CVE class.
266//! - **No arbitrary object instantiation from tags** — custom
267//! tags surface as [`Value::Tagged`] data; opt-in dispatch via
268//! [`TagRegistry`]. There is no path from a parsed YAML
269//! document to running attacker-chosen code.
270//! - **Resource budgets** — seven configurable limits in
271//! [`ParserConfig`] cap depth, document size, alias
272//! expansions, mapping keys, sequence length, duplicate-key
273//! policy, and boolean strictness. [`ParserConfig::strict`]
274//! tightens every budget for untrusted input. Alias-byte
275//! accumulation uses `saturating_add` so a crafted overflow
276//! still trips the cap.
277//! - **Pluggable policies** — [`policy::DenyAnchors`],
278//! [`policy::DenyTags`], [`policy::MaxScalarLength`] for
279//! organisational "Safe YAML" enforcement. Custom policies
280//! implement [`policy::Policy`].
281//! - **Supply chain** — `cargo audit`, `cargo deny`, `cargo vet`
282//! gate every PR. Releases ship SLSA Build L2 provenance and
283//! sigstore signatures (verification cookbook in
284//! [`pkg/VERIFY.md`](https://github.com/sebastienrousseau/noyalib/blob/main/pkg/VERIFY.md)).
285//! No archived or unmaintained crate appears in the dependency
286//! graph.
287//!
288//! Disclosure policy: see
289//! [`SECURITY.md`](https://github.com/sebastienrousseau/noyalib/blob/main/SECURITY.md).
290//!
291//! ## Performance and complexity
292//!
293//! - **Parser** — single-pass, `O(n)` in input bytes for the
294//! scanner; loader is `O(n)` events. `IndexMap` insert is
295//! amortised `O(1)`; `FxHasher` keeps key hashing cheap on
296//! short keys.
297//! - **Streaming deserialise** — bypasses the dynamic `Value`
298//! AST when the caller asks for a typed `T`, eliminating
299//! intermediate allocations. ~30% faster than the
300//! AST-via-`Value` path on real workloads.
301//! - **Zero-copy scanner** — string scalars come out as
302//! `Cow::Borrowed` when no escape sequence forces an
303//! allocation. [`borrowed::BorrowedValue`] surfaces this all
304//! the way to the caller.
305//! - **SIMD primitives** — [`simd::find_any_of`] dispatches to
306//! `memchr` SSE2/NEON for arity 1/2/3 and SWAR for arity 4+.
307//! With `nightly-simd`, the structural-bitmask scanner widens
308//! to 32-byte lanes — ~9× speedup vs the memchr loop on 1 MiB
309//! inputs.
310//! - **SWAR decimal parser** — folds 8 ASCII digits per `u64`
311//! cycle. ~2× faster than `<i64 as FromStr>::from_str` on big
312//! numbers.
313//! - **Serialiser** — branchless integer (`itoa`) and float
314//! (`ryu`) formatting in the hot path; falls back to
315//! `core::fmt` under `--no-default-features`.
316//! - **Parallel multi-document** — [`parallel::parse`] scales
317//! near-linearly with cores on `---`-separated streams; the
318//! pre-scan is `O(input.len())` on the calling thread.
319//! - **`Value::clone`** is `O(n)` over the value graph; share
320//! via `Arc<Value>` when that matters.
321//!
322//! ## Platform support
323//!
324//! - **Tier 1**: `x86_64-unknown-linux-gnu`,
325//! `x86_64-apple-darwin`, `aarch64-apple-darwin`,
326//! `x86_64-pc-windows-msvc`, `aarch64-unknown-linux-gnu`. CI
327//! runs on each of these on every PR.
328//! - **Tier 2**: musl Linux (`*-musl`),
329//! `i686-pc-windows-msvc`, `aarch64-pc-windows-msvc`. Built
330//! in release CI; not gated on every PR.
331//! - **Embedded / `no_std`**: any `alloc` target that also has
332//! atomic pointer support (`target_has_atomic = "ptr"`) —
333//! Cortex-M3/M4/M7 (`thumbv7m-none-eabi`,
334//! `thumbv7em-none-eabi*`), RISC-V with the `A` extension
335//! (`riscv32imac-unknown-none-elf`) and similar. CI builds
336//! `thumbv7em-none-eabihf` and `riscv32imac-unknown-none-elf`
337//! on every PR.
338//!
339//! Targets **without** atomic compare-and-swap — Cortex-M0 /
340//! M0+ / M1 (`thumbv6m-none-eabi`), RISC-V cores lacking the
341//! `A` extension (`riscv32i-unknown-none-elf`), AVR and
342//! MSP430 — are **not** supported: `alloc::sync::Arc` does
343//! not exist there, and it backs the shared interior of
344//! [`Error`], the tag registry, the interner and the [`cst`]
345//! green tree. Supporting them would mean an `Rc` fallback
346//! that drops `Send`/`Sync` from those public types on exactly
347//! those targets, or making every downstream user pick a
348//! `critical-section` implementation. If you need a CAS-free
349//! target, please open an issue rather than assuming it works.
350//!
351//! The `std`-only items ([`from_reader`], [`to_writer`],
352//! [`Spanned<T>`] deserialisation via TLS, the [`cst`]
353//! module) are gone; the rest of the surface compiles. CI
354//! enforces `cargo check --no-default-features` on every PR.
355//! - **WASM**: `wasm32-unknown-unknown` via the `noyalib-wasm`
356//! companion crate. 338 KB release binary (LTO). Browser
357//! demo in `demos/wasm/`.
358//! - **Big-endian**: validated under Miri's
359//! `mips64-unknown-linux-gnuabi64` simulation in the weekly
360//! `miri-bigendian` job.
361//!
362//! ## Error model
363//!
364//! Every fallible function returns [`Result<T>`](crate::Result)
365//! aliasing `core::result::Result<T, Error>`. [`Error`] is
366//! `#[non_exhaustive]`, implements `core::fmt::Display`,
367//! `core::error::Error` (via `std::error::Error` under the
368//! `std` feature), and — with `--features miette` —
369//! `miette::Diagnostic` for rich terminal reports.
370//!
371//! Each entry-point's `# Errors` section enumerates the variant
372//! set callers must handle; cross-reference the [`Error`]
373//! variants for descriptions.
374
375// SPDX-License-Identifier: MIT OR Apache-2.0
376// Copyright (c) 2026 Noyalib. All rights reserved.
377
378#![forbid(unsafe_code)]
379#![warn(missing_docs)]
380#![cfg_attr(docsrs, feature(doc_cfg))]
381#![cfg_attr(not(feature = "std"), no_std)]
382#![cfg_attr(
383 all(feature = "nightly-simd", noyalib_nightly),
384 allow(unstable_features),
385 feature(portable_simd)
386)]
387// Opt-in coverage annotations. `noyalib_coverage` is set by the
388// build script when `NOYALIB_COVERAGE=1` is exported (typically by
389// the CI coverage job running on nightly). When active, items
390// annotated with `#[cfg_attr(noyalib_coverage, coverage(off))]`
391// are excluded from coverage instrumentation. Stable builds and
392// regular nightly builds never see the `coverage_attribute`
393// feature flag, so the annotations are no-ops there.
394// Kani injects its own preamble into the crate — `<kani_macro_overrides>`
395// carries a `#[macro_use] extern crate`, which this crate's lint table
396// denies. That table governs code written here; a verifier's generated
397// prelude is not that, and there is no edit to this repository that
398// would satisfy it.
399//
400// Scoped to `cfg(kani)` so the lint keeps its full force everywhere
401// else: a `#[macro_use] extern crate` written by hand still fails to
402// compile.
403#![cfg_attr(kani, allow(macro_use_extern_crate))]
404#![cfg_attr(noyalib_coverage, allow(unstable_features))]
405#![cfg_attr(noyalib_coverage, feature(coverage_attribute))]
406
407// README doctest coverage: every ```rust block in
408// crates/noyalib/README.md is exercised by `cargo test --doc`.
409// The hidden module exists only when doctesting so the README
410// content does not leak into the docs.rs page (the lib's own
411// crate-level docs above are the canonical surface there).
412#[cfg(doctest)]
413#[doc = include_str!("../README.md")]
414mod readme_doctests {}
415
416// The workspace-root README (GitHub landing page) is doctested by
417// `scripts/check-readme-examples.sh` — every ```rust block there
418// is extracted and compiled against `noyalib` from a scratch
419// project. Cannot `include_str!("../../../README.md")` here
420// because the workspace-root README lives outside the crate's
421// package layout, so `cargo publish --dry-run` would fail
422// verification. The script-based path preserves the invariant
423// (broken root-README examples fail CI) without breaking publish.
424
425#[cfg(not(feature = "std"))]
426extern crate alloc;
427
428/// Internal prelude for no_std compatibility.
429/// Provides String, Vec, Box, etc. from alloc when std is absent.
430#[cfg(not(feature = "std"))]
431pub(crate) mod prelude {
432 pub(crate) use alloc::borrow::{Cow, ToOwned};
433 pub(crate) use alloc::boxed::Box;
434 pub(crate) use alloc::format;
435 pub(crate) use alloc::string::{String, ToString};
436 pub(crate) use alloc::sync::Arc;
437 pub(crate) use alloc::vec;
438 pub(crate) use alloc::vec::Vec;
439 pub(crate) use core::fmt;
440
441 // `rustc_hash::FxHashMap`/`FxHashSet` are aliases for the std
442 // `HashMap`/`HashSet`, so they do not exist without std. hashbrown
443 // provides the same containers for bare-metal targets; keying them
444 // with `FxBuildHasher` keeps hashing behaviour identical to the
445 // hosted build. See #210.
446 pub(crate) use rustc_hash::FxBuildHasher;
447 pub(crate) type FxHashMap<K, V> = hashbrown::HashMap<K, V, FxBuildHasher>;
448 pub(crate) type FxHashSet<T> = hashbrown::HashSet<T, FxBuildHasher>;
449
450 // `indexmap`'s default hasher is `RandomState`, which lives in std.
451 // Without it `IndexMap<K, V>` needs its third parameter spelled at
452 // every use — including public signatures such as
453 // `Mapping::into_inner`. Defaulting `S` to `FxBuildHasher` here
454 // keeps the spelling identical across targets, so no public API
455 // changes shape for hosted callers. See #210.
456 pub(crate) type IndexMap<K, V, S = FxBuildHasher> = indexmap::IndexMap<K, V, S>;
457
458 // `f64::fract` and `f64::mul_add` are std inherent methods; `core`
459 // has neither. libm supplies the equivalents. `fract` is defined as
460 // `self - self.trunc()`, which is what libm::trunc gives exactly.
461 // See #210.
462 #[inline]
463 pub(crate) fn f64_fract(x: f64) -> f64 {
464 x - libm::trunc(x)
465 }
466
467 #[inline]
468 pub(crate) fn f64_mul_add(a: f64, b: f64, c: f64) -> f64 {
469 libm::fma(a, b, c)
470 }
471}
472
473/// Internal prelude for std compatibility.
474#[cfg(feature = "std")]
475pub(crate) mod prelude {
476 pub(crate) use std::borrow::{Cow, ToOwned};
477 pub(crate) use std::boxed::Box;
478 pub(crate) use std::fmt;
479 pub(crate) use std::format;
480 pub(crate) use std::string::{String, ToString};
481 pub(crate) use std::sync::Arc;
482 pub(crate) use std::vec;
483 pub(crate) use std::vec::Vec;
484
485 // Hosted builds keep the std-backed maps unchanged; the no_std
486 // prelude substitutes hashbrown equivalents. See #210.
487 pub(crate) use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
488
489 // Hosted builds keep indexmap's own default hasher; the no_std
490 // prelude substitutes `FxBuildHasher`. See #210.
491 pub(crate) use indexmap::IndexMap;
492
493 // Hosted builds use the inherent std methods; the no_std prelude
494 // routes these through libm. See #210.
495 #[inline]
496 pub(crate) fn f64_fract(x: f64) -> f64 {
497 x.fract()
498 }
499
500 #[inline]
501 pub(crate) fn f64_mul_add(a: f64, b: f64, c: f64) -> f64 {
502 a.mul_add(b, c)
503 }
504}
505
506mod anchors;
507/// Zero-copy YAML values that borrow from the input.
508/// [`ariadne`] adapter — render `crate::Error` as an
509/// `ariadne::Report` with the offending byte range labelled.
510/// Behind the `ariadne` Cargo feature.
511#[cfg(feature = "ariadne")]
512#[cfg_attr(docsrs, doc(cfg(feature = "ariadne")))]
513pub mod ariadne_adapter;
514
515/// Internal RFC 4648 base64 codec for `!!binary` scalars.
516mod base64;
517/// `!include` directive — resolver types
518/// (`IncludeResolver`, `IncludeRequest`, `InputSource`,
519/// `SymlinkPolicy`, `SafeFileResolver`). Wired into
520/// `ParserConfig::include_resolver`.
521#[cfg(feature = "include")]
522#[cfg_attr(docsrs, doc(cfg(feature = "include")))]
523pub mod include;
524
525/// Declarative `parser_config!` / `serializer_config!` builder
526/// macros. Pure expansion to the existing chained-setter
527/// builders — zero runtime overhead.
528mod macros;
529
530/// Pluggable error-message formatters: [`i18n::MessageFormatter`]
531/// trait plus [`i18n::DefaultFormatter`] (developer-facing,
532/// verbatim) and [`i18n::UserFormatter`] (user-facing,
533/// simplified language). Use
534/// [`crate::Error::render_with_formatter`] to plug in
535/// localisation tables or custom rendering.
536pub mod i18n;
537
538/// `Spanned<T>` + garde / validator → `miette::Report` bridge.
539/// Behind the `miette` Cargo feature; the actual conversion
540/// functions are gated on `miette + garde` or `miette + validator`.
541#[cfg(feature = "miette")]
542#[cfg_attr(docsrs, doc(cfg(feature = "miette")))]
543pub mod validated_miette;
544
545pub mod borrowed;
546mod comments;
547/// Drop-in compatibility shims for upstream YAML crates. Each shim
548/// is gated behind its own feature flag so unused migration paths
549/// add zero compile cost. See [`compat::serde_yaml`] for the
550/// `serde_yaml` 0.9 surface.
551pub mod compat;
552#[cfg(feature = "std")]
553pub mod cst;
554mod de;
555/// Framework-neutral diagnostics and optional rendering bridges.
556pub mod diagnostic;
557/// Workspace-private `---` document-boundary scanner shared by
558/// `parallel::split`, `recovery::split_documents`, and
559/// `tokio_async::find_doc_boundary` — one CRLF/BOM-aware scanner,
560/// one DoS cap.
561pub(crate) mod doc_boundary;
562/// Multi-document loading and iteration.
563pub mod document;
564mod error;
565/// [`figment`] provider integration. Pulls in `figment` 0.10
566/// when the `figment` Cargo feature is enabled.
567#[cfg(feature = "figment")]
568#[cfg_attr(docsrs, doc(cfg(feature = "figment")))]
569pub mod figment;
570mod flattened;
571pub mod fmt;
572/// Key interning for memory-efficient repeated-key workloads.
573pub mod interner;
574/// A float that refuses to silently lose information (requires
575/// `lossless-float` feature) — the floating-point sibling of
576/// `lossless-u64`.
577#[cfg(feature = "lossless-float")]
578#[cfg_attr(docsrs, doc(cfg(feature = "lossless-float")))]
579pub mod lossless_float;
580/// Parallel multi-document YAML parsing via Rayon. Gated by the
581/// `parallel` feature.
582#[cfg(feature = "parallel")]
583#[cfg_attr(docsrs, doc(cfg(feature = "parallel")))]
584pub mod parallel;
585mod parser;
586pub mod path;
587/// Pluggable parser policies for "Safe YAML" enforcement.
588pub mod policy;
589/// Error-recovering parser for LSP / IDE partial parsing. Gated
590/// by the `recovery` feature.
591#[cfg(feature = "recovery")]
592#[cfg_attr(docsrs, doc(cfg(feature = "recovery")))]
593pub mod recovery;
594mod schema;
595/// JSON Schema codegen via [`schemars`] — derive
596/// [`schemars::JsonSchema`] for a Rust type and call
597/// [`schema_for`] / [`schema_for_yaml`] to obtain the schema as a
598/// [`crate::Value`] or as YAML text. Requires the `schema` feature.
599#[cfg(feature = "schema")]
600#[cfg_attr(docsrs, doc(cfg(feature = "schema")))]
601mod schema_codegen;
602/// Schema *validation* — enforce a JSON Schema 2020-12 contract
603/// against a parsed [`Value`]. Pairs with [`schema_codegen`].
604/// Requires the `validate-schema` feature (which implies `schema`).
605#[cfg(feature = "validate-schema")]
606#[cfg_attr(docsrs, doc(cfg(feature = "validate-schema")))]
607mod schema_validate;
608mod ser;
609/// `sval` zero-allocation streaming serialization adapter.
610/// Gated by the `sval` feature.
611#[cfg(feature = "sval")]
612#[cfg_attr(docsrs, doc(cfg(feature = "sval")))]
613pub mod sval_adapter;
614/// Native async YAML parsing for tokio runtimes —
615/// `from_async_reader` plus a `tokio_util::codec::Decoder`.
616/// Gated by the `tokio` feature.
617#[cfg(feature = "tokio")]
618#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
619pub mod tokio_async;
620
621/// SIMD-friendly multi-byte search primitives.
622///
623/// Pure-safe Rust (no `unsafe`, no platform intrinsics, no
624/// hardware-specific deps). The vectorisation comes from
625/// `memchr`'s SSE2 / NEON dispatch for arity 1/2/3 and SWAR
626/// (SIMD-Within-A-Register) for arity 4+. The parser hot path
627/// uses these primitives unconditionally; the `simd` Cargo
628/// feature is retained as a no-op for forward compatibility.
629pub mod simd;
630pub(crate) mod span_context;
631pub(crate) mod spanned;
632mod streaming;
633pub mod tag_registry;
634/// Declarative post-deserialise validation via [`garde`] or [`validator`]
635/// (requires the corresponding feature).
636#[cfg(any(feature = "garde", feature = "validator"))]
637#[cfg_attr(docsrs, doc(cfg(any(feature = "garde", feature = "validator"))))]
638pub mod validated;
639mod value;
640pub mod with;
641
642pub use anchors::{
643 AnchorRegistry, ArcAnchor, ArcAnchorRegistry, ArcWeakAnchor, RcAnchor, RcWeakAnchor,
644};
645// Recursive anchor wrappers depend on `Rc<RefCell<…>>` /
646// `Arc<Mutex<…>>` which require `std` (RefCell+Mutex live under
647// `core::cell` / `std::sync` — the latter only available with std).
648#[cfg(feature = "std")]
649pub use anchors::{ArcRecursion, ArcRecursive, RcRecursion, RcRecursive};
650pub use comments::{Comment, CommentKind, load_comments};
651pub use de::RequireIndent;
652pub use de::{
653 Deserializer, DuplicateKeyPolicy, MergeKeyPolicy, NonScalarKeyPolicy, ParserConfig,
654 ParserLimits, ParserProfile, YamlVersion, from_slice, from_slice_with_config, from_str,
655 from_str_borrowing, from_str_borrowing_with_config, from_str_with_config, from_value,
656};
657#[cfg(feature = "std")]
658pub use de::{from_reader, from_reader_with_config};
659#[cfg(all(feature = "std", feature = "strict-deserialise"))]
660pub use de::{from_reader_strict, from_slice_strict, from_str_strict};
661pub use diagnostic::{Diagnostic, DiagnosticCode, DiagnosticLabel, DiagnosticSeverity, SourceSpan};
662#[cfg(feature = "std")]
663pub use document::{DocumentReadIterator, read, read_with_config};
664pub use document::{
665 load_all, load_all_as, load_all_as_with_config, load_all_with_config, try_load_all,
666};
667pub use error::{BudgetBreach, CroppedRegion, Error, ErrorKind, Location, RenderOptions, Result};
668pub use flattened::Flattened;
669pub use fmt::{Commented, FlowMap, FlowSeq, FoldStr, FoldString, LitStr, LitString, SpaceAfter};
670pub use path::{Path, PathError, PathErrorKind, QueryPath, QuerySegment};
671pub use schema::{
672 is_yaml_failsafe_compatible, is_yaml_json_compatible, validate_yaml_core_schema,
673 validate_yaml_failsafe_schema, validate_yaml_json_schema,
674};
675#[cfg(feature = "schema")]
676#[cfg_attr(docsrs, doc(cfg(feature = "schema")))]
677pub use schema_codegen::{JsonSchema, schema_for, schema_for_yaml};
678#[cfg(feature = "validate-schema")]
679#[cfg_attr(docsrs, doc(cfg(feature = "validate-schema")))]
680pub use schema_validate::{
681 CompiledSchema, CompiledSchemaBuilder, DEFAULT_MAX_SCHEMA_DEPTH,
682 DEFAULT_MAX_SCHEMA_ERROR_BYTES, DEFAULT_MAX_SCHEMA_ERRORS, DEFAULT_MAX_SCHEMA_NODES,
683 MAX_SCHEMA_MESSAGE_BYTES, SchemaViolation, coerce_to_schema, validate_against_schema,
684 validate_against_schema_str,
685};
686pub use ser::{
687 FlowStyle, ScalarStyle, Serializer, SerializerConfig, to_fmt_writer, to_fmt_writer_with_config,
688 to_string, to_string_multi, to_string_multi_with_config, to_string_value,
689 to_string_value_with_config, to_string_with_config, to_value,
690};
691#[cfg(feature = "std")]
692pub use ser::{
693 to_string_tracking_shared, to_string_tracking_shared_with_config, to_writer_tracking_shared,
694 to_writer_tracking_shared_with_config,
695};
696#[cfg(feature = "std")]
697pub use ser::{
698 to_writer, to_writer_multi, to_writer_multi_with_config, to_writer_value,
699 to_writer_value_with_config, to_writer_with_config,
700};
701pub use spanned::Spanned;
702pub use streaming::StreamingDeserializer;
703pub use tag_registry::TagRegistry;
704#[cfg(feature = "garde")]
705#[cfg_attr(docsrs, doc(cfg(feature = "garde")))]
706pub use validated::Validated;
707#[cfg(feature = "validator")]
708#[cfg_attr(docsrs, doc(cfg(feature = "validator")))]
709pub use validated::ValidatedValidator;
710pub use value::{
711 Mapping, MappingAny, MaybeTag, Number, ParseNumberError, Sequence, Tag, TaggedValue, Value,
712 ValueIndex, check_for_tag, nobang,
713};