1use crate::value::{Mapping, MappingAny, Number, Tag, TaggedValue, Value};
68
69impl sval::Value for Value {
70 fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
71 match self {
72 Self::Null => stream.null(),
73 Self::Bool(b) => stream.bool(*b),
74 Self::Number(n) => n.stream(stream),
75 Self::String(s) => stream.value(s.as_str()),
76 Self::Sequence(items) => stream_seq(items, stream),
77 Self::Mapping(m) => m.stream(stream),
78 Self::Tagged(t) => t.stream(stream),
79 }
80 }
81}
82
83impl sval::Value for Number {
84 fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
85 match self {
86 Self::Integer(i) => stream.i64(*i),
87 #[cfg(feature = "lossless-u64")]
88 Self::Unsigned(u) => stream.u64(*u),
89 Self::Float(f) => stream.f64(*f),
90 }
91 }
92}
93
94impl sval::Value for Mapping {
95 fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
96 stream.map_begin(Some(self.len()))?;
97 for (k, v) in self {
98 stream.map_key_begin()?;
99 stream.value(k.as_str())?;
100 stream.map_key_end()?;
101 stream.map_value_begin()?;
102 stream.value(v)?;
103 stream.map_value_end()?;
104 }
105 stream.map_end()
106 }
107}
108
109impl sval::Value for MappingAny {
110 fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
111 stream.map_begin(Some(self.len()))?;
112 for (k, v) in self {
113 stream.map_key_begin()?;
114 stream.value(k)?;
115 stream.map_key_end()?;
116 stream.map_value_begin()?;
117 stream.value(v)?;
118 stream.map_value_end()?;
119 }
120 stream.map_end()
121 }
122}
123
124impl sval::Value for Tag {
125 fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
129 stream.value(self.as_str())
130 }
131}
132
133impl sval::Value for TaggedValue {
134 fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
135 let label = sval::Label::new_computed(self.tag().as_str());
140 stream.tagged_begin(None, Some(&label), None)?;
141 stream.value(self.value())?;
142 stream.tagged_end(None, Some(&label), None)
143 }
144}
145
146fn stream_seq<'sval, S: sval::Stream<'sval> + ?Sized>(
147 items: &'sval [Value],
148 stream: &mut S,
149) -> sval::Result {
150 stream.seq_begin(Some(items.len()))?;
151 for item in items {
152 stream.seq_value_begin()?;
153 stream.value(item)?;
154 stream.seq_value_end()?;
155 }
156 stream.seq_end()
157}
158
159pub fn to_sval_writer<'sval, S: sval::Stream<'sval> + ?Sized>(
177 stream: &mut S,
178 value: &'sval Value,
179) -> sval::Result {
180 sval::Value::stream(value, stream)
181}
182
183#[derive(Debug, Clone, Copy, Default)]
187pub struct SvalConfig {
188 pub coerce_non_finite_to_null: bool,
195}
196
197pub fn to_sval_writer_with_config<'sval, S: sval::Stream<'sval> + ?Sized>(
208 stream: &mut S,
209 value: &'sval Value,
210 config: &SvalConfig,
211) -> sval::Result {
212 if config.coerce_non_finite_to_null
213 && let Value::Number(Number::Float(f)) = value
214 && !f.is_finite()
215 {
216 return stream.null();
217 }
218 sval::Value::stream(value, stream)
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::value::{Mapping, Number, Sequence};
229
230 #[derive(Default)]
233 struct Recorder(Vec<String>);
234
235 impl sval::Stream<'_> for Recorder {
236 fn null(&mut self) -> sval::Result {
237 self.0.push("null".into());
238 Ok(())
239 }
240 fn bool(&mut self, v: bool) -> sval::Result {
241 self.0.push(format!("bool({v})"));
242 Ok(())
243 }
244 fn i64(&mut self, v: i64) -> sval::Result {
245 self.0.push(format!("i64({v})"));
246 Ok(())
247 }
248 fn f64(&mut self, v: f64) -> sval::Result {
249 self.0.push(format!("f64({v})"));
250 Ok(())
251 }
252 fn text_begin(&mut self, _: Option<usize>) -> sval::Result {
253 self.0.push("text_begin".into());
254 Ok(())
255 }
256 fn text_fragment_computed(&mut self, fragment: &str) -> sval::Result {
257 self.0.push(format!("text({fragment})"));
258 Ok(())
259 }
260 fn text_end(&mut self) -> sval::Result {
261 self.0.push("text_end".into());
262 Ok(())
263 }
264 fn map_begin(&mut self, _: Option<usize>) -> sval::Result {
265 self.0.push("map_begin".into());
266 Ok(())
267 }
268 fn map_end(&mut self) -> sval::Result {
269 self.0.push("map_end".into());
270 Ok(())
271 }
272 fn map_key_begin(&mut self) -> sval::Result {
273 self.0.push("map_key_begin".into());
274 Ok(())
275 }
276 fn map_key_end(&mut self) -> sval::Result {
277 self.0.push("map_key_end".into());
278 Ok(())
279 }
280 fn map_value_begin(&mut self) -> sval::Result {
281 self.0.push("map_value_begin".into());
282 Ok(())
283 }
284 fn map_value_end(&mut self) -> sval::Result {
285 self.0.push("map_value_end".into());
286 Ok(())
287 }
288 fn seq_begin(&mut self, _: Option<usize>) -> sval::Result {
289 self.0.push("seq_begin".into());
290 Ok(())
291 }
292 fn seq_end(&mut self) -> sval::Result {
293 self.0.push("seq_end".into());
294 Ok(())
295 }
296 fn seq_value_begin(&mut self) -> sval::Result {
297 self.0.push("seq_value_begin".into());
298 Ok(())
299 }
300 fn seq_value_end(&mut self) -> sval::Result {
301 self.0.push("seq_value_end".into());
302 Ok(())
303 }
304 }
305
306 #[test]
307 fn null_streams_null_event() {
308 let mut r = Recorder::default();
309 sval::Value::stream(&Value::Null, &mut r).unwrap();
310 assert_eq!(r.0, vec!["null".to_string()]);
311 }
312
313 #[test]
314 fn bool_streams_bool_event() {
315 let mut r = Recorder::default();
316 sval::Value::stream(&Value::Bool(true), &mut r).unwrap();
317 assert_eq!(r.0, vec!["bool(true)".to_string()]);
318 }
319
320 #[test]
321 fn integer_streams_i64_event() {
322 let mut r = Recorder::default();
323 sval::Value::stream(&Value::Number(Number::Integer(42)), &mut r).unwrap();
324 assert_eq!(r.0, vec!["i64(42)".to_string()]);
325 }
326
327 #[test]
328 fn float_streams_f64_event() {
329 let mut r = Recorder::default();
330 sval::Value::stream(&Value::Number(Number::Float(2.5)), &mut r).unwrap();
331 assert_eq!(r.0, vec!["f64(2.5)".to_string()]);
332 }
333
334 #[test]
335 fn sequence_streams_seq_events() {
336 let mut r = Recorder::default();
337 let v: Sequence = vec![Value::Bool(true), Value::Bool(false)];
338 sval::Value::stream(&Value::Sequence(v), &mut r).unwrap();
339 let s = r.0.join(",");
340 assert!(s.contains("seq_begin"));
341 assert!(s.contains("bool(true)"));
342 assert!(s.contains("bool(false)"));
343 assert!(s.contains("seq_end"));
344 }
345
346 #[test]
347 fn tagged_value_wraps_inner() {
348 use crate::value::{Tag, TaggedValue};
349 let mut r = Recorder::default();
350 let tv = TaggedValue::new(Tag::new("!timestamp"), Value::String("2026".into()));
351 sval::Value::stream(&Value::Tagged(Box::new(tv)), &mut r).unwrap();
352 let s = r.0.join(",");
356 assert!(s.contains("text(2026)"));
357 }
358
359 #[test]
360 fn mapping_any_streams_value_keyed_events() {
361 use crate::value::{MappingAny, Number};
362 let mut r = Recorder::default();
363 let mut m = MappingAny::new();
364 let _ = m.insert(Value::Number(Number::Integer(7)), Value::Bool(true));
365 sval::Value::stream(&m, &mut r).unwrap();
366 let s = r.0.join(",");
367 assert!(s.contains("map_begin"));
368 assert!(s.contains("i64(7)"));
369 assert!(s.contains("bool(true)"));
370 assert!(s.contains("map_end"));
371 }
372
373 #[test]
374 fn number_impl_streams_directly() {
375 let mut r = Recorder::default();
376 sval::Value::stream(&Number::Integer(99), &mut r).unwrap();
377 assert_eq!(r.0, vec!["i64(99)".to_string()]);
378 }
379
380 #[test]
381 fn to_sval_writer_helper_streams() {
382 let mut r = Recorder::default();
383 let v = Value::Bool(false);
384 to_sval_writer(&mut r, &v).unwrap();
385 assert_eq!(r.0, vec!["bool(false)".to_string()]);
386 }
387
388 #[test]
389 fn tag_streams_as_text() {
390 let mut r = Recorder::default();
391 let t = Tag::new("!timestamp");
392 sval::Value::stream(&t, &mut r).unwrap();
393 let s = r.0.join(",");
394 assert!(s.contains("text(!timestamp)"));
395 }
396
397 #[test]
398 fn nan_coerced_to_null_via_config() {
399 let mut r = Recorder::default();
400 let v = Value::Number(Number::Float(f64::NAN));
401 let cfg = SvalConfig {
402 coerce_non_finite_to_null: true,
403 };
404 to_sval_writer_with_config(&mut r, &v, &cfg).unwrap();
405 assert_eq!(r.0, vec!["null".to_string()]);
406 }
407
408 #[test]
409 fn finite_float_passes_through_config() {
410 let mut r = Recorder::default();
411 let v = Value::Number(Number::Float(2.5));
412 let cfg = SvalConfig {
413 coerce_non_finite_to_null: true,
414 };
415 to_sval_writer_with_config(&mut r, &v, &cfg).unwrap();
416 assert_eq!(r.0, vec!["f64(2.5)".to_string()]);
417 }
418
419 #[test]
420 fn null_string_streams_via_value_route() {
421 let mut r = Recorder::default();
423 sval::Value::stream(&Value::String("hello".into()), &mut r).unwrap();
424 let s = r.0.join(",");
425 assert!(s.contains("text(hello)"));
426 }
427
428 #[test]
429 fn mapping_streams_map_events() {
430 let mut r = Recorder::default();
431 let mut m = Mapping::new();
432 let _ = m.insert("k".to_string(), Value::Bool(true));
433 sval::Value::stream(&Value::Mapping(m), &mut r).unwrap();
434 let s = r.0.join(",");
435 assert!(s.contains("map_begin"));
436 assert!(s.contains("map_key_begin"));
437 assert!(s.contains("text(k)"));
438 assert!(s.contains("map_value_begin"));
439 assert!(s.contains("bool(true)"));
440 assert!(s.contains("map_end"));
441 }
442}