Skip to main content

tako_rs_core/
responder.rs

1//! Response generation utilities and trait implementations for HTTP responses.
2//!
3//! This module provides the core `Responder` trait that enables various types to be
4//! converted into HTTP responses. It includes implementations for common types like
5//! strings, status codes, and custom response types. The trait allows handlers to
6//! return different types that are automatically converted to proper HTTP responses.
7//!
8//! # Examples
9//!
10//! ```rust
11//! use tako::responder::Responder;
12//! use http::StatusCode;
13//!
14//! // String response
15//! let response = "Hello, World!".into_response();
16//!
17//! // Status code with body
18//! let response = (StatusCode::OK, "Success").into_response();
19//!
20//! // Empty response
21//! let response = ().into_response();
22//! ```
23
24use std::borrow::Cow;
25use std::convert::Infallible;
26
27use bytes::Bytes;
28use http::HeaderMap;
29use http::StatusCode;
30use http::header::HeaderName;
31use http::header::HeaderValue;
32use http_body_util::Full;
33
34use crate::body::TakoBody;
35use crate::types::Response;
36
37/// A default 404 Not Found response.
38///
39/// Useful as a simple fallback:
40/// `router.fallback(|_| async { NOT_FOUND });`
41pub const NOT_FOUND: (StatusCode, &str) = (StatusCode::NOT_FOUND, "Not Found");
42
43/// Trait for converting types into HTTP responses.
44///
45/// This trait provides a unified interface for converting various types into
46/// `Response<TakoBody>` objects. It enables handlers to return different types
47/// that are automatically converted to proper HTTP responses, making the API
48/// more ergonomic and flexible.
49///
50/// # Examples
51///
52/// ```rust
53/// use tako::responder::Responder;
54/// use tako::body::TakoBody;
55/// use http::Response;
56///
57/// // Custom implementation
58/// struct JsonResponse {
59///     data: String,
60/// }
61///
62/// impl Responder for JsonResponse {
63///     fn into_response(self) -> Response<TakoBody> {
64///         let mut response = Response::new(TakoBody::from(self.data));
65///         response.headers_mut().insert(
66///             "content-type",
67///             "application/json".parse().unwrap()
68///         );
69///         response
70///     }
71/// }
72/// ```
73#[doc(alias = "response")]
74pub trait Responder {
75  /// Converts the implementing type into an HTTP response.
76  fn into_response(self) -> Response;
77}
78
79/// Alias for [`Responder`] matching the axum-style naming.
80///
81/// Both names refer to the same trait; pick whichever reads better in context.
82/// Existing code using `Responder` continues to compile unchanged.
83pub use Responder as IntoResponse;
84
85impl Responder for Response {
86  fn into_response(self) -> Response {
87    self
88  }
89}
90
91impl Responder for TakoBody {
92  fn into_response(self) -> Response {
93    Response::new(self)
94  }
95}
96
97impl Responder for &'static str {
98  fn into_response(self) -> Response {
99    Response::new(TakoBody::full(Full::from(Bytes::from_static(
100      self.as_bytes(),
101    ))))
102  }
103}
104
105impl Responder for String {
106  fn into_response(self) -> Response {
107    Response::new(TakoBody::full(Full::from(Bytes::from(self))))
108  }
109}
110
111impl Responder for () {
112  fn into_response(self) -> Response {
113    Response::new(TakoBody::empty())
114  }
115}
116
117impl Responder for Infallible {
118  fn into_response(self) -> Response {
119    match self {}
120  }
121}
122
123impl Responder for (StatusCode, &'static str) {
124  fn into_response(self) -> Response {
125    let (status, body) = self;
126    let mut res = Response::new(TakoBody::full(Full::from(Bytes::from_static(
127      body.as_bytes(),
128    ))));
129    *res.status_mut() = status;
130    res
131  }
132}
133
134impl Responder for (StatusCode, String) {
135  fn into_response(self) -> Response {
136    let (status, body) = self;
137    let mut res = Response::new(TakoBody::full(Full::from(Bytes::from(body))));
138    *res.status_mut() = status;
139    res
140  }
141}
142
143impl Responder for (StatusCode, Vec<u8>) {
144  fn into_response(self) -> Response {
145    let (status, body) = self;
146    let mut res = Response::new(TakoBody::full(Full::from(Bytes::from(body))));
147    *res.status_mut() = status;
148    res
149  }
150}
151
152impl Responder for Bytes {
153  fn into_response(self) -> Response {
154    Response::new(TakoBody::full(Full::from(self)))
155  }
156}
157
158impl Responder for Vec<u8> {
159  fn into_response(self) -> Response {
160    Response::new(TakoBody::full(Full::from(Bytes::from(self))))
161  }
162}
163
164impl Responder for Cow<'static, str> {
165  fn into_response(self) -> Response {
166    match self {
167      Cow::Borrowed(s) => {
168        Response::new(TakoBody::full(Full::from(Bytes::from_static(s.as_bytes()))))
169      }
170      Cow::Owned(s) => Response::new(TakoBody::full(Full::from(Bytes::from(s)))),
171    }
172  }
173}
174
175impl Responder for serde_json::Value {
176  fn into_response(self) -> Response {
177    match serde_json::to_vec(&self) {
178      Ok(buf) => {
179        let mut res = Response::new(TakoBody::full(Full::from(Bytes::from(buf))));
180        res.headers_mut().insert(
181          http::header::CONTENT_TYPE,
182          HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
183        );
184        res
185      }
186      Err(err) => {
187        let mut res = Response::new(TakoBody::from(err.to_string()));
188        *res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
189        res.headers_mut().insert(
190          http::header::CONTENT_TYPE,
191          HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
192        );
193        res
194      }
195    }
196  }
197}
198
199impl Responder for (StatusCode, HeaderMap, TakoBody) {
200  fn into_response(self) -> Response {
201    let (status, headers, body) = self;
202    let mut res = Response::new(body);
203    *res.status_mut() = status;
204    *res.headers_mut() = headers;
205    res
206  }
207}
208
209impl Responder for (StatusCode, HeaderMap) {
210  fn into_response(self) -> Response {
211    let (status, headers) = self;
212    let mut res = Response::new(TakoBody::empty());
213    *res.status_mut() = status;
214    *res.headers_mut() = headers;
215    res
216  }
217}
218
219impl Responder for HeaderMap {
220  fn into_response(self) -> Response {
221    let mut res = Response::new(TakoBody::empty());
222    *res.headers_mut() = self;
223    res
224  }
225}
226
227impl Responder for StatusCode {
228  fn into_response(self) -> Response {
229    let mut res = Response::new(TakoBody::empty());
230    *res.status_mut() = self;
231    res
232  }
233}
234
235pub struct StaticHeaders<const N: usize>(pub [(HeaderName, &'static str); N]);
236
237impl<const N: usize> Responder for (StatusCode, StaticHeaders<N>) {
238  fn into_response(self) -> Response {
239    let (status, StaticHeaders(headers)) = self;
240    let mut res = Response::new(TakoBody::empty());
241    *res.status_mut() = status;
242
243    for (name, value) in headers {
244      res
245        .headers_mut()
246        .append(name, HeaderValue::from_static(value));
247    }
248    res
249  }
250}
251
252impl Responder for anyhow::Error {
253  fn into_response(self) -> Response {
254    let mut res = Response::new(TakoBody::from(self.to_string()));
255    *res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
256    res.headers_mut().insert(
257      http::header::CONTENT_TYPE,
258      HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
259    );
260    res
261  }
262}
263
264impl ResponderError for anyhow::Error {}
265
266/// Native `Result<R, E>` support for handler returns where both arms implement
267/// [`Responder`]. The `Ok` value renders normally; the `Err` value is rendered
268/// via its own [`Responder`] impl so error types stay typed instead of being
269/// forced through a single panic-or-string path.
270///
271/// `anyhow::Result<T>` is `Result<T, anyhow::Error>` and falls into this blanket
272/// via the [`Responder`]/[`ResponderError`] impls on [`anyhow::Error`] just above.
273/// There is intentionally only one `Result<_, _>` blanket so future changes cannot
274/// introduce overlap between two specialised impls.
275impl<T, E> Responder for Result<T, E>
276where
277  T: Responder,
278  E: ResponderError,
279{
280  fn into_response(self) -> Response {
281    match self {
282      Ok(ok) => ok.into_response(),
283      Err(err) => err.into_response(),
284    }
285  }
286}
287
288/// Marker trait that opts a type into being used as the `Err` arm of a
289/// handler-returned `Result<_, E>`.
290///
291/// Implement [`Responder`] on your error type, then add `impl ResponderError for MyErr {}`
292/// to make it usable as `Result<_, MyErr>`. `anyhow::Error` already implements both,
293/// so `anyhow::Result<T>` works out of the box.
294pub trait ResponderError: Responder {}