facet_yaml/
lib.rs

1//! YAML parser and serializer using facet-format.
2//!
3//! This crate provides YAML support via the `FormatParser` trait,
4//! using saphyr-parser for streaming event-based parsing.
5//!
6//! # Example
7//!
8//! ```
9//! use facet::Facet;
10//! use facet_yaml::{from_str, to_string};
11//!
12//! #[derive(Facet, Debug, PartialEq)]
13//! struct Config {
14//!     name: String,
15//!     port: u16,
16//! }
17//!
18//! let yaml = "name: myapp\nport: 8080";
19//! let config: Config = from_str(yaml).unwrap();
20//! assert_eq!(config.name, "myapp");
21//! assert_eq!(config.port, 8080);
22//!
23//! let output = to_string(&config).unwrap();
24//! assert!(output.contains("name: myapp"));
25//! ```
26
27extern crate alloc;
28
29mod error;
30mod parser;
31mod serializer;
32
33#[cfg(feature = "axum")]
34mod axum;
35
36pub use error::{YamlError, YamlErrorKind};
37
38#[cfg(feature = "axum")]
39pub use axum::{Yaml, YamlRejection};
40pub use parser::YamlParser;
41pub use serializer::{
42    YamlSerializeError, YamlSerializer, peek_to_string, peek_to_writer, to_string, to_vec,
43    to_writer,
44};
45
46// Re-export DeserializeError for convenience
47pub use facet_format::DeserializeError;
48
49/// Deserialize a value from a YAML string into an owned type.
50///
51/// This is the recommended default for most use cases. The input does not need
52/// to outlive the result, making it suitable for deserializing from temporary
53/// buffers (e.g., HTTP request bodies, config files read into a String).
54///
55/// Types containing `&str` fields cannot be deserialized with this function;
56/// use `String` or `Cow<str>` instead. For zero-copy deserialization into
57/// borrowed types, use [`from_str_borrowed`].
58///
59/// # Example
60///
61/// ```
62/// use facet::Facet;
63/// use facet_yaml::from_str;
64///
65/// #[derive(Facet, Debug, PartialEq)]
66/// struct Config {
67///     name: String,
68///     port: u16,
69/// }
70///
71/// let yaml = "name: myapp\nport: 8080";
72/// let config: Config = from_str(yaml).unwrap();
73/// assert_eq!(config.name, "myapp");
74/// assert_eq!(config.port, 8080);
75/// ```
76pub fn from_str<T>(input: &str) -> Result<T, DeserializeError<YamlError>>
77where
78    T: facet_core::Facet<'static>,
79{
80    use facet_format::FormatDeserializer;
81    let parser = YamlParser::new(input).map_err(DeserializeError::Parser)?;
82    let mut de = FormatDeserializer::new_owned(parser);
83    de.deserialize_root()
84}
85
86/// Deserialize a value from a YAML string, allowing zero-copy borrowing.
87///
88/// This variant requires the input to outlive the result (`'input: 'facet`),
89/// enabling zero-copy deserialization of string fields as `&str` or `Cow<str>`.
90///
91/// Use this when you need maximum performance and can guarantee the input
92/// buffer outlives the deserialized value. For most use cases, prefer
93/// [`from_str`] which doesn't have lifetime requirements.
94///
95/// Note: Due to YAML's streaming parser model, string values are typically
96/// owned. Zero-copy borrowing works best with `Cow<str>` fields.
97///
98/// # Example
99///
100/// ```
101/// use facet::Facet;
102/// use facet_yaml::from_str_borrowed;
103///
104/// #[derive(Facet, Debug, PartialEq)]
105/// struct Config {
106///     name: String,
107///     port: u16,
108/// }
109///
110/// let yaml = "name: myapp\nport: 8080";
111/// let config: Config = from_str_borrowed(yaml).unwrap();
112/// assert_eq!(config.name, "myapp");
113/// assert_eq!(config.port, 8080);
114/// ```
115pub fn from_str_borrowed<'input, 'facet, T>(
116    input: &'input str,
117) -> Result<T, DeserializeError<YamlError>>
118where
119    T: facet_core::Facet<'facet>,
120    'input: 'facet,
121{
122    use facet_format::FormatDeserializer;
123    let parser = YamlParser::new(input).map_err(DeserializeError::Parser)?;
124    let mut de = FormatDeserializer::new(parser);
125    de.deserialize_root()
126}
127
128/// Deserialize a value from YAML bytes into an owned type.
129///
130/// This is the recommended default for most use cases. The input does not need
131/// to outlive the result, making it suitable for deserializing from temporary
132/// buffers (e.g., HTTP request bodies).
133///
134/// # Errors
135///
136/// Returns an error if the input is not valid UTF-8 or if deserialization fails.
137///
138/// # Example
139///
140/// ```
141/// use facet::Facet;
142/// use facet_yaml::from_slice;
143///
144/// #[derive(Facet, Debug, PartialEq)]
145/// struct Config {
146///     name: String,
147///     port: u16,
148/// }
149///
150/// let yaml = b"name: myapp\nport: 8080";
151/// let config: Config = from_slice(yaml).unwrap();
152/// assert_eq!(config.name, "myapp");
153/// assert_eq!(config.port, 8080);
154/// ```
155pub fn from_slice<T>(input: &[u8]) -> Result<T, DeserializeError<YamlError>>
156where
157    T: facet_core::Facet<'static>,
158{
159    let s = core::str::from_utf8(input).map_err(|e| {
160        DeserializeError::Parser(YamlError::without_span(YamlErrorKind::InvalidUtf8(e)))
161    })?;
162    from_str(s)
163}
164
165/// Deserialize a value from YAML bytes, allowing zero-copy borrowing.
166///
167/// This variant requires the input to outlive the result (`'input: 'facet`),
168/// enabling zero-copy deserialization of string fields as `&str` or `Cow<str>`.
169///
170/// Use this when you need maximum performance and can guarantee the input
171/// buffer outlives the deserialized value. For most use cases, prefer
172/// [`from_slice`] which doesn't have lifetime requirements.
173///
174/// # Errors
175///
176/// Returns an error if the input is not valid UTF-8 or if deserialization fails.
177///
178/// # Example
179///
180/// ```
181/// use facet::Facet;
182/// use facet_yaml::from_slice_borrowed;
183///
184/// #[derive(Facet, Debug, PartialEq)]
185/// struct Config {
186///     name: String,
187///     port: u16,
188/// }
189///
190/// let yaml = b"name: myapp\nport: 8080";
191/// let config: Config = from_slice_borrowed(yaml).unwrap();
192/// assert_eq!(config.name, "myapp");
193/// assert_eq!(config.port, 8080);
194/// ```
195pub fn from_slice_borrowed<'input, 'facet, T>(
196    input: &'input [u8],
197) -> Result<T, DeserializeError<YamlError>>
198where
199    T: facet_core::Facet<'facet>,
200    'input: 'facet,
201{
202    let s = core::str::from_utf8(input).map_err(|e| {
203        DeserializeError::Parser(YamlError::without_span(YamlErrorKind::InvalidUtf8(e)))
204    })?;
205    from_str_borrowed(s)
206}