use std::collections::HashSet;
use serde::ser::{self, Serialize};
use oneyaml_core::{Limits, code};
use super::build::{self, ToValue, f32_value, is_scalar};
use crate::emit::scalar::Rules;
use crate::emit::{Emitter, Slot, Stacks, Start, document_start};
use crate::error::Error;
use crate::value::{DepthExceeded, Number, VALUE_MAGIC, Value, arm, disarm, take_handed_over};
pub(crate) struct DocState {
pub(crate) out: String,
variants: Vec<(&'static str, bool)>,
max_depth: usize,
max_typed: usize,
too_deep: bool,
failed: Option<Error>,
poisoned: bool,
stacks: Stacks,
drops: Vec<Value>,
}
impl DocState {
pub(crate) fn new(limits: &Limits) -> Self {
Self {
out: String::new(),
variants: Vec::new(),
max_depth: limits.get_max_depth(),
max_typed: limits.get_max_typed_depth(),
too_deep: false,
failed: None,
poisoned: false,
stacks: Stacks::default(),
drops: Vec::new(),
}
}
fn begin(&mut self, rules: &Rules, start: Start) -> At {
self.out.clear();
self.variants.clear();
self.too_deep = false;
self.failed = None;
self.poisoned = false;
let slot = document_start(&mut self.out, rules, start);
At {
slot,
depth: 0,
typed: ToValue::root(self.max_typed),
}
}
pub(crate) fn document<T: ?Sized + Serialize>(
&mut self,
rules: &Rules,
start: Start,
value: &T,
) -> Result<(), Error> {
let at = self.begin(rules, start);
emit(value, self, rules, at)?;
self.finish()
}
pub(crate) fn root<'b>(&'b mut self, rules: &'b Rules, start: Start) -> Streamer<'b> {
let at = self.begin(rules, start);
Streamer {
st: self,
rules,
at,
}
}
pub(crate) fn finish(&mut self) -> Result<(), Error> {
if self.poisoned {
return Err(swallowed());
}
if self.too_deep {
return Err(DepthExceeded {
limit: self.max_depth,
}
.into_error());
}
if let Some(e) = self.failed.take() {
return Err(e);
}
if !self.variants.is_empty() {
debug_assert!(false, "enum variants left without content");
return Err(Error::internal("enum variants left without content", None));
}
self.out.push('\n');
Ok(())
}
fn deeper(&mut self, depth: usize) {
if depth > self.max_depth {
self.too_deep = true;
}
}
fn live(&self) -> bool {
!self.too_deep
}
fn record(&mut self, r: Result<(), Error>) {
if let Err(e) = r {
if self.failed.is_none() {
self.failed = Some(e);
}
}
}
fn guard<T>(&mut self, r: Result<T, Error>) -> Result<T, Error> {
if r.is_err() {
self.poisoned = true;
}
r
}
}
fn swallowed() -> Error {
Error::new(
code::INVALID_SERIALIZE_CALLS,
"the value's Serialize impl went on after an error; the document is refused",
None,
)
}
#[derive(Clone, Copy, Debug)]
struct At {
slot: Slot,
depth: usize,
typed: ToValue,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Shape {
Str,
Seq,
Map,
Scalar,
Other,
}
impl Shape {
fn of(v: &Value) -> Self {
match v {
Value::String(_) => Shape::Str,
Value::Sequence(_) => Shape::Seq,
Value::Mapping(_) => Shape::Map,
Value::Null | Value::Bool(_) | Value::Number(_) => Shape::Scalar,
_ => Shape::Other,
}
}
}
#[derive(Clone, Copy)]
enum Below<'v> {
Leaf,
Collection,
Value(&'v Value),
}
fn value_depth(stacks: &mut Stacks, v: &Value, cap: usize) -> usize {
match v.untag_ref() {
Value::Sequence(_) | Value::Mapping(_) => stacks.depth(v, cap),
_ => 0,
}
}
#[derive(Clone, Copy, Debug)]
struct Placed {
slot: Slot,
tagged: bool,
depth: usize,
}
fn open(st: &mut DocState, rules: &Rules, at: At, shape: Shape, below: Below<'_>) -> Placed {
let mut wrappers = 0usize;
let mut taggable = match shape {
Shape::Str | Shape::Seq | Shape::Map => true,
Shape::Scalar => rules.tagged_scalar_variants(),
Shape::Other => false,
};
for (name, form) in st.variants.iter_mut().rev() {
*form = taggable && !name.is_empty();
taggable = !*form;
if !*form {
wrappers = wrappers.saturating_add(1);
}
}
let depth = at.depth.saturating_add(wrappers);
let inner = match below {
Below::Leaf => 0,
Below::Collection => 1,
Below::Value(v) => value_depth(&mut st.stacks, v, st.max_depth.saturating_sub(depth)),
};
if wrappers > 0 || inner > 0 {
st.deeper(depth.saturating_add(inner));
}
let mut p = Placed {
slot: at.slot,
tagged: false,
depth,
};
if st.variants.is_empty() {
return p;
}
if !st.live() {
st.variants.clear();
return p;
}
let mut em = Emitter::new(&mut st.out, rules);
let mut failed = Ok(());
for (name, tag) in st.variants.drain(..) {
if tag {
if let Err(e) = em.local_tag(p.slot, name) {
failed = Err(e);
}
p.tagged = true;
} else {
let col = p.slot.map_col;
em.open_block(p.slot, p.tagged, col);
p.slot = em.str_key(name, col);
p.tagged = false;
}
}
st.record(failed);
p
}
fn emit<T: ?Sized + Serialize>(
v: &T,
st: &mut DocState,
rules: &Rules,
at: At,
) -> Result<(), Error> {
let r = {
let _armed = arm();
v.serialize(Streamer {
st: &mut *st,
rules,
at,
})
};
st.guard(r)
}
fn frame(st: &mut DocState, rules: &Rules, at: At, shape: Shape) -> Result<Frame, Error> {
let child = at.typed.nested()?;
let p = open(st, rules, at, shape, Below::Collection);
let depth = p.depth.saturating_add(1);
let (col, empty) = if shape == Shape::Seq {
(p.slot.seq_col, "[]")
} else {
(p.slot.map_col, "{}")
};
Ok(Frame {
slot: p.slot,
tagged: p.tagged,
col,
depth,
child,
count: 0,
empty,
keys: Keys::default(),
key: None,
})
}
fn handed(st: &mut DocState, rules: &Rules, at: At, v: &Value) {
let p = open(st, rules, at, Shape::of(v), Below::Value(v));
if !st.live() {
return;
}
let r = match v {
Value::String(text) => {
Emitter::new(&mut st.out, rules).string(p.slot, p.tagged, text);
Ok(())
}
Value::Null | Value::Bool(_) | Value::Number(_) => {
Emitter::new(&mut st.out, rules).plain(p.slot, p.tagged, v)
}
_ => {
let mut tasks = st.stacks.take_tasks();
let r = Emitter::new(&mut st.out, rules).run_with(v, p.slot, p.tagged, &mut tasks);
st.stacks.put_tasks(tasks);
r
}
};
st.record(r);
}
pub(crate) struct Frame {
slot: Slot,
tagged: bool,
col: usize,
depth: usize,
child: ToValue,
count: usize,
empty: &'static str,
keys: Keys,
key: Option<Value>,
}
impl Frame {
fn entry(&mut self, st: &mut DocState, rules: &Rules) -> bool {
let first = self.count == 0;
self.count = self.count.saturating_add(1);
if !st.live() {
return false;
}
let mut em = Emitter::new(&mut st.out, rules);
if first {
em.open_block(self.slot, self.tagged, self.col);
} else {
em.newline(self.col);
}
true
}
fn at(&self, slot: Slot) -> At {
At {
slot,
depth: self.depth,
typed: self.child,
}
}
pub(crate) fn element<T: ?Sized + Serialize>(
&mut self,
st: &mut DocState,
rules: &Rules,
v: &T,
) -> Result<(), Error> {
let index = self.count;
if self.entry(st, rules) {
Emitter::new(&mut st.out, rules).push_str("- ");
}
let at = self.at(Slot::inline(self.col, rules));
emit(v, st, rules, at).map_err(|e| e.in_index(index))
}
pub(crate) fn field<T: ?Sized + Serialize>(
&mut self,
st: &mut DocState,
rules: &Rules,
name: &'static str,
v: &T,
) -> Result<(), Error> {
let slot = if self.entry(st, rules) {
Emitter::new(&mut st.out, rules).str_key(name, self.col)
} else {
Slot::value(self.col, rules)
};
emit(v, st, rules, self.at(slot)).map_err(|e| e.in_key(name))?;
if self.keys.insert_name(name) {
Ok(())
} else {
st.guard(Err(build::duplicate_key(Some(name))))
}
}
pub(crate) fn key<T: ?Sized + Serialize>(
&mut self,
st: &mut DocState,
k: &T,
) -> Result<(), Error> {
if self.key.is_some() {
return st.guard(Err(build::key_without_value()));
}
let k = build::build(k, self.child);
self.key = Some(st.guard(k)?);
Ok(())
}
pub(crate) fn value<T: ?Sized + Serialize>(
&mut self,
st: &mut DocState,
rules: &Rules,
v: &T,
) -> Result<(), Error> {
let Some(k) = self.key.take() else {
return st.guard(Err(build::value_without_key()));
};
let d = value_depth(&mut st.stacks, &k, st.max_depth.saturating_sub(self.depth));
if d > 0 {
st.deeper(self.depth.saturating_add(d));
}
let slot = if self.entry(st, rules) {
let mut tasks = st.stacks.take_tasks();
let written = Emitter::new(&mut st.out, rules).key(&k, self.col, &mut tasks);
st.stacks.put_tasks(tasks);
written.unwrap_or_else(|e| {
st.record(Err(e));
Slot::value(self.col, rules)
})
} else {
Slot::value(self.col, rules)
};
if let Err(e) = emit(v, st, rules, self.at(slot)) {
return Err(match k.as_str() {
Some(n) => e.in_key(n),
None => e,
});
}
match self.keys.insert_value(k) {
None => Ok(()),
Some(k) => st.guard(Err(build::duplicate_key(k.as_str()))),
}
}
pub(crate) fn end(self, st: &mut DocState, rules: &Rules) -> Result<(), Error> {
if self.key.is_some() {
return st.guard(Err(build::key_without_value()));
}
if self.count == 0 && st.live() {
Emitter::new(&mut st.out, rules).inline_text(self.slot, self.tagged, self.empty);
}
Ok(())
}
}
const INLINE_NAMES: usize = 8;
const LINEAR_KEYS: usize = 8;
#[derive(Default)]
struct Keys {
names: [&'static str; INLINE_NAMES],
named: usize,
name_set: Option<HashSet<&'static str>>,
values: Vec<Value>,
digests: [Option<u64>; LINEAR_KEYS],
value_set: Option<HashSet<KeyEntry>>,
}
struct KeyEntry {
digest: u64,
key: Value,
}
impl PartialEq for KeyEntry {
fn eq(&self, other: &Self) -> bool {
self.digest == other.digest && self.key == other.key
}
}
impl Eq for KeyEntry {}
impl std::hash::Hash for KeyEntry {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u64(self.digest);
}
}
impl Keys {
fn insert_name(&mut self, name: &'static str) -> bool {
if let Some(set) = &mut self.name_set {
return set.insert(name);
}
let held = self.names.get(..self.named).unwrap_or(&[]);
if held.contains(&name) {
return false;
}
if let Some(slot) = self.names.get_mut(self.named) {
*slot = name;
self.named = self.named.saturating_add(1);
return true;
}
let mut set: HashSet<&'static str> = self.names.iter().copied().collect();
set.insert(name);
self.name_set = Some(set);
true
}
fn insert_value(&mut self, key: Value) -> Option<Value> {
if let Some(set) = &mut self.value_set {
let e = KeyEntry {
digest: crate::value::walk::digest(&key),
key,
};
if set.contains(&e) {
return Some(e.key);
}
set.insert(e);
return None;
}
let digest = match key {
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => None,
_ => Some(crate::value::walk::digest(&key)),
};
let held = self.values.iter().zip(self.digests.iter());
let seen = held.into_iter().any(|(k, d)| match (digest, d) {
(None, None) => *k == key,
(Some(a), Some(b)) => a == *b && *k == key,
_ => false,
});
if seen {
return Some(key);
}
if let Some(slot) = self.digests.get_mut(self.values.len()) {
*slot = digest;
self.values.push(key);
return None;
}
let mut set: HashSet<KeyEntry> = std::mem::take(&mut self.values)
.into_iter()
.zip(self.digests)
.map(|(key, digest)| KeyEntry {
digest: digest.unwrap_or_else(|| crate::value::walk::digest(&key)),
key,
})
.collect();
set.insert(KeyEntry {
digest: digest.unwrap_or_else(|| crate::value::walk::digest(&key)),
key,
});
self.value_set = Some(set);
None
}
}
pub(crate) struct Streamer<'b> {
st: &'b mut DocState,
rules: &'b Rules,
at: At,
}
impl<'b> Streamer<'b> {
fn nested(&self) -> Result<At, Error> {
Ok(At {
typed: self.at.typed.nested()?,
..self.at
})
}
fn string(self, s: &str) -> Result<(), Error> {
let p = open(self.st, self.rules, self.at, Shape::Str, Below::Leaf);
if self.st.live() {
Emitter::new(&mut self.st.out, self.rules).string(p.slot, p.tagged, s);
}
Ok(())
}
fn plain(self, v: &Value) -> Result<(), Error> {
let p = open(self.st, self.rules, self.at, Shape::Scalar, Below::Leaf);
if self.st.live() {
let r = Emitter::new(&mut self.st.out, self.rules).plain(p.slot, p.tagged, v);
self.st.record(r);
}
Ok(())
}
fn number(self, n: Number) -> Result<(), Error> {
let p = open(self.st, self.rules, self.at, Shape::Scalar, Below::Leaf);
if self.st.live() {
Emitter::new(&mut self.st.out, self.rules).separate(p.slot, p.tagged);
let r = self.rules.write_number(&mut self.st.out, &n);
self.st.record(r);
}
Ok(())
}
fn collection(self, shape: Shape) -> Result<Compound<'b>, Error> {
let Streamer { st, rules, at } = self;
let frame = frame(st, rules, at, shape)?;
Ok(Compound { st, rules, frame })
}
fn variant_collection(self, name: &'static str, shape: Shape) -> Result<Compound<'b>, Error> {
let Streamer { st, rules, at } = self;
let pending = st.variants.len();
st.variants.push((name, false));
match frame(st, rules, at, shape) {
Ok(frame) => Ok(Compound { st, rules, frame }),
Err(e) => {
st.variants.truncate(pending);
Err(e)
}
}
}
pub(crate) fn root_frame(self, name: Option<&'static str>, seq: bool) -> Result<Frame, Error> {
if let Some(name) = name {
self.st.variants.push((name, false));
}
let shape = if seq { Shape::Seq } else { Shape::Map };
frame(self.st, self.rules, self.at, shape)
}
}
impl<'b> ser::Serializer for Streamer<'b> {
type Ok = ();
type Error = Error;
type SerializeSeq = Compound<'b>;
type SerializeTuple = Compound<'b>;
type SerializeTupleStruct = Compound<'b>;
type SerializeTupleVariant = Compound<'b>;
type SerializeMap = Compound<'b>;
type SerializeStruct = Compound<'b>;
type SerializeStructVariant = Compound<'b>;
fn serialize_bool(self, v: bool) -> Result<(), Error> {
disarm();
self.plain(&Value::Bool(v))
}
fn serialize_i8(self, v: i8) -> Result<(), Error> {
disarm();
self.number(Number::from(v))
}
fn serialize_i16(self, v: i16) -> Result<(), Error> {
disarm();
self.number(Number::from(v))
}
fn serialize_i32(self, v: i32) -> Result<(), Error> {
disarm();
self.number(Number::from(v))
}
fn serialize_i64(self, v: i64) -> Result<(), Error> {
disarm();
self.number(Number::from(v))
}
fn serialize_i128(self, v: i128) -> Result<(), Error> {
disarm();
self.number(Number::from(v))
}
fn serialize_u8(self, v: u8) -> Result<(), Error> {
disarm();
self.number(Number::from(v))
}
fn serialize_u16(self, v: u16) -> Result<(), Error> {
disarm();
self.number(Number::from(v))
}
fn serialize_u32(self, v: u32) -> Result<(), Error> {
disarm();
self.number(Number::from(v))
}
fn serialize_u64(self, v: u64) -> Result<(), Error> {
disarm();
self.number(Number::from(v))
}
fn serialize_u128(self, v: u128) -> Result<(), Error> {
disarm();
self.number(Number::from(v))
}
fn serialize_f32(self, v: f32) -> Result<(), Error> {
disarm();
self.number(Number::from(f32_value(v)))
}
fn serialize_f64(self, v: f64) -> Result<(), Error> {
disarm();
self.number(Number::from(v))
}
fn serialize_char(self, v: char) -> Result<(), Error> {
disarm();
let mut buf = [0u8; 4];
self.string(v.encode_utf8(&mut buf))
}
fn serialize_str(self, v: &str) -> Result<(), Error> {
disarm();
self.string(v)
}
fn serialize_bytes(self, v: &[u8]) -> Result<(), Error> {
disarm();
let mut c = self.collection(Shape::Seq)?;
for b in v {
c.frame.element(c.st, c.rules, b)?;
}
c.frame.end(c.st, c.rules)
}
fn serialize_none(self) -> Result<(), Error> {
disarm();
self.plain(&Value::Null)
}
fn serialize_some<T: ?Sized + Serialize>(self, v: &T) -> Result<(), Error> {
disarm();
let at = self.nested()?;
emit(v, self.st, self.rules, at)
}
fn serialize_unit(self) -> Result<(), Error> {
disarm();
self.plain(&Value::Null)
}
fn serialize_unit_struct(self, _: &'static str) -> Result<(), Error> {
disarm();
self.plain(&Value::Null)
}
fn serialize_unit_variant(
self,
_: &'static str,
_: u32,
name: &'static str,
) -> Result<(), Error> {
disarm();
self.string(name)
}
fn serialize_newtype_struct<T: ?Sized + Serialize>(
self,
name: &'static str,
v: &T,
) -> Result<(), Error> {
disarm();
if name == VALUE_MAGIC {
if let Some(value) = take_handed_over() {
if !is_scalar(&value) {
self.nested()?;
}
handed(self.st, self.rules, self.at, &value);
crate::value::walk::drop_value_with(value, &mut self.st.drops);
return Ok(());
}
let at = self.nested()?;
return v.serialize(Streamer {
st: self.st,
rules: self.rules,
at,
});
}
let at = self.nested()?;
emit(v, self.st, self.rules, at)
}
fn serialize_newtype_variant<T: ?Sized + Serialize>(
self,
_: &'static str,
_: u32,
name: &'static str,
v: &T,
) -> Result<(), Error> {
disarm();
let at = self.nested()?;
let pending = self.st.variants.len();
self.st.variants.push((name, false));
let r = emit(v, self.st, self.rules, at);
if r.is_err() {
self.st.variants.truncate(pending);
}
r.map_err(|e| e.in_key(name))
}
fn serialize_seq(self, _: Option<usize>) -> Result<Compound<'b>, Error> {
disarm();
self.collection(Shape::Seq)
}
fn serialize_tuple(self, _: usize) -> Result<Compound<'b>, Error> {
disarm();
self.collection(Shape::Seq)
}
fn serialize_tuple_struct(self, _: &'static str, _: usize) -> Result<Compound<'b>, Error> {
disarm();
self.collection(Shape::Seq)
}
fn serialize_tuple_variant(
self,
_: &'static str,
_: u32,
name: &'static str,
_: usize,
) -> Result<Compound<'b>, Error> {
disarm();
self.variant_collection(name, Shape::Seq)
}
fn serialize_map(self, _: Option<usize>) -> Result<Compound<'b>, Error> {
disarm();
self.collection(Shape::Map)
}
fn serialize_struct(self, _: &'static str, _: usize) -> Result<Compound<'b>, Error> {
disarm();
self.collection(Shape::Map)
}
fn serialize_struct_variant(
self,
_: &'static str,
_: u32,
name: &'static str,
_: usize,
) -> Result<Compound<'b>, Error> {
disarm();
self.variant_collection(name, Shape::Map)
}
}
pub(crate) struct Compound<'b> {
st: &'b mut DocState,
rules: &'b Rules,
frame: Frame,
}
macro_rules! element_compound {
($($trait:ident::$method:ident),* $(,)?) => {$(
impl ser::$trait for Compound<'_> {
type Ok = ();
type Error = Error;
fn $method<T: ?Sized + Serialize>(&mut self, v: &T) -> Result<(), Error> {
self.frame.element(self.st, self.rules, v)
}
fn end(self) -> Result<(), Error> {
self.frame.end(self.st, self.rules)
}
}
)*};
}
element_compound!(
SerializeSeq::serialize_element,
SerializeTuple::serialize_element,
SerializeTupleStruct::serialize_field,
SerializeTupleVariant::serialize_field,
);
macro_rules! field_compound {
($($trait:ident),* $(,)?) => {$(
impl ser::$trait for Compound<'_> {
type Ok = ();
type Error = Error;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
name: &'static str,
v: &T,
) -> Result<(), Error> {
self.frame.field(self.st, self.rules, name, v)
}
fn end(self) -> Result<(), Error> {
self.frame.end(self.st, self.rules)
}
}
)*};
}
field_compound!(SerializeStruct, SerializeStructVariant);
impl ser::SerializeMap for Compound<'_> {
type Ok = ();
type Error = Error;
fn serialize_key<T: ?Sized + Serialize>(&mut self, k: &T) -> Result<(), Error> {
self.frame.key(self.st, k)
}
fn serialize_value<T: ?Sized + Serialize>(&mut self, v: &T) -> Result<(), Error> {
self.frame.value(self.st, self.rules, v)
}
fn end(self) -> Result<(), Error> {
self.frame.end(self.st, self.rules)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
use super::*;
#[test]
fn keys_detect_duplicates_inline_and_in_sets() {
let mut k = Keys::default();
let names: Vec<&'static str> =
vec!["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"];
for n in &names {
assert!(k.insert_name(n));
}
assert!(k.name_set.is_some());
for n in &names {
assert!(!k.insert_name(n), "{n}");
}
let mut k = Keys::default();
for i in 0..20u32 {
assert!(k.insert_value(Value::from(i)).is_none());
assert_eq!(k.value_set.is_some(), i >= 8);
}
for i in 0..20u32 {
assert!(k.insert_value(Value::from(i)).is_some());
}
let mut k = Keys::default();
assert!(k.insert_value(Value::from("x")).is_none());
assert_eq!(k.insert_value(Value::from("x")), Some(Value::from("x")));
let a: Value = crate::from_str("{a: 1, b: 2}").unwrap();
let b: Value = crate::from_str("{b: 2, a: 1}").unwrap();
assert!(k.insert_value(a).is_none());
assert!(k.insert_value(b).is_some());
let mixed = |i: u32| {
if i % 2 == 0 {
Value::from(i)
} else {
Value::from(vec![Value::from(i)])
}
};
for n in [3u32, 8, 9, 20] {
let mut k = Keys::default();
for i in 0..n {
assert!(k.insert_value(mixed(i)).is_none(), "{n} {i}");
}
assert_eq!(k.value_set.is_some(), n > 8);
for i in 0..n {
assert!(k.insert_value(mixed(i)).is_some(), "{n} {i}");
}
assert!(k.insert_value(Value::from(vec![Value::from(0)])).is_none());
}
}
}
#[cfg(test)]
mod depth_tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
use super::*;
use crate::emit::output_rules;
use oneyaml_core::Settings;
#[test]
fn nothing_is_written_past_max_depth() {
let s = Settings::new().max_depth(2);
let rules = output_rules(&s).unwrap();
let mut doc = DocState::new(s.get_limits());
let tail: Vec<String> = vec!["x".repeat(100); 1000];
let value = (
vec![vec![vec![1]]],
&tail,
std::collections::BTreeMap::from([(1, &tail)]),
);
let e = doc.document(&rules, Start::First, &value).unwrap_err();
assert_eq!(e.code(), code::VALUE_DEPTH_EXCEEDED);
assert!(doc.out.len() < 16, "{:?}", doc.out);
let mut deep = Value::Null;
for _ in 0..10_000 {
let mut m = crate::value::Mapping::new();
m.insert(Value::from("a"), deep);
deep = Value::from(m);
}
let e = doc
.document(&rules, Start::First, &vec![&deep])
.unwrap_err();
assert_eq!(e.code(), code::VALUE_DEPTH_EXCEEDED);
assert_eq!(doc.out, "- ");
}
}