salvo_core/writing/
mod.rs

1//! Writer trait and it's implements.
2
3mod json;
4mod redirect;
5mod seek;
6mod text;
7
8use http::header::{AsHeaderName, IntoHeaderName};
9use http::{HeaderMap, StatusCode};
10pub use json::Json;
11pub use redirect::Redirect;
12pub use seek::ReadSeeker;
13pub use text::Text;
14
15use crate::http::header::{CONTENT_TYPE, HeaderValue};
16use crate::{Depot, Request, Response, async_trait};
17
18/// `Writer` is used to write data to [`Response`].
19///
20/// `Writer` requires the use of [`Depot`] and Request, which allows for greater flexibility.
21/// For scenarios that do not require this flexibility, [`Scribe`] can be used, for example [`Text`], [`Json`] are
22/// implemented from [`Scribe`].
23#[async_trait]
24pub trait Writer {
25    /// Write data to [`Response`].
26    #[must_use = "write future must be used"]
27    async fn write(self, req: &mut Request, depot: &mut Depot, res: &mut Response);
28}
29
30/// `Scribe` is used to write data to [`Response`].
31///
32/// `Scribe` is simpler than [`Writer`] and it implements [`Writer`]. It does not require the use of Depot and Request.
33///
34/// There are several built-in implementations of the `Scribe` trait.
35pub trait Scribe {
36    /// Render data to [`Response`].
37    fn render(self, res: &mut Response);
38}
39#[async_trait]
40impl<P> Writer for P
41where
42    P: Scribe + Sized + Send,
43{
44    #[inline]
45    async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
46        self.render(res)
47    }
48}
49
50#[async_trait]
51impl<P> Writer for Option<P>
52where
53    P: Scribe + Sized + Send,
54{
55    #[inline]
56    async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
57        match self {
58            Some(v) => v.render(res),
59            None => {
60                res.status_code(StatusCode::NOT_FOUND);
61            }
62        }
63    }
64}
65
66#[async_trait]
67impl<T, E> Writer for Result<T, E>
68where
69    T: Writer + Send,
70    E: Writer + Send,
71{
72    #[inline]
73    async fn write(self, req: &mut Request, depot: &mut Depot, res: &mut Response) {
74        match self {
75            Ok(v) => {
76                v.write(req, depot, res).await;
77            }
78            Err(e) => {
79                e.write(req, depot, res).await;
80            }
81        }
82    }
83}
84
85#[allow(clippy::unit_arg)]
86impl Scribe for () {
87    #[inline]
88    fn render(self, _res: &mut Response) {}
89}
90
91impl Scribe for StatusCode {
92    #[inline]
93    fn render(self, res: &mut Response) {
94        res.status_code(self);
95    }
96}
97
98impl Scribe for &'static str {
99    #[inline]
100    fn render(self, res: &mut Response) {
101        try_set_header(
102            &mut res.headers,
103            CONTENT_TYPE,
104            HeaderValue::from_static("text/plain; charset=utf-8"),
105        );
106        let _ = res.write_body(self);
107    }
108}
109impl Scribe for &String {
110    #[inline]
111    fn render(self, res: &mut Response) {
112        try_set_header(
113            &mut res.headers,
114            CONTENT_TYPE,
115            HeaderValue::from_static("text/plain; charset=utf-8"),
116        );
117        let _ = res.write_body(self.as_bytes().to_vec());
118    }
119}
120impl Scribe for String {
121    #[inline]
122    fn render(self, res: &mut Response) {
123        try_set_header(
124            &mut res.headers,
125            CONTENT_TYPE,
126            HeaderValue::from_static("text/plain; charset=utf-8"),
127        );
128        let _ = res.write_body(self);
129    }
130}
131impl Scribe for std::convert::Infallible {
132    #[inline]
133    fn render(self, _res: &mut Response) {}
134}
135
136macro_rules! writer_tuple_impls {
137    ($(
138        $Tuple:tt {
139            $(($idx:tt) -> $T:ident,)+
140        }
141    )+) => {$(
142        #[async_trait::async_trait]
143        impl<$($T,)+> Writer for ($($T,)+) where $($T: Writer + Send,)+
144        {
145            async fn write(self, req: &mut Request, depot: &mut Depot, res: &mut Response) {
146                $(
147                    self.$idx.write(req, depot, res).await;
148                )+
149            }
150        })+
151    }
152}
153
154crate::for_each_tuple!(writer_tuple_impls);
155
156#[inline(always)]
157fn try_set_header<K, V>(headers: &mut HeaderMap<V>, key: K, val: V)
158where
159    K: IntoHeaderName,
160    for<'a> &'a K: AsHeaderName,
161{
162    if !headers.contains_key(&key) {
163        let _ = headers.insert(key, val);
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use crate::prelude::*;
170
171    use crate::test::{ResponseExt, TestClient};
172
173    #[tokio::test]
174    async fn test_write_str() {
175        #[handler]
176        async fn test() -> &'static str {
177            "hello"
178        }
179
180        let router = Router::new().push(Router::with_path("test").get(test));
181
182        let mut res = TestClient::get("http://127.0.0.1:8698/test")
183            .send(router)
184            .await;
185        assert_eq!(res.take_string().await.unwrap(), "hello");
186        assert_eq!(
187            res.headers().get("content-type").unwrap(),
188            "text/plain; charset=utf-8"
189        );
190    }
191
192    #[tokio::test]
193    async fn test_write_string() {
194        #[handler]
195        async fn test() -> String {
196            "hello".to_owned()
197        }
198
199        let router = Router::new().push(Router::with_path("test").get(test));
200        let mut res = TestClient::get("http://127.0.0.1:8698/test")
201            .send(router)
202            .await;
203        assert_eq!(res.take_string().await.unwrap(), "hello");
204        assert_eq!(
205            res.headers().get("content-type").unwrap(),
206            "text/plain; charset=utf-8"
207        );
208    }
209}