use crate::value::{Mapping, MappingAny, Number, Tag, TaggedValue, Value};
impl sval::Value for Value {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
match self {
Value::Null => stream.null(),
Value::Bool(b) => stream.bool(*b),
Value::Number(n) => n.stream(stream),
Value::String(s) => stream.value(s.as_str()),
Value::Sequence(items) => stream_seq(items, stream),
Value::Mapping(m) => m.stream(stream),
Value::Tagged(t) => t.stream(stream),
}
}
}
impl sval::Value for Number {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
match self {
Number::Integer(i) => stream.i64(*i),
Number::Float(f) => stream.f64(*f),
}
}
}
impl sval::Value for Mapping {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
stream.map_begin(Some(self.len()))?;
for (k, v) in self.iter() {
stream.map_key_begin()?;
stream.value(k.as_str())?;
stream.map_key_end()?;
stream.map_value_begin()?;
stream.value(v)?;
stream.map_value_end()?;
}
stream.map_end()
}
}
impl sval::Value for MappingAny {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
stream.map_begin(Some(self.len()))?;
for (k, v) in self.iter() {
stream.map_key_begin()?;
stream.value(k)?;
stream.map_key_end()?;
stream.map_value_begin()?;
stream.value(v)?;
stream.map_value_end()?;
}
stream.map_end()
}
}
impl sval::Value for Tag {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
stream.value(self.as_str())
}
}
impl sval::Value for TaggedValue {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
let label = sval::Label::new_computed(self.tag().as_str());
stream.tagged_begin(None, Some(&label), None)?;
stream.value(self.value())?;
stream.tagged_end(None, Some(&label), None)
}
}
fn stream_seq<'sval, S: sval::Stream<'sval> + ?Sized>(
items: &'sval [Value],
stream: &mut S,
) -> sval::Result {
stream.seq_begin(Some(items.len()))?;
for item in items {
stream.seq_value_begin()?;
stream.value(item)?;
stream.seq_value_end()?;
}
stream.seq_end()
}
pub fn to_sval_writer<'sval, S: sval::Stream<'sval> + ?Sized>(
stream: &mut S,
value: &'sval Value,
) -> sval::Result {
sval::Value::stream(value, stream)
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SvalConfig {
pub coerce_non_finite_to_null: bool,
}
pub fn to_sval_writer_with_config<'sval, S: sval::Stream<'sval> + ?Sized>(
stream: &mut S,
value: &'sval Value,
config: &SvalConfig,
) -> sval::Result {
if config.coerce_non_finite_to_null
&& let Value::Number(Number::Float(f)) = value
&& !f.is_finite()
{
return stream.null();
}
sval::Value::stream(value, stream)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::{Mapping, Number, Sequence};
#[derive(Default)]
struct Recorder(Vec<String>);
impl sval::Stream<'_> for Recorder {
fn null(&mut self) -> sval::Result {
self.0.push("null".into());
Ok(())
}
fn bool(&mut self, v: bool) -> sval::Result {
self.0.push(format!("bool({v})"));
Ok(())
}
fn i64(&mut self, v: i64) -> sval::Result {
self.0.push(format!("i64({v})"));
Ok(())
}
fn f64(&mut self, v: f64) -> sval::Result {
self.0.push(format!("f64({v})"));
Ok(())
}
fn text_begin(&mut self, _: Option<usize>) -> sval::Result {
self.0.push("text_begin".into());
Ok(())
}
fn text_fragment_computed(&mut self, fragment: &str) -> sval::Result {
self.0.push(format!("text({fragment})"));
Ok(())
}
fn text_end(&mut self) -> sval::Result {
self.0.push("text_end".into());
Ok(())
}
fn map_begin(&mut self, _: Option<usize>) -> sval::Result {
self.0.push("map_begin".into());
Ok(())
}
fn map_end(&mut self) -> sval::Result {
self.0.push("map_end".into());
Ok(())
}
fn map_key_begin(&mut self) -> sval::Result {
self.0.push("map_key_begin".into());
Ok(())
}
fn map_key_end(&mut self) -> sval::Result {
self.0.push("map_key_end".into());
Ok(())
}
fn map_value_begin(&mut self) -> sval::Result {
self.0.push("map_value_begin".into());
Ok(())
}
fn map_value_end(&mut self) -> sval::Result {
self.0.push("map_value_end".into());
Ok(())
}
fn seq_begin(&mut self, _: Option<usize>) -> sval::Result {
self.0.push("seq_begin".into());
Ok(())
}
fn seq_end(&mut self) -> sval::Result {
self.0.push("seq_end".into());
Ok(())
}
fn seq_value_begin(&mut self) -> sval::Result {
self.0.push("seq_value_begin".into());
Ok(())
}
fn seq_value_end(&mut self) -> sval::Result {
self.0.push("seq_value_end".into());
Ok(())
}
}
#[test]
fn null_streams_null_event() {
let mut r = Recorder::default();
sval::Value::stream(&Value::Null, &mut r).unwrap();
assert_eq!(r.0, vec!["null".to_string()]);
}
#[test]
fn bool_streams_bool_event() {
let mut r = Recorder::default();
sval::Value::stream(&Value::Bool(true), &mut r).unwrap();
assert_eq!(r.0, vec!["bool(true)".to_string()]);
}
#[test]
fn integer_streams_i64_event() {
let mut r = Recorder::default();
sval::Value::stream(&Value::Number(Number::Integer(42)), &mut r).unwrap();
assert_eq!(r.0, vec!["i64(42)".to_string()]);
}
#[test]
fn float_streams_f64_event() {
let mut r = Recorder::default();
sval::Value::stream(&Value::Number(Number::Float(2.5)), &mut r).unwrap();
assert_eq!(r.0, vec!["f64(2.5)".to_string()]);
}
#[test]
fn sequence_streams_seq_events() {
let mut r = Recorder::default();
let v: Sequence = vec![Value::Bool(true), Value::Bool(false)];
sval::Value::stream(&Value::Sequence(v), &mut r).unwrap();
let s = r.0.join(",");
assert!(s.contains("seq_begin"));
assert!(s.contains("bool(true)"));
assert!(s.contains("bool(false)"));
assert!(s.contains("seq_end"));
}
#[test]
fn tagged_value_wraps_inner() {
use crate::value::{Tag, TaggedValue};
let mut r = Recorder::default();
let tv = TaggedValue::new(Tag::new("!timestamp"), Value::String("2026".into()));
sval::Value::stream(&Value::Tagged(Box::new(tv)), &mut r).unwrap();
let s = r.0.join(",");
assert!(s.contains("text(2026)"));
}
#[test]
fn mapping_any_streams_value_keyed_events() {
use crate::value::{MappingAny, Number};
let mut r = Recorder::default();
let mut m = MappingAny::new();
let _ = m.insert(Value::Number(Number::Integer(7)), Value::Bool(true));
sval::Value::stream(&m, &mut r).unwrap();
let s = r.0.join(",");
assert!(s.contains("map_begin"));
assert!(s.contains("i64(7)"));
assert!(s.contains("bool(true)"));
assert!(s.contains("map_end"));
}
#[test]
fn number_impl_streams_directly() {
let mut r = Recorder::default();
sval::Value::stream(&Number::Integer(99), &mut r).unwrap();
assert_eq!(r.0, vec!["i64(99)".to_string()]);
}
#[test]
fn to_sval_writer_helper_streams() {
let mut r = Recorder::default();
let v = Value::Bool(false);
to_sval_writer(&mut r, &v).unwrap();
assert_eq!(r.0, vec!["bool(false)".to_string()]);
}
#[test]
fn tag_streams_as_text() {
let mut r = Recorder::default();
let t = Tag::new("!timestamp");
sval::Value::stream(&t, &mut r).unwrap();
let s = r.0.join(",");
assert!(s.contains("text(!timestamp)"));
}
#[test]
fn nan_coerced_to_null_via_config() {
let mut r = Recorder::default();
let v = Value::Number(Number::Float(f64::NAN));
let cfg = SvalConfig {
coerce_non_finite_to_null: true,
};
to_sval_writer_with_config(&mut r, &v, &cfg).unwrap();
assert_eq!(r.0, vec!["null".to_string()]);
}
#[test]
fn finite_float_passes_through_config() {
let mut r = Recorder::default();
let v = Value::Number(Number::Float(2.5));
let cfg = SvalConfig {
coerce_non_finite_to_null: true,
};
to_sval_writer_with_config(&mut r, &v, &cfg).unwrap();
assert_eq!(r.0, vec!["f64(2.5)".to_string()]);
}
#[test]
fn null_string_streams_via_value_route() {
let mut r = Recorder::default();
sval::Value::stream(&Value::String("hello".into()), &mut r).unwrap();
let s = r.0.join(",");
assert!(s.contains("text(hello)"));
}
#[test]
fn mapping_streams_map_events() {
let mut r = Recorder::default();
let mut m = Mapping::new();
let _ = m.insert("k".to_string(), Value::Bool(true));
sval::Value::stream(&Value::Mapping(m), &mut r).unwrap();
let s = r.0.join(",");
assert!(s.contains("map_begin"));
assert!(s.contains("map_key_begin"));
assert!(s.contains("text(k)"));
assert!(s.contains("map_value_begin"));
assert!(s.contains("bool(true)"));
assert!(s.contains("map_end"));
}
}