salvo_core/writing/
text.rs

1use std::fmt::{self, Debug, Display, Formatter};
2
3use super::{Scribe, try_set_header};
4use crate::http::Response;
5use crate::http::header::{CONTENT_TYPE, HeaderValue};
6
7/// Write text content to response as text content.
8///
9/// # Example
10///
11/// ```
12/// use salvo_core::prelude::*;
13///
14/// #[handler]
15/// async fn hello(res: &mut Response) -> Text<&'static str> {
16///     Text::Plain("hello")
17/// }
18/// ```
19#[non_exhaustive]
20pub enum Text<C> {
21    /// It will set `content-type` to `text/plain; charset=utf-8`.
22    Plain(C),
23    /// It will set `content-type` to `application/json; charset=utf-8`.
24    Json(C),
25    /// It will set `content-type` to `application/xml; charset=utf-8`.
26    Xml(C),
27    /// It will set `content-type` to `text/html; charset=utf-8`.
28    Html(C),
29    /// It will set `content-type` to `text/javascript; charset=utf-8`.
30    Js(C),
31    /// It will set `content-type` to `text/css; charset=utf-8`.
32    Css(C),
33    /// It will set `content-type` to `text/csv; charset=utf-8`.
34    Csv(C),
35    /// It will set `content-type` to `application/atom+xml; charset=utf-8`.
36    Atom(C),
37    /// It will set `content-type` to `application/rss+xml; charset=utf-8`.
38    Rss(C),
39    /// It will set `content-type` to `application/rdf+xml; charset=utf-8`.
40    Rdf(C),
41}
42
43impl<C> Text<C>
44where
45    C: AsRef<str>,
46{
47    fn try_set_header(self, res: &mut Response) -> C {
48        let (ctype, content) = match self {
49            Self::Plain(content) => (
50                HeaderValue::from_static("text/plain; charset=utf-8"),
51                content,
52            ),
53            Self::Json(content) => (
54                HeaderValue::from_static("application/json; charset=utf-8"),
55                content,
56            ),
57            Self::Xml(content) => (
58                HeaderValue::from_static("application/xml; charset=utf-8"),
59                content,
60            ),
61            Self::Html(content) => (
62                HeaderValue::from_static("text/html; charset=utf-8"),
63                content,
64            ),
65            Self::Js(content) => (
66                HeaderValue::from_static("text/javascript; charset=utf-8"),
67                content,
68            ),
69            Self::Css(content) => (HeaderValue::from_static("text/css; charset=utf-8"), content),
70            Self::Csv(content) => (HeaderValue::from_static("text/csv; charset=utf-8"), content),
71            Self::Atom(content) => (
72                HeaderValue::from_static("application/atom+xml; charset=utf-8"),
73                content,
74            ),
75            Self::Rss(content) => (
76                HeaderValue::from_static("application/rss+xml; charset=utf-8"),
77                content,
78            ),
79            Self::Rdf(content) => (
80                HeaderValue::from_static("application/rdf+xml; charset=utf-8"),
81                content,
82            ),
83        };
84        try_set_header(&mut res.headers, CONTENT_TYPE, ctype);
85        content
86    }
87}
88impl Scribe for Text<&'static str> {
89    #[inline]
90    fn render(self, res: &mut Response) {
91        let content = self.try_set_header(res);
92        let _ = res.write_body(content);
93    }
94}
95impl Scribe for Text<String> {
96    #[inline]
97    fn render(self, res: &mut Response) {
98        let content = self.try_set_header(res);
99        let _ = res.write_body(content);
100    }
101}
102impl Scribe for Text<&String> {
103    #[inline]
104    fn render(self, res: &mut Response) {
105        let content = self.try_set_header(res);
106        let _ = res.write_body(content.as_bytes().to_vec());
107    }
108}
109impl<C: Debug> Debug for Text<C> {
110    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
111        match self {
112            Self::Plain(content) => f.debug_tuple("Text::Plain").field(content).finish(),
113            Self::Json(content) => f.debug_tuple("Text::Json").field(content).finish(),
114            Self::Xml(content) => f.debug_tuple("Text::Xml").field(content).finish(),
115            Self::Html(content) => f.debug_tuple("Text::Html").field(content).finish(),
116            Self::Js(content) => f.debug_tuple("Text::Js").field(content).finish(),
117            Self::Css(content) => f.debug_tuple("Text::Css").field(content).finish(),
118            Self::Csv(content) => f.debug_tuple("Text::Csv").field(content).finish(),
119            Self::Atom(content) => f.debug_tuple("Text::Atom").field(content).finish(),
120            Self::Rss(content) => f.debug_tuple("Text::Rss").field(content).finish(),
121            Self::Rdf(content) => f.debug_tuple("Text::Rdf").field(content).finish(),
122        }
123    }
124}
125impl<C: Display> Display for Text<C> {
126    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
127        match self {
128            Self::Plain(content)
129            | Self::Json(content)
130            | Self::Xml(content)
131            | Self::Html(content)
132            | Self::Js(content)
133            | Self::Css(content)
134            | Self::Csv(content)
135            | Self::Atom(content)
136            | Self::Rss(content)
137            | Self::Rdf(content) => Display::fmt(content, f),
138        }
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use crate::prelude::*;
145
146    use super::*;
147    use crate::test::{ResponseExt, TestClient};
148
149    #[tokio::test]
150    async fn test_write_str() {
151        #[handler]
152        async fn test() -> &'static str {
153            "hello"
154        }
155
156        let router = Router::new().push(Router::with_path("test").get(test));
157
158        let mut res = TestClient::get("http://127.0.0.1:8698/test")
159            .send(router)
160            .await;
161        assert_eq!(res.take_string().await.unwrap(), "hello");
162        assert_eq!(
163            res.headers().get("content-type").unwrap(),
164            "text/plain; charset=utf-8"
165        );
166    }
167
168    #[tokio::test]
169    async fn test_write_string() {
170        #[handler]
171        async fn test() -> String {
172            "hello".to_owned()
173        }
174
175        let router = Router::new().push(Router::with_path("test").get(test));
176        let mut res = TestClient::get("http://127.0.0.1:8698/test")
177            .send(router)
178            .await;
179        assert_eq!(res.take_string().await.unwrap(), "hello");
180        assert_eq!(
181            res.headers().get("content-type").unwrap(),
182            "text/plain; charset=utf-8"
183        );
184    }
185
186    #[tokio::test]
187    async fn test_write_plain_text() {
188        #[handler]
189        async fn test() -> Text<&'static str> {
190            Text::Plain("hello")
191        }
192
193        let router = Router::new().push(Router::with_path("test").get(test));
194
195        let mut res = TestClient::get("http://127.0.0.1:8698/test")
196            .send(router)
197            .await;
198        assert_eq!(res.take_string().await.unwrap(), "hello");
199        assert_eq!(
200            res.headers().get("content-type").unwrap(),
201            "text/plain; charset=utf-8"
202        );
203    }
204
205    #[tokio::test]
206    async fn test_write_json_text() {
207        #[handler]
208        async fn test() -> Text<&'static str> {
209            Text::Json(r#"{"hello": "world"}"#)
210        }
211
212        let router = Router::new().push(Router::with_path("test").get(test));
213        let mut res = TestClient::get("http://127.0.0.1:8698/test")
214            .send(router)
215            .await;
216        assert_eq!(res.take_string().await.unwrap(), r#"{"hello": "world"}"#);
217        assert_eq!(
218            res.headers().get("content-type").unwrap(),
219            "application/json; charset=utf-8"
220        );
221    }
222
223    #[tokio::test]
224    async fn test_write_html_text() {
225        #[handler]
226        async fn test() -> Text<&'static str> {
227            Text::Html("<html><body>hello</body></html>")
228        }
229
230        let router = Router::new().push(Router::with_path("test").get(test));
231        let mut res = TestClient::get("http://127.0.0.1:8698/test")
232            .send(router)
233            .await;
234        assert_eq!(
235            res.take_string().await.unwrap(),
236            "<html><body>hello</body></html>"
237        );
238        assert_eq!(
239            res.headers().get("content-type").unwrap(),
240            "text/html; charset=utf-8"
241        );
242    }
243    #[tokio::test]
244    async fn test_write_xml_text() {
245        #[handler]
246        async fn test() -> Text<&'static str> {
247            Text::Xml("<xml>hello</xml>")
248        }
249
250        let router = Router::new().push(Router::with_path("test").get(test));
251        let mut res = TestClient::get("http://127.0.0.1:8698/test")
252            .send(router)
253            .await;
254        assert_eq!(res.take_string().await.unwrap(), "<xml>hello</xml>");
255        assert_eq!(
256            res.headers().get("content-type").unwrap(),
257            "application/xml; charset=utf-8"
258        );
259    }
260
261    #[tokio::test]
262    async fn test_write_js_text() {
263        #[handler]
264        async fn test() -> Text<&'static str> {
265            Text::Js("var a = 1;")
266        }
267
268        let router = Router::new().push(Router::with_path("test").get(test));
269        let mut res = TestClient::get("http://127.0.0.1:8698/test")
270            .send(router)
271            .await;
272        assert_eq!(res.take_string().await.unwrap(), "var a = 1;");
273        assert_eq!(
274            res.headers().get("content-type").unwrap(),
275            "text/javascript; charset=utf-8"
276        );
277    }
278
279    #[tokio::test]
280    async fn test_write_css_text() {
281        #[handler]
282        async fn test() -> Text<&'static str> {
283            Text::Css("body {color: red;}")
284        }
285
286        let router = Router::new().push(Router::with_path("test").get(test));
287        let mut res = TestClient::get("http://127.0.0.1:8698/test")
288            .send(router)
289            .await;
290        assert_eq!(res.take_string().await.unwrap(), "body {color: red;}");
291        assert_eq!(
292            res.headers().get("content-type").unwrap(),
293            "text/css; charset=utf-8"
294        );
295    }
296
297    #[tokio::test]
298    async fn test_write_csv_text() {
299        #[handler]
300        async fn test() -> Text<&'static str> {
301            Text::Csv("a,b,c")
302        }
303
304        let router = Router::new().push(Router::with_path("test").get(test));
305        let mut res = TestClient::get("http://127.0.0.1:8698/test")
306            .send(router)
307            .await;
308        assert_eq!(res.take_string().await.unwrap(), "a,b,c");
309        assert_eq!(
310            res.headers().get("content-type").unwrap(),
311            "text/csv; charset=utf-8"
312        );
313    }
314
315    #[tokio::test]
316    async fn test_write_atom_text() {
317        #[handler]
318        async fn test() -> Text<&'static str> {
319            Text::Atom("<feed></feed>")
320        }
321
322        let router = Router::new().push(Router::with_path("test").get(test));
323        let mut res = TestClient::get("http://127.0.0.1:8698/test")
324            .send(router)
325            .await;
326        assert_eq!(res.take_string().await.unwrap(), "<feed></feed>");
327        assert_eq!(
328            res.headers().get("content-type").unwrap(),
329            "application/atom+xml; charset=utf-8"
330        );
331    }
332
333    #[tokio::test]
334    async fn test_write_rss_text() {
335        #[handler]
336        async fn test() -> Text<&'static str> {
337            Text::Rss("<rss></rss>")
338        }
339
340        let router = Router::new().push(Router::with_path("test").get(test));
341        let mut res = TestClient::get("http://127.0.0.1:8698/test")
342            .send(router)
343            .await;
344        assert_eq!(res.take_string().await.unwrap(), "<rss></rss>");
345        assert_eq!(
346            res.headers().get("content-type").unwrap(),
347            "application/rss+xml; charset=utf-8"
348        );
349    }
350
351    #[tokio::test]
352    async fn test_write_rdf_text() {
353        #[handler]
354        async fn test() -> Text<&'static str> {
355            Text::Rdf("<rdf></rdf>")
356        }
357
358        let router = Router::new().push(Router::with_path("test").get(test));
359        let mut res = TestClient::get("http://127.0.0.1:8698/test")
360            .send(router)
361            .await;
362        assert_eq!(res.take_string().await.unwrap(), "<rdf></rdf>");
363        assert_eq!(
364            res.headers().get("content-type").unwrap(),
365            "application/rdf+xml; charset=utf-8"
366        );
367    }
368}