use crate::error::{Error, Result};
use crate::parser::{self};
use crate::prelude::*;
use crate::span_context;
use crate::value::Value;
use serde::Deserialize;
#[cfg(feature = "std")]
use std::io;
mod config;
mod deserializer;
pub use config::{DuplicateKeyPolicy, MergeKeyPolicy, ParserConfig, RequireIndent, YamlVersion};
pub use deserializer::Deserializer;
pub(crate) use deserializer::{SpannedMapAccess, is_binary_tag};
pub fn from_str<T>(s: &str) -> Result<T>
where
T: for<'de> Deserialize<'de> + 'static,
{
from_str_with_config(s, &ParserConfig::default())
}
pub fn from_str_borrowing<'a, T>(s: &'a str) -> Result<T>
where
T: Deserialize<'a>,
{
from_str_borrowing_with_config(s, &ParserConfig::default())
}
pub fn from_str_borrowing_with_config<'a, T>(s: &'a str, config: &ParserConfig) -> Result<T>
where
T: Deserialize<'a>,
{
let parse_config = parser::ParseConfig::from(config);
if s.len() > parse_config.max_document_length {
return Err(Error::Parse(format!(
"document exceeds maximum length of {} bytes",
parse_config.max_document_length
)));
}
let mut de = crate::streaming::StreamingDeserializer::with_config(s, parse_config);
if let Some(registry) = config.tag_registry.as_ref() {
de = de.with_tag_registry(Arc::clone(registry));
}
T::deserialize(&mut de)
}
fn is_value_target<T: 'static + ?Sized>() -> bool {
use core::any::TypeId;
TypeId::of::<T>() == TypeId::of::<Value>()
}
#[cfg(all(feature = "std", feature = "figment"))]
pub(crate) fn from_str_typed_no_tag_preserve<T>(s: &str, config: &ParserConfig) -> Result<T>
where
T: for<'de> Deserialize<'de>,
{
let stream_eligible = config.merge_key_policy == MergeKeyPolicy::Auto
&& !config.ignore_binary_tag_for_string
&& config.policies.is_empty();
if stream_eligible {
if let Some(res) = crate::streaming::from_str_streaming(s, config) {
return res;
}
}
let parse_config = parser::ParseConfig::from(config);
let (value, span_tree) = parser::parse_one(s, &parse_config)?;
for p in &config.policies {
p.check_value(&value)?;
}
let spans = span_context::build_span_map(&value, &span_tree);
let ctx = span_context::SpanContext {
spans,
source: s.into(),
};
let _guard = span_context::set_span_context(ctx);
let de = Deserializer::with_options(
&value,
Some(_guard.as_ref()),
config.ignore_binary_tag_for_string,
);
T::deserialize(de)
}
#[cfg(all(feature = "std", feature = "strict-deserialise"))]
pub fn from_str_strict<T>(s: &str) -> Result<T>
where
T: for<'de> Deserialize<'de> + 'static,
{
let unknown = std::sync::Mutex::new(Vec::<String>::new());
let value: Value = from_str_with_config(s, &ParserConfig::default())?;
let result: Result<T> = serde_ignored::deserialize(&value, |path| {
unknown
.lock()
.expect("from_str_strict: ignored-paths lock poisoned")
.push(path.to_string());
});
let extras = unknown
.into_inner()
.expect("from_str_strict: ignored-paths lock poisoned");
let typed = result?;
if !extras.is_empty() {
let msg = if extras.len() == 1 {
format!("unknown field at `{}`", extras[0])
} else {
let joined = extras
.iter()
.map(|p| format!("`{p}`"))
.collect::<Vec<_>>()
.join(", ");
format!("unknown fields: {joined}")
};
return Err(Error::UnknownField(msg));
}
Ok(typed)
}
#[cfg(all(feature = "std", feature = "strict-deserialise"))]
pub fn from_slice_strict<T>(b: &[u8]) -> Result<T>
where
T: for<'de> Deserialize<'de> + 'static,
{
let s = core::str::from_utf8(b).map_err(|e| Error::Deserialize(e.to_string()))?;
from_str_strict(s)
}
#[cfg(all(feature = "std", feature = "strict-deserialise"))]
pub fn from_reader_strict<R, T>(mut reader: R) -> Result<T>
where
R: io::Read,
T: for<'de> Deserialize<'de> + 'static,
{
let mut s = String::new();
let _ = reader.read_to_string(&mut s).map_err(Error::Io)?;
from_str_strict(&s)
}
pub fn from_str_with_config<T>(s: &str, config: &ParserConfig) -> Result<T>
where
T: for<'de> Deserialize<'de> + 'static,
{
let stream_eligible = config.merge_key_policy == MergeKeyPolicy::Auto
&& !config.ignore_binary_tag_for_string
&& config.policies.is_empty()
&& properties_inactive(config)
&& includes_inactive(config);
if stream_eligible {
if let Some(res) = crate::streaming::from_str_streaming(s, config) {
return res;
}
}
let parse_config = parser::ParseConfig::from(config);
if is_value_target::<T>() {
let mut value = parser::parse_one_value(s, &parse_config)?;
apply_includes(&mut value, config)?;
apply_properties(&mut value, config)?;
for p in &config.policies {
p.check_value(&value)?;
}
let boxed: Box<dyn core::any::Any> = Box::new(value);
let downcast: Box<T> = boxed
.downcast::<T>()
.expect("is_value_target proved T == Value");
return Ok(*downcast);
}
#[cfg(feature = "std")]
{
let (mut value, span_tree) = parser::parse_one(s, &parse_config)?;
apply_includes(&mut value, config)?;
apply_properties(&mut value, config)?;
for p in &config.policies {
p.check_value(&value)?;
}
let spans = span_context::build_span_map(&value, &span_tree);
let ctx = span_context::SpanContext {
spans,
source: s.into(),
};
let _guard = span_context::set_span_context(ctx);
let de = Deserializer::with_options(
&value,
Some(_guard.as_ref()),
config.ignore_binary_tag_for_string,
);
T::deserialize(de)
}
#[cfg(not(feature = "std"))]
{
let value = parser::parse_one_value(s, &parse_config)?;
let de = Deserializer::with_options(&value, None, config.ignore_binary_tag_for_string);
T::deserialize(de)
}
}
#[cfg(feature = "std")]
#[inline]
fn properties_inactive(config: &ParserConfig) -> bool {
config.properties.is_none()
}
#[cfg(not(feature = "std"))]
#[inline]
fn properties_inactive(_config: &ParserConfig) -> bool {
true
}
#[cfg(feature = "std")]
fn apply_properties(value: &mut Value, config: &ParserConfig) -> Result<()> {
if let Some(props) = config.properties.as_ref() {
let action = if config.strict_properties {
crate::value::MissingAction::Error(false)
} else {
crate::value::MissingAction::Empty
};
value.interpolate_inner(
&|name| match props.get(name) {
Some(v) => crate::value::ResolveOutcome::Found(v.clone()),
None => crate::value::ResolveOutcome::Missing,
},
action,
)?;
}
Ok(())
}
#[cfg(not(feature = "std"))]
#[inline]
fn apply_properties(_value: &mut Value, _config: &ParserConfig) -> Result<()> {
Ok(())
}
#[cfg(feature = "include")]
#[inline]
fn includes_inactive(config: &ParserConfig) -> bool {
config.include_resolver.is_none()
}
#[cfg(not(feature = "include"))]
#[inline]
fn includes_inactive(_config: &ParserConfig) -> bool {
true
}
#[cfg(feature = "include")]
fn apply_includes(value: &mut Value, config: &ParserConfig) -> Result<()> {
if let Some(resolver) = config.include_resolver.as_ref() {
let parse_config = parser::ParseConfig::from(config);
let mut visited: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut next_id: usize = 1;
resolve_includes_recursive(
value,
resolver,
&parse_config,
config.max_include_depth,
0,
0,
&mut visited,
&mut next_id,
)?;
}
Ok(())
}
#[cfg(not(feature = "include"))]
#[inline]
fn apply_includes(_value: &mut Value, _config: &ParserConfig) -> Result<()> {
Ok(())
}
#[cfg(feature = "include")]
#[allow(clippy::too_many_arguments)]
fn resolve_includes_recursive(
value: &mut Value,
resolver: &crate::include::IncludeResolver,
parse_config: &parser::ParseConfig,
max_depth: usize,
depth: usize,
from_id: usize,
visited: &mut std::collections::HashSet<String>,
next_id: &mut usize,
) -> Result<()> {
if depth > max_depth {
return Err(Error::RecursionLimitExceeded { depth });
}
match value {
Value::Tagged(boxed) => {
if boxed.tag().as_str() == "!include" {
let spec = match boxed.value().as_str() {
Some(s) => s.to_string(),
None => {
return Err(Error::Custom(
"!include directive expects a scalar string spec".into(),
));
}
};
if !visited.insert(spec.clone()) {
return Err(Error::Custom(format!(
"!include cycle detected: `{spec}` already in resolution chain"
)));
}
let (path, fragment) = crate::include::split_fragment(&spec);
let req = crate::include::IncludeRequest {
spec: &spec,
from_id,
depth,
};
let source = resolver.resolve(req)?;
let id = *next_id;
*next_id += 1;
let mut included = parser::parse_one_value(&source.bytes, parse_config)?;
resolve_includes_recursive(
&mut included,
resolver,
parse_config,
max_depth,
depth + 1,
id,
visited,
next_id,
)?;
if let Some(frag) = fragment {
if let Some(map) = included.as_mapping() {
match map.get(frag) {
Some(v) => *value = v.clone(),
None => {
return Err(Error::Custom(format!(
"!include fragment `#{frag}` not found in `{path}`"
)));
}
}
} else {
return Err(Error::Custom(format!(
"!include fragment `#{frag}` requires a mapping-shaped \
included document; `{path}` is not a mapping"
)));
}
} else {
*value = included;
}
let _ = visited.remove(&spec);
} else {
resolve_includes_recursive(
boxed.value_mut(),
resolver,
parse_config,
max_depth,
depth,
from_id,
visited,
next_id,
)?;
}
}
Value::Sequence(seq) => {
for v in seq {
resolve_includes_recursive(
v,
resolver,
parse_config,
max_depth,
depth,
from_id,
visited,
next_id,
)?;
}
}
Value::Mapping(map) => {
for v in map.values_mut() {
resolve_includes_recursive(
v,
resolver,
parse_config,
max_depth,
depth,
from_id,
visited,
next_id,
)?;
}
}
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
}
Ok(())
}
pub fn from_slice<T>(b: &[u8]) -> Result<T>
where
T: for<'de> Deserialize<'de> + 'static,
{
let s = core::str::from_utf8(b).map_err(|e| Error::Deserialize(e.to_string()))?;
from_str(s)
}
pub fn from_slice_with_config<T>(b: &[u8], config: &ParserConfig) -> Result<T>
where
T: for<'de> Deserialize<'de> + 'static,
{
let s = core::str::from_utf8(b).map_err(|e| Error::Deserialize(e.to_string()))?;
from_str_with_config(s, config)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn from_reader<R, T>(reader: R) -> Result<T>
where
R: io::Read,
T: for<'de> Deserialize<'de> + 'static,
{
from_reader_with_config(reader, &ParserConfig::default())
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn from_reader_with_config<R, T>(mut reader: R, config: &ParserConfig) -> Result<T>
where
R: io::Read,
T: for<'de> Deserialize<'de> + 'static,
{
let mut s = String::new();
let _ = reader.read_to_string(&mut s).map_err(Error::Io)?;
from_str_with_config(&s, config)
}
pub fn from_value<T>(value: &Value) -> Result<T>
where
T: for<'de> Deserialize<'de> + 'static,
{
if is_value_target::<T>() {
let cloned = value.clone();
let boxed: Box<dyn core::any::Any> = Box::new(cloned);
let downcast: Box<T> = boxed
.downcast::<T>()
.expect("is_value_target proved T == Value");
return Ok(*downcast);
}
T::deserialize(Deserializer::new(value))
}