use std::cell::Cell;
use std::fmt;
use oneyaml_core::{Limits, code};
use serde::de::{self, MapAccess, SeqAccess, Visitor};
use serde::ser::{self, SerializeMap, SerializeSeq};
use super::{Mapping, Number, Sequence, Tag, TaggedValue, Value};
pub(crate) const VALUE_MAGIC: &str = "$__oneyaml_private_Value";
thread_local! {
static SLOT: Cell<Option<Value>> = const { Cell::new(None) };
static OURS: Cell<bool> = const { Cell::new(false) };
static SER_SLOT: Cell<Option<Value>> = const { Cell::new(None) };
}
struct SlotGuard;
impl Drop for SlotGuard {
fn drop(&mut self) {
if let Ok(Some(left)) = SLOT.try_with(Cell::take) {
super::walk::drop_value(left);
}
}
}
pub(crate) fn hand_off<'de, V: Visitor<'de>>(value: Value, v: V) -> Result<V::Value, crate::Error> {
let _guard = SlotGuard;
if SLOT.try_with(|s| s.set(Some(value))).is_err() {
return Err(tls_gone());
}
v.visit_newtype_struct(de::value::UnitDeserializer::<crate::Error>::new())
}
fn tls_gone() -> crate::Error {
crate::Error::new(
code::THREAD_LOCAL_UNAVAILABLE,
"a Value cannot be deserialized while this thread's thread-local storage is being \
destroyed",
None,
)
.with_help(
"deserialize into a typed target instead, or deserialize the Value before the thread \
starts shutting down",
)
}
pub(crate) struct TlsGone;
impl fmt::Display for TlsGone {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(
"a Value cannot be serialized by oneyaml while this thread's thread-local storage \
is being destroyed",
)
}
}
pub(crate) fn is_tls_gone<T: ?Sized>() -> bool {
std::any::type_name::<T>() == std::any::type_name::<TlsGone>()
}
pub(crate) fn tls_gone_ser() -> crate::Error {
crate::Error::new(code::THREAD_LOCAL_UNAVAILABLE, TlsGone.to_string(), None).with_help(
"serialize the Value before the thread starts shutting down; third-party serializers \
still receive it as plain serde data",
)
}
impl<'de> de::Deserialize<'de> for Value {
fn deserialize<D: de::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
d.deserialize_newtype_struct(VALUE_MAGIC, ValueVisitor)
}
}
struct ValueVisitor;
impl<'de> Visitor<'de> for ValueVisitor {
type Value = Value;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("any YAML value")
}
fn visit_newtype_struct<D: de::Deserializer<'de>>(self, d: D) -> Result<Value, D::Error> {
match SLOT.try_with(Cell::take).ok().flatten() {
Some(v) => Ok(v),
None => d.deserialize_any(self),
}
}
fn visit_bool<E>(self, v: bool) -> Result<Value, E> {
Ok(Value::Bool(v))
}
fn visit_i64<E>(self, v: i64) -> Result<Value, E> {
Ok(Value::from(v))
}
fn visit_i128<E>(self, v: i128) -> Result<Value, E> {
Ok(Value::from(v))
}
fn visit_u64<E>(self, v: u64) -> Result<Value, E> {
Ok(Value::from(v))
}
fn visit_u128<E>(self, v: u128) -> Result<Value, E> {
Ok(Value::from(v))
}
fn visit_f64<E>(self, v: f64) -> Result<Value, E> {
Ok(Value::from(v))
}
fn visit_str<E>(self, v: &str) -> Result<Value, E> {
Ok(Value::from(v))
}
fn visit_string<E>(self, v: String) -> Result<Value, E> {
Ok(Value::String(v))
}
fn visit_unit<E>(self) -> Result<Value, E> {
Ok(Value::Null)
}
fn visit_none<E>(self) -> Result<Value, E> {
Ok(Value::Null)
}
fn visit_some<D: de::Deserializer<'de>>(self, d: D) -> Result<Value, D::Error> {
de::Deserialize::deserialize(d)
}
fn visit_seq<A: SeqAccess<'de>>(self, mut a: A) -> Result<Value, A::Error> {
let mut items = Vec::with_capacity(a.size_hint().unwrap_or(0).min(4096));
while let Some(v) = a.next_element::<Value>()? {
items.push(v);
}
Ok(Value::Sequence(Sequence::from(items)))
}
fn visit_map<A: MapAccess<'de>>(self, mut a: A) -> Result<Value, A::Error> {
let mut m = Mapping::new();
while let Some((k, v)) = a.next_entry::<Value, Value>()? {
m.insert(k, v);
}
Ok(Value::Mapping(m))
}
fn visit_enum<A: de::EnumAccess<'de>>(self, a: A) -> Result<Value, A::Error> {
use de::VariantAccess;
let (name, variant) = a.variant::<String>()?;
let value = variant.newtype_variant::<Value>()?;
Ok(Value::Tagged(Box::new(TaggedValue {
tag: Tag::new(format!("!{name}")),
value,
})))
}
}
impl<'de> de::Deserialize<'de> for Mapping {
fn deserialize<D: de::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
match Value::deserialize(d)? {
Value::Mapping(m) => Ok(m),
other => Err(de::Error::invalid_type(unexpected(&other), &"a mapping")),
}
}
}
impl<'de> de::Deserialize<'de> for Sequence {
fn deserialize<D: de::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
match Value::deserialize(d)? {
Value::Sequence(s) => Ok(s),
other => Err(de::Error::invalid_type(unexpected(&other), &"a sequence")),
}
}
}
impl<'de> de::Deserialize<'de> for Number {
fn deserialize<D: de::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
match Value::deserialize(d)? {
Value::Number(n) => Ok(n),
other => Err(de::Error::invalid_type(unexpected(&other), &"a number")),
}
}
}
fn unexpected(v: &Value) -> de::Unexpected<'_> {
match v {
Value::Null => de::Unexpected::Unit,
Value::Bool(b) => de::Unexpected::Bool(*b),
Value::Number(_) => de::Unexpected::Other("number"),
Value::String(s) => de::Unexpected::Str(s),
Value::Sequence(_) => de::Unexpected::Seq,
Value::Mapping(_) => de::Unexpected::Map,
Value::Tagged(_) => de::Unexpected::Other("tagged value"),
}
}
fn serialize_limit() -> usize {
Limits::default().get_max_typed_depth()
}
fn too_deep<E: ser::Error>() -> E {
E::custom(format!(
"{}: Value nests deeper than {} levels, the limit for serializing through serde \
(the default Limits::max_typed_depth); oneyaml's own emitter is iterative",
code::TYPED_DEPTH_EXCEEDED,
serialize_limit()
))
}
fn checked<S: ser::Serializer>(v: &Value, s: S) -> Result<S::Ok, S::Error> {
let limit = serialize_limit();
if super::walk::serde_depth(v, limit) > limit {
return Err(too_deep());
}
plain(v, s)
}
struct Unchecked<'a>(&'a Value);
impl ser::Serialize for Unchecked<'_> {
fn serialize<S: ser::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if to_value_serializer::<S>() && armed() {
return hand_over(s, &Payload::Value(self.0), || {
super::walk::clone_value(self.0)
});
}
plain(self.0, s)
}
}
fn plain<S: ser::Serializer>(v: &Value, s: S) -> Result<S::Ok, S::Error> {
match v {
Value::Null => s.serialize_unit(),
Value::Bool(b) => s.serialize_bool(*b),
Value::Number(n) => serialize_number(n, s),
Value::String(v) => s.serialize_str(v),
Value::Sequence(seq) => {
let mut out = s.serialize_seq(Some(seq.len()))?;
for item in seq.iter() {
out.serialize_element(&Unchecked(item))?;
}
out.end()
}
Value::Mapping(m) => {
let mut out = s.serialize_map(Some(m.len()))?;
for (k, v) in m {
out.serialize_entry(&Unchecked(k), &Unchecked(v))?;
}
out.end()
}
Value::Tagged(t) => {
let mut out = s.serialize_map(Some(1))?;
out.serialize_entry(&TagText(&t.tag), &Unchecked(&t.value))?;
out.end()
}
}
}
enum Payload<'a> {
Value(&'a Value),
Sequence(&'a Sequence),
Mapping(&'a Mapping),
Number(&'a Number),
}
impl ser::Serialize for Payload<'_> {
fn serialize<S: ser::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
clear_slot();
match self {
Payload::Value(v) => checked(v, s),
Payload::Sequence(q) => serialize_sequence(q, s),
Payload::Mapping(m) => serialize_mapping(m, s),
Payload::Number(n) => serialize_number(n, s),
}
}
}
fn clear_slot() {
if let Ok(Some(left)) = SER_SLOT.try_with(Cell::take) {
super::walk::drop_value(left);
}
}
struct SlotClear;
impl Drop for SlotClear {
fn drop(&mut self) {
clear_slot();
}
}
#[must_use = "the mark is restored when the guard drops"]
pub(crate) fn arm() -> Armed {
let prev = OURS.try_with(|o| o.replace(true)).unwrap_or(false);
Armed { prev }
}
pub(crate) struct Armed {
prev: bool,
}
impl Drop for Armed {
fn drop(&mut self) {
let _ = OURS.try_with(|o| o.set(self.prev));
}
}
pub(crate) fn disarm() {
let _ = OURS.try_with(|o| o.set(false));
}
fn armed() -> bool {
OURS.try_with(|o| o.replace(false)).unwrap_or(false)
}
fn to_value_serializer<S>() -> bool {
let name = std::any::type_name::<S>();
name == std::any::type_name::<crate::ser::build::ToValue>()
|| name == std::any::type_name::<crate::ser::stream::Streamer<'static>>()
}
fn document_serializer<S>() -> bool {
let name = std::any::type_name::<S>();
let ours = std::any::type_name::<&mut crate::ser::Serializer<()>>();
ours.strip_suffix("<()>")
.and_then(|prefix| name.strip_prefix(prefix))
.is_some_and(|rest| rest.starts_with('<'))
}
pub(crate) fn take_handed_over() -> Option<Value> {
SER_SLOT.try_with(Cell::take).ok().flatten()
}
fn hand_over<S: ser::Serializer>(
s: S,
payload: &Payload<'_>,
clone: impl FnOnce() -> Value,
) -> Result<S::Ok, S::Error> {
let _clear = SlotClear;
match SER_SLOT.try_with(|slot| slot.replace(Some(clone()))) {
Ok(Some(left)) => super::walk::drop_value(left),
Ok(None) => {}
Err(_) => return Err(ser::Error::custom(TlsGone)),
}
s.serialize_newtype_struct(VALUE_MAGIC, payload)
}
fn ours<S>() -> bool {
(to_value_serializer::<S>() && armed()) || document_serializer::<S>()
}
impl ser::Serialize for Value {
fn serialize<S: ser::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if ours::<S>() {
return hand_over(s, &Payload::Value(self), || super::walk::clone_value(self));
}
checked(self, s)
}
}
impl ser::Serialize for Mapping {
fn serialize<S: ser::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if ours::<S>() {
return hand_over(s, &Payload::Mapping(self), || Value::Mapping(self.clone()));
}
serialize_mapping(self, s)
}
}
impl ser::Serialize for Sequence {
fn serialize<S: ser::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if ours::<S>() {
return hand_over(s, &Payload::Sequence(self), || {
Value::Sequence(self.clone())
});
}
serialize_sequence(self, s)
}
}
fn serialize_mapping<S: ser::Serializer>(m: &Mapping, s: S) -> Result<S::Ok, S::Error> {
let limit = serialize_limit().saturating_sub(1);
let deep = m.iter().any(|(k, v)| {
super::walk::serde_depth(k, limit) > limit || super::walk::serde_depth(v, limit) > limit
});
if deep {
return Err(too_deep());
}
let mut out = s.serialize_map(Some(m.len()))?;
for (k, v) in m {
out.serialize_entry(&Unchecked(k), &Unchecked(v))?;
}
out.end()
}
fn serialize_sequence<S: ser::Serializer>(q: &Sequence, s: S) -> Result<S::Ok, S::Error> {
let limit = serialize_limit().saturating_sub(1);
if q.iter().any(|v| super::walk::serde_depth(v, limit) > limit) {
return Err(too_deep());
}
let mut out = s.serialize_seq(Some(q.len()))?;
for item in q.iter() {
out.serialize_element(&Unchecked(item))?;
}
out.end()
}
impl ser::Serialize for Number {
fn serialize<S: ser::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if ours::<S>() {
return hand_over(s, &Payload::Number(self), || Value::Number(self.clone()));
}
serialize_number(self, s)
}
}
fn serialize_number<S: ser::Serializer>(n: &Number, s: S) -> Result<S::Ok, S::Error> {
if let Some(f) = n.as_float() {
s.serialize_f64(f)
} else if let Some(u) = n.as_u64() {
s.serialize_u64(u)
} else if let Some(i) = n.as_i64() {
s.serialize_i64(i)
} else if let Some(u) = n.as_u128() {
s.serialize_u128(u)
} else if let Some(i) = n.as_i128() {
s.serialize_i128(i)
} else {
s.serialize_str(&n.to_string())
}
}
struct TagText<'a>(&'a super::Tag);
impl ser::Serialize for TagText<'_> {
fn serialize<S: ser::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.collect_str(self.0)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
use super::*;
struct Panics;
impl<'de> Visitor<'de> for Panics {
type Value = ();
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("nothing")
}
#[allow(clippy::panic)]
fn visit_newtype_struct<D: de::Deserializer<'de>>(self, _: D) -> Result<(), D::Error> {
panic!("visitor panicked");
}
}
#[test]
fn payload_not_taken_is_plain_data_and_clears_the_slot() {
let wide: Number = crate::from_str("-123456789012345678901234567890123456789012").unwrap();
let two = Number::from(2);
let q: Sequence = vec![Value::from(1)].into();
let mut m = Mapping::new();
m.insert(Value::from("a"), Value::from(true));
let cases = [
(Payload::Number(&wide), format!("\"{wide}\"")),
(Payload::Number(&two), "2".to_owned()),
(Payload::Sequence(&q), "[1]".to_owned()),
(Payload::Mapping(&m), "{\"a\":true}".to_owned()),
(Payload::Value(&Value::Null), "null".to_owned()),
];
for (payload, want) in cases {
SER_SLOT.with(|slot| slot.set(Some(Value::from("left"))));
assert_eq!(serde_json::to_string(&payload).unwrap(), want);
assert!(SER_SLOT.with(Cell::take).is_none());
}
}
#[test]
fn hand_off_never_leaves_a_value_behind() {
let unwound = std::panic::catch_unwind(|| hand_off(Value::from(1), Panics));
assert!(unwound.is_err());
assert!(SLOT.with(Cell::take).is_none());
assert!(hand_off(Value::from(1), ValueVisitor).is_ok());
assert!(hand_off(Value::from(2), serde::de::IgnoredAny).is_ok());
assert!(SLOT.with(Cell::take).is_none());
}
}