Skip to main content

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.86.0** stable
162//!   (edition 2024; raised from 1.85.0 in v0.0.16). 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.86.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.** `cargo +1.85.0 check --lib`
170//!   still succeeds, but `cargo +1.85.0 check --all-targets`
171//!   fails — `criterion 0.8` (dev-dependency) requires 1.86, so
172//!   no test, bench or coverage run can execute there. An
173//!   unverifiable MSRV is not a contract, so we do not publish
174//!   one. The MSRV moves only when the toolchain we build and
175//!   test at moves — never speculatively — and will never
176//!   require a rustc newer than 12 months old at release time.
177//! - **Bumping the MSRV is a patch release while the crate is on
178//!   `0.0.x`.** Cargo treats every `0.0.x` as its own
179//!   incompatible version, so the patch position is the only one
180//!   that moves; there is no minor slot to spend. This becomes a
181//!   genuine minor-version event at `1.0`. See
182//!   [`doc/POLICIES.md`](https://github.com/sebastienrousseau/noyalib/blob/main/doc/POLICIES.md)
183//!   §1, which is the single source of truth for the floor.
184//! - **Companion crates** ([`noya-cli`], [`noyalib-lsp`]) share
185//!   the same `1.86.0` floor — as of v0.0.16 the whole lockstep
186//!   set declares one number.
187//! - **`compare-saphyr`** is the one surface above the floor:
188//!   it is an opt-in, bench-only feature whose `serde-saphyr`
189//!   dependency uses let-chains and needs rustc 1.88+. It is
190//!   therefore excluded from the MSRV gate, as is
191//!   `--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`] | `schema` |
216//! | `figment` | ⛔ | `figment 0.10` | [`figment::Yaml`](crate::figment) Provider | `std` |
217//! | `garde` | ⛔ | `garde 0.22` | [`Validated<T>`] | — |
218//! | `validator` | ⛔ | `validator 0.19` | [`ValidatedValidator<T>`] | — |
219//! | `robotics` | ⛔ | — | `Degrees` / `Radians` / `StrictFloat` newtypes | — |
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 pre-scans document
253//!   boundaries on the calling thread, then dispatches each
254//!   document to the global Rayon pool — `T: Send` is required.
255//! - [`anchors::ArcAnchorRegistry`] / [`anchors::ArcAnchor`] use
256//!   `Arc` + `Weak` and are explicitly multi-thread-safe; the
257//!   `Rc`-backed siblings are single-thread.
258//!
259//! ## Security posture
260//!
261//! - **No `unsafe`** — `#![forbid(unsafe_code)]` enforced at
262//!   compile time on every workspace crate.
263//! - **No FFI** — pure Rust scanner / parser / serialiser /
264//!   CST. Closes the historical `libyaml` C-FFI CVE class.
265//! - **No arbitrary object instantiation from tags** — custom
266//!   tags surface as [`Value::Tagged`] data; opt-in dispatch via
267//!   [`TagRegistry`]. There is no path from a parsed YAML
268//!   document to running attacker-chosen code.
269//! - **Resource budgets** — seven configurable limits in
270//!   [`ParserConfig`] cap depth, document size, alias
271//!   expansions, mapping keys, sequence length, duplicate-key
272//!   policy, and boolean strictness. [`ParserConfig::strict`]
273//!   tightens every budget for untrusted input. Alias-byte
274//!   accumulation uses `saturating_add` so a crafted overflow
275//!   still trips the cap.
276//! - **Pluggable policies** — [`policy::DenyAnchors`],
277//!   [`policy::DenyTags`], [`policy::MaxScalarLength`] for
278//!   organisational "Safe YAML" enforcement. Custom policies
279//!   implement [`policy::Policy`].
280//! - **Supply chain** — `cargo audit`, `cargo deny`, `cargo vet`
281//!   gate every PR. Releases ship SLSA L3 provenance and
282//!   sigstore signatures (verification cookbook in
283//!   [`pkg/VERIFY.md`](https://github.com/sebastienrousseau/noyalib/blob/main/pkg/VERIFY.md)).
284//!   No archived or unmaintained crate appears in the dependency
285//!   graph.
286//!
287//! Disclosure policy: see
288//! [`SECURITY.md`](https://github.com/sebastienrousseau/noyalib/blob/main/SECURITY.md).
289//!
290//! ## Performance and complexity
291//!
292//! - **Parser** — single-pass, `O(n)` in input bytes for the
293//!   scanner; loader is `O(n)` events. `IndexMap` insert is
294//!   amortised `O(1)`; `FxHasher` keeps key hashing cheap on
295//!   short keys.
296//! - **Streaming deserialise** — bypasses the dynamic `Value`
297//!   AST when the caller asks for a typed `T`, eliminating
298//!   intermediate allocations. ~30% faster than the
299//!   AST-via-`Value` path on real workloads.
300//! - **Zero-copy scanner** — string scalars come out as
301//!   `Cow::Borrowed` when no escape sequence forces an
302//!   allocation. [`borrowed::BorrowedValue`] surfaces this all
303//!   the way to the caller.
304//! - **SIMD primitives** — [`simd::find_any_of`] dispatches to
305//!   `memchr` SSE2/NEON for arity 1/2/3 and SWAR for arity 4+.
306//!   With `nightly-simd`, the structural-bitmask scanner widens
307//!   to 32-byte lanes — ~9× speedup vs the memchr loop on 1 MiB
308//!   inputs.
309//! - **SWAR decimal parser** — folds 8 ASCII digits per `u64`
310//!   cycle. ~2× faster than `<i64 as FromStr>::from_str` on big
311//!   numbers.
312//! - **Serialiser** — branchless integer (`itoa`) and float
313//!   (`ryu`) formatting in the hot path; falls back to
314//!   `core::fmt` under `--no-default-features`.
315//! - **Parallel multi-document** — [`parallel::parse`] scales
316//!   near-linearly with cores on `---`-separated streams; the
317//!   pre-scan is `O(input.len())` on the calling thread.
318//! - **`Value::clone`** is `O(n)` over the value graph; share
319//!   via `Arc<Value>` when that matters.
320//!
321//! ## Platform support
322//!
323//! - **Tier 1**: `x86_64-unknown-linux-gnu`,
324//!   `x86_64-apple-darwin`, `aarch64-apple-darwin`,
325//!   `x86_64-pc-windows-msvc`, `aarch64-unknown-linux-gnu`. CI
326//!   runs on each of these on every PR.
327//! - **Tier 2**: musl Linux (`*-musl`),
328//!   `i686-pc-windows-msvc`, `aarch64-pc-windows-msvc`. Built
329//!   in release CI; not gated on every PR.
330//! - **Embedded / `no_std`**: any target supported by `alloc`.
331//!   The `std`-only items ([`from_reader`], [`to_writer`],
332//!   [`Spanned<T>`] deserialisation via TLS, the [`cst`]
333//!   module) are gone; the rest of the surface compiles. CI
334//!   enforces `cargo check --no-default-features` on every PR.
335//! - **WASM**: `wasm32-unknown-unknown` via the `noyalib-wasm`
336//!   companion crate. 338 KB release binary (LTO). Browser
337//!   demo in `crates/noyalib/examples/wasm/`.
338//! - **Big-endian**: validated under Miri's
339//!   `mips64-unknown-linux-gnuabi64` simulation in the weekly
340//!   `miri-bigendian` job.
341//!
342//! ## Error model
343//!
344//! Every fallible function returns [`Result<T>`](crate::Result)
345//! aliasing `core::result::Result<T, Error>`. [`Error`] is
346//! `#[non_exhaustive]`, implements `core::fmt::Display`,
347//! `core::error::Error` (via `std::error::Error` under the
348//! `std` feature), and — with `--features miette` —
349//! `miette::Diagnostic` for rich terminal reports.
350//!
351//! Each entry-point's `# Errors` section enumerates the variant
352//! set callers must handle; cross-reference the [`Error`]
353//! variants for descriptions.
354
355// SPDX-License-Identifier: MIT OR Apache-2.0
356// Copyright (c) 2026 Noyalib. All rights reserved.
357
358#![forbid(unsafe_code)]
359#![warn(missing_docs)]
360#![cfg_attr(docsrs, feature(doc_cfg))]
361#![cfg_attr(not(feature = "std"), no_std)]
362#![cfg_attr(
363    all(feature = "nightly-simd", noyalib_nightly),
364    allow(unstable_features),
365    feature(portable_simd)
366)]
367// Opt-in coverage annotations. `noyalib_coverage` is set by the
368// build script when `NOYALIB_COVERAGE=1` is exported (typically by
369// the CI coverage job running on nightly). When active, items
370// annotated with `#[cfg_attr(noyalib_coverage, coverage(off))]`
371// are excluded from coverage instrumentation. Stable builds and
372// regular nightly builds never see the `coverage_attribute`
373// feature flag, so the annotations are no-ops there.
374#![cfg_attr(noyalib_coverage, allow(unstable_features))]
375#![cfg_attr(noyalib_coverage, feature(coverage_attribute))]
376
377// README doctest coverage: every ```rust block in
378// crates/noyalib/README.md is exercised by `cargo test --doc`.
379// The hidden module exists only when doctesting so the README
380// content does not leak into the docs.rs page (the lib's own
381// crate-level docs above are the canonical surface there).
382#[cfg(doctest)]
383#[doc = include_str!("../README.md")]
384mod readme_doctests {}
385
386// The workspace-root README (GitHub landing page) is doctested by
387// `scripts/check-readme-examples.sh` — every ```rust block there
388// is extracted and compiled against `noyalib` from a scratch
389// project. Cannot `include_str!("../../../README.md")` here
390// because the workspace-root README lives outside the crate's
391// package layout, so `cargo publish --dry-run` would fail
392// verification. The script-based path preserves the invariant
393// (broken root-README examples fail CI) without breaking publish.
394
395#[cfg(not(feature = "std"))]
396extern crate alloc;
397
398/// Internal prelude for no_std compatibility.
399/// Provides String, Vec, Box, etc. from alloc when std is absent.
400#[cfg(not(feature = "std"))]
401pub(crate) mod prelude {
402    pub(crate) use alloc::borrow::{Cow, ToOwned};
403    pub(crate) use alloc::boxed::Box;
404    pub(crate) use alloc::format;
405    pub(crate) use alloc::string::{String, ToString};
406    pub(crate) use alloc::sync::Arc;
407    pub(crate) use alloc::vec;
408    pub(crate) use alloc::vec::Vec;
409    pub(crate) use core::fmt;
410
411    // `rustc_hash::FxHashMap`/`FxHashSet` are aliases for the std
412    // `HashMap`/`HashSet`, so they do not exist without std. hashbrown
413    // provides the same containers for bare-metal targets; keying them
414    // with `FxBuildHasher` keeps hashing behaviour identical to the
415    // hosted build. See #210.
416    pub(crate) use rustc_hash::FxBuildHasher;
417    pub(crate) type FxHashMap<K, V> = hashbrown::HashMap<K, V, FxBuildHasher>;
418    pub(crate) type FxHashSet<T> = hashbrown::HashSet<T, FxBuildHasher>;
419
420    // `indexmap`'s default hasher is `RandomState`, which lives in std.
421    // Without it `IndexMap<K, V>` needs its third parameter spelled at
422    // every use — including public signatures such as
423    // `Mapping::into_inner`. Defaulting `S` to `FxBuildHasher` here
424    // keeps the spelling identical across targets, so no public API
425    // changes shape for hosted callers. See #210.
426    pub(crate) type IndexMap<K, V, S = FxBuildHasher> = indexmap::IndexMap<K, V, S>;
427
428    // `f64::fract` and `f64::mul_add` are std inherent methods; `core`
429    // has neither. libm supplies the equivalents. `fract` is defined as
430    // `self - self.trunc()`, which is what libm::trunc gives exactly.
431    // See #210.
432    #[inline]
433    pub(crate) fn f64_fract(x: f64) -> f64 {
434        x - libm::trunc(x)
435    }
436
437    #[inline]
438    pub(crate) fn f64_mul_add(a: f64, b: f64, c: f64) -> f64 {
439        libm::fma(a, b, c)
440    }
441}
442
443/// Internal prelude for std compatibility.
444#[cfg(feature = "std")]
445pub(crate) mod prelude {
446    pub(crate) use std::borrow::{Cow, ToOwned};
447    pub(crate) use std::boxed::Box;
448    pub(crate) use std::fmt;
449    pub(crate) use std::format;
450    pub(crate) use std::string::{String, ToString};
451    pub(crate) use std::sync::Arc;
452    pub(crate) use std::vec;
453    pub(crate) use std::vec::Vec;
454
455    // Hosted builds keep the std-backed maps unchanged; the no_std
456    // prelude substitutes hashbrown equivalents. See #210.
457    pub(crate) use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
458
459    // Hosted builds keep indexmap's own default hasher; the no_std
460    // prelude substitutes `FxBuildHasher`. See #210.
461    pub(crate) use indexmap::IndexMap;
462
463    // Hosted builds use the inherent std methods; the no_std prelude
464    // routes these through libm. See #210.
465    #[inline]
466    pub(crate) fn f64_fract(x: f64) -> f64 {
467        x.fract()
468    }
469
470    #[inline]
471    pub(crate) fn f64_mul_add(a: f64, b: f64, c: f64) -> f64 {
472        a.mul_add(b, c)
473    }
474}
475
476mod anchors;
477/// Zero-copy YAML values that borrow from the input.
478/// [`ariadne`] adapter — render `crate::Error` as an
479/// `ariadne::Report` with the offending byte range labelled.
480/// Behind the `ariadne` Cargo feature.
481#[cfg(feature = "ariadne")]
482#[cfg_attr(docsrs, doc(cfg(feature = "ariadne")))]
483pub mod ariadne_adapter;
484
485/// Internal RFC 4648 base64 codec for `!!binary` scalars.
486mod base64;
487/// `!include` directive — resolver types
488/// (`IncludeResolver`, `IncludeRequest`, `InputSource`,
489/// `SymlinkPolicy`, `SafeFileResolver`). Wired into
490/// `ParserConfig::include_resolver`.
491#[cfg(feature = "include")]
492#[cfg_attr(docsrs, doc(cfg(feature = "include")))]
493pub mod include;
494
495/// Declarative `parser_config!` / `serializer_config!` builder
496/// macros. Pure expansion to the existing chained-setter
497/// builders — zero runtime overhead.
498mod macros;
499
500/// Pluggable error-message formatters: [`i18n::MessageFormatter`]
501/// trait plus [`i18n::DefaultFormatter`] (developer-facing,
502/// verbatim) and [`i18n::UserFormatter`] (user-facing,
503/// simplified language). Use
504/// [`crate::Error::render_with_formatter`] to plug in
505/// localisation tables or custom rendering.
506pub mod i18n;
507
508/// `Spanned<T>` + garde / validator → `miette::Report` bridge.
509/// Behind the `miette` Cargo feature; the actual conversion
510/// functions are gated on `miette + garde` or `miette + validator`.
511#[cfg(feature = "miette")]
512#[cfg_attr(docsrs, doc(cfg(feature = "miette")))]
513pub mod validated_miette;
514
515pub mod borrowed;
516mod comments;
517/// Drop-in compatibility shims for upstream YAML crates. Each shim
518/// is gated behind its own feature flag so unused migration paths
519/// add zero compile cost. See [`compat::serde_yaml`] for the
520/// `serde_yaml` 0.9 surface.
521pub mod compat;
522/// Side-table CST for byte-faithful round-tripping with typed
523/// path-targeted edits.
524///
525/// See `docs/design/green-tree.md` for the architectural plan. The
526/// `Document` API depends on the parser's `SpanTree`, which lives
527/// under the `std` feature.
528#[cfg(feature = "std")]
529pub mod cst;
530mod de;
531/// Spanned-to-miette diagnostic bridge (requires `miette` feature).
532#[cfg(feature = "miette")]
533#[cfg_attr(docsrs, doc(cfg(feature = "miette")))]
534pub mod diagnostic;
535/// Workspace-private `---` document-boundary scanner shared by
536/// `parallel::split`, `recovery::split_documents`, and
537/// `tokio_async::find_doc_boundary` — one CRLF/BOM-aware scanner,
538/// one DoS cap.
539pub(crate) mod doc_boundary;
540/// Multi-document loading and iteration.
541pub mod document;
542mod error;
543/// [`figment`] provider integration. Pulls in `figment` 0.10
544/// when the `figment` Cargo feature is enabled.
545#[cfg(feature = "figment")]
546#[cfg_attr(docsrs, doc(cfg(feature = "figment")))]
547pub mod figment;
548mod flattened;
549/// Formatting wrappers for per-value YAML output style control.
550pub mod fmt;
551/// Key interning for memory-efficient repeated-key workloads.
552pub mod interner;
553/// Parallel multi-document YAML parsing via Rayon. Gated by the
554/// `parallel` feature.
555#[cfg(feature = "parallel")]
556#[cfg_attr(docsrs, doc(cfg(feature = "parallel")))]
557pub mod parallel;
558mod parser;
559mod path;
560/// Pluggable parser policies for "Safe YAML" enforcement.
561pub mod policy;
562/// Error-recovering parser for LSP / IDE partial parsing. Gated
563/// by the `recovery` feature.
564#[cfg(feature = "recovery")]
565#[cfg_attr(docsrs, doc(cfg(feature = "recovery")))]
566pub mod recovery;
567/// Robotics and scientific numeric types (requires `robotics` feature).
568#[cfg(feature = "robotics")]
569#[cfg_attr(docsrs, doc(cfg(feature = "robotics")))]
570pub mod robotics;
571mod schema;
572/// JSON Schema codegen via [`schemars`] — derive
573/// [`schemars::JsonSchema`] for a Rust type and call
574/// [`schema_for`] / [`schema_for_yaml`] to obtain the schema as a
575/// [`crate::Value`] or as YAML text. Requires the `schema` feature.
576#[cfg(feature = "schema")]
577#[cfg_attr(docsrs, doc(cfg(feature = "schema")))]
578mod schema_codegen;
579/// Schema *validation* — enforce a JSON Schema 2020-12 contract
580/// against a parsed [`Value`]. Pairs with [`schema_codegen`].
581/// Requires the `validate-schema` feature (which implies `schema`).
582#[cfg(feature = "validate-schema")]
583#[cfg_attr(docsrs, doc(cfg(feature = "validate-schema")))]
584mod schema_validate;
585mod ser;
586/// `sval` zero-allocation streaming serialization adapter.
587/// Gated by the `sval` feature.
588#[cfg(feature = "sval")]
589#[cfg_attr(docsrs, doc(cfg(feature = "sval")))]
590pub mod sval_adapter;
591/// Native async YAML parsing for tokio runtimes —
592/// `from_async_reader` plus a `tokio_util::codec::Decoder`.
593/// Gated by the `tokio` feature.
594#[cfg(feature = "tokio")]
595#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
596pub mod tokio_async;
597
598/// SIMD-friendly multi-byte search primitives.
599///
600/// Pure-safe Rust (no `unsafe`, no platform intrinsics, no
601/// hardware-specific deps). The vectorisation comes from
602/// `memchr`'s SSE2 / NEON dispatch for arity 1/2/3 and SWAR
603/// (SIMD-Within-A-Register) for arity 4+. The parser hot path
604/// uses these primitives unconditionally; the `simd` Cargo
605/// feature is retained as a no-op for forward compatibility.
606pub mod simd;
607pub(crate) mod span_context;
608pub(crate) mod spanned;
609mod streaming;
610pub mod tag_registry;
611/// Declarative post-deserialise validation via [`garde`] or [`validator`]
612/// (requires the corresponding feature).
613#[cfg(any(feature = "garde", feature = "validator"))]
614#[cfg_attr(docsrs, doc(cfg(any(feature = "garde", feature = "validator"))))]
615pub mod validated;
616mod value;
617pub mod with;
618
619pub use anchors::{
620    AnchorRegistry, ArcAnchor, ArcAnchorRegistry, ArcWeakAnchor, RcAnchor, RcWeakAnchor,
621};
622// Recursive anchor wrappers depend on `Rc<RefCell<…>>` /
623// `Arc<Mutex<…>>` which require `std` (RefCell+Mutex live under
624// `core::cell` / `std::sync` — the latter only available with std).
625#[cfg(feature = "std")]
626pub use anchors::{ArcRecursion, ArcRecursive, RcRecursion, RcRecursive};
627pub use comments::{Comment, CommentKind, load_comments};
628pub use de::RequireIndent;
629pub use de::{
630    Deserializer, DuplicateKeyPolicy, MergeKeyPolicy, ParserConfig, YamlVersion, from_slice,
631    from_slice_with_config, from_str, from_str_borrowing, from_str_borrowing_with_config,
632    from_str_with_config, from_value,
633};
634#[cfg(feature = "std")]
635pub use de::{from_reader, from_reader_with_config};
636#[cfg(all(feature = "std", feature = "strict-deserialise"))]
637pub use de::{from_reader_strict, from_slice_strict, from_str_strict};
638#[cfg(feature = "std")]
639pub use document::{DocumentReadIterator, read, read_with_config};
640pub use document::{load_all, load_all_as, load_all_with_config, try_load_all};
641pub use error::{BudgetBreach, CroppedRegion, Error, ErrorKind, Location, RenderOptions, Result};
642pub use flattened::Flattened;
643pub use fmt::{Commented, FlowMap, FlowSeq, FoldStr, FoldString, LitStr, LitString, SpaceAfter};
644pub use path::Path;
645pub use schema::{
646    is_yaml_failsafe_compatible, is_yaml_json_compatible, validate_yaml_core_schema,
647    validate_yaml_failsafe_schema, validate_yaml_json_schema,
648};
649#[cfg(feature = "schema")]
650#[cfg_attr(docsrs, doc(cfg(feature = "schema")))]
651pub use schema_codegen::{JsonSchema, schema_for, schema_for_yaml};
652#[cfg(feature = "validate-schema")]
653#[cfg_attr(docsrs, doc(cfg(feature = "validate-schema")))]
654pub use schema_validate::{coerce_to_schema, validate_against_schema, validate_against_schema_str};
655pub use ser::{
656    FlowStyle, ScalarStyle, Serializer, SerializerConfig, to_fmt_writer, to_fmt_writer_with_config,
657    to_string, to_string_multi, to_string_multi_with_config, to_string_value,
658    to_string_value_with_config, to_string_with_config, to_value,
659};
660#[cfg(feature = "std")]
661pub use ser::{
662    to_string_tracking_shared, to_string_tracking_shared_with_config, to_writer_tracking_shared,
663    to_writer_tracking_shared_with_config,
664};
665#[cfg(feature = "std")]
666pub use ser::{
667    to_writer, to_writer_multi, to_writer_multi_with_config, to_writer_value,
668    to_writer_value_with_config, to_writer_with_config,
669};
670pub use spanned::Spanned;
671pub use streaming::StreamingDeserializer;
672pub use tag_registry::TagRegistry;
673#[cfg(feature = "garde")]
674#[cfg_attr(docsrs, doc(cfg(feature = "garde")))]
675pub use validated::Validated;
676#[cfg(feature = "validator")]
677#[cfg_attr(docsrs, doc(cfg(feature = "validator")))]
678pub use validated::ValidatedValidator;
679pub use value::{
680    Mapping, MappingAny, MaybeTag, Number, ParseNumberError, Sequence, Tag, TaggedValue, Value,
681    ValueIndex, check_for_tag, nobang,
682};