salvo_core/writing/
mod.rs1mod 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#[async_trait]
24pub trait Writer {
25 #[must_use = "write future must be used"]
27 async fn write(self, req: &mut Request, depot: &mut Depot, res: &mut Response);
28}
29
30pub trait Scribe {
36 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}