sval/data/
text.rs

1use crate::{
2    std::fmt::{self, Write as _},
3    Error, Result, Stream, Tag, Value,
4};
5
6/**
7Adapt a [`fmt::Display`] into an [`sval::Value`].
8*/
9#[repr(transparent)]
10pub struct Display<V: ?Sized>(V);
11
12impl<V: fmt::Display> Display<V> {
13    /**
14    Adapt a [`fmt::Display`] into an [`sval::Value`].
15    */
16    pub fn new(value: V) -> Display<V> {
17        Display(value)
18    }
19
20    /**
21    Get a reference to the inner value.
22    */
23    pub fn inner(&self) -> &V {
24        &self.0
25    }
26
27    /**
28    Convert into the inner value.
29    */
30    pub fn into_inner(self) -> V {
31        self.0
32    }
33}
34
35impl<V: fmt::Display + ?Sized> Display<V> {
36    /**
37    Adapt a reference to a [`fmt::Display`] into an [`sval::Value`].
38    */
39    pub fn new_borrowed<'a>(value: &'a V) -> &'a Display<V> {
40        // SAFETY: `&'a V` and `&'a Display<V>` have the same ABI
41        unsafe { &*(value as *const _ as *const Display<V>) }
42    }
43}
44
45impl<V: fmt::Display> Value for Display<V> {
46    fn stream<'sval, S: Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> Result {
47        stream.text_begin(None)?;
48        stream_display_fragments(stream, &self.0)?;
49        stream.text_end()
50    }
51}
52
53/**
54Stream a [`fmt::Display`] as text into a [`Stream`].
55
56This function can be used to stream a value as text using its `Display` implementation.
57*/
58pub fn stream_display<'sval>(
59    stream: &mut (impl Stream<'sval> + ?Sized),
60    value: impl fmt::Display,
61) -> Result {
62    stream.value_computed(&Display::new(value))
63}
64
65/**
66Stream a [`fmt::Display`] as text fragments into a [`Stream`], without calling [`Stream::text_begin`] or [`Stream::text_end`].
67
68This function can be used to stream a part of a larger value.
69*/
70pub fn stream_display_fragments<'sval>(
71    stream: &mut (impl Stream<'sval> + ?Sized),
72    value: impl fmt::Display,
73) -> Result {
74    write!(
75        Writer(|fragment: &str| stream
76            .text_fragment_computed(fragment)
77            .map_err(|_| fmt::Error)),
78        "{}",
79        value
80    )
81    .map_err(|_| Error::new())
82}
83
84struct Writer<F>(F);
85
86impl<F: FnMut(&str) -> fmt::Result> fmt::Write for Writer<F> {
87    fn write_str(&mut self, s: &str) -> fmt::Result {
88        (self.0)(s).map_err(|_| fmt::Error)
89    }
90}
91
92impl Value for char {
93    fn stream<'sval, S: Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> Result {
94        let mut buf = [0; 4];
95        let value = &*self.encode_utf8(&mut buf);
96
97        stream.text_begin(Some(value.len()))?;
98        stream.text_fragment_computed(value)?;
99        stream.text_end()
100    }
101}
102
103impl Value for str {
104    fn stream<'sval, S: Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> Result {
105        stream.text_begin(Some(self.len()))?;
106        stream.text_fragment(self)?;
107        stream.text_end()
108    }
109
110    fn tag(&self) -> Option<Tag> {
111        None
112    }
113
114    fn to_text(&self) -> Option<&str> {
115        Some(self)
116    }
117}
118
119#[cfg(feature = "alloc")]
120mod alloc_support {
121    use super::*;
122
123    use crate::std::string::String;
124
125    impl Value for String {
126        fn stream<'a, S: Stream<'a> + ?Sized>(&'a self, stream: &mut S) -> Result {
127            (&**self).stream(stream)
128        }
129
130        fn tag(&self) -> Option<Tag> {
131            None
132        }
133
134        #[inline]
135        fn to_text(&self) -> Option<&str> {
136            Some(self)
137        }
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    struct TextLike(&'static str);
146    struct TextLikeComputed(&'static str);
147
148    impl Value for TextLike {
149        fn stream<'sval, S: Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> Result {
150            self.0.stream(stream)
151        }
152    }
153
154    impl Value for TextLikeComputed {
155        fn stream<'sval, S: Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> Result {
156            stream.text_begin(Some(self.0.len()))?;
157            stream.text_fragment_computed(self.0)?;
158            stream.text_end()
159        }
160    }
161
162    #[test]
163    fn string_cast() {
164        assert_eq!(Some("a string"), "a string".to_text());
165        assert_eq!(Some("a string"), TextLike("a string").to_text());
166        assert_eq!(None, TextLikeComputed("123").to_text());
167    }
168
169    #[test]
170    fn string_tag() {
171        assert_eq!(None, TextLike("123").tag());
172        assert_eq!(None, TextLikeComputed("123").tag());
173    }
174}