Skip to main content

topcoat_view/
format.rs

1#[cfg(feature = "http")]
2use http::{HeaderMap, StatusCode};
3
4/// A string writer for rendered output.
5///
6/// [`write_str`](Self::write_str) and [`write_char`](Self::write_char)
7/// append text without escaping. For escaped output, use an
8/// [`HtmlWriter`](crate::HtmlWriter) with the appropriate
9/// [`HtmlContext`](crate::HtmlContext).
10pub struct Formatter<'a> {
11    buf: &'a mut String,
12    #[cfg(feature = "http")]
13    status_code: Option<StatusCode>,
14    #[cfg(feature = "http")]
15    headers: HeaderMap,
16}
17
18impl<'a> Formatter<'a> {
19    /// Creates a new `Formatter` that writes into the given destination.
20    #[inline]
21    pub fn new(buf: &'a mut String) -> Self {
22        Self {
23            buf,
24            #[cfg(feature = "http")]
25            status_code: None,
26            #[cfg(feature = "http")]
27            headers: HeaderMap::new(),
28        }
29    }
30
31    /// Writes a string verbatim.
32    #[inline]
33    pub fn write_str(&mut self, s: &str) {
34        self.buf.push_str(s);
35    }
36
37    /// Writes a single character verbatim.
38    #[inline]
39    pub fn write_char(&mut self, c: char) {
40        self.buf.push(c);
41    }
42
43    /// Records a response status code, keeping an earlier one if already
44    /// recorded: the first status code rendered wins.
45    #[cfg(feature = "http")]
46    #[inline]
47    pub(crate) fn record_status_code(&mut self, status_code: StatusCode) {
48        self.status_code.get_or_insert(status_code);
49    }
50
51    /// Records response headers, keeping earlier values for names already
52    /// recorded: the first render part that mentions a header name provides
53    /// all of that name's values.
54    #[cfg(feature = "http")]
55    pub(crate) fn record_headers(&mut self, headers: HeaderMap) {
56        if self.headers.is_empty() {
57            self.headers = headers;
58            return;
59        }
60        for name in headers.keys() {
61            if !self.headers.contains_key(name) {
62                for value in headers.get_all(name) {
63                    self.headers.append(name.clone(), value.clone());
64                }
65            }
66        }
67    }
68
69    /// Consumes the formatter, returning the recorded status code and
70    /// headers.
71    #[cfg(feature = "http")]
72    pub(crate) fn into_recorded(self) -> (Option<StatusCode>, HeaderMap) {
73        (self.status_code, self.headers)
74    }
75}
76
77impl std::fmt::Write for Formatter<'_> {
78    fn write_str(&mut self, s: &str) -> std::fmt::Result {
79        Formatter::write_str(self, s);
80        Ok(())
81    }
82
83    fn write_char(&mut self, c: char) -> std::fmt::Result {
84        Formatter::write_char(self, c);
85        Ok(())
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn write_str_is_verbatim() {
95        let mut buf = String::new();
96        let mut f = Formatter::new(&mut buf);
97        f.write_str("<b>&\"'</b>");
98        assert_eq!(buf, "<b>&\"'</b>");
99    }
100
101    #[test]
102    fn write_char_is_verbatim() {
103        let mut buf = String::new();
104        let mut f = Formatter::new(&mut buf);
105        f.write_char('<');
106        f.write_char('é');
107        assert_eq!(buf, "<é");
108    }
109
110    #[test]
111    fn fmt_write_is_verbatim() {
112        use std::fmt::Write;
113
114        let (one, two) = (1, "two");
115        let mut buf = String::new();
116        let mut f = Formatter::new(&mut buf);
117        write!(f, "{one} < {two}").unwrap();
118        assert_eq!(buf, "1 < two");
119    }
120}