titan_core/respond.rs
1use std::convert::Infallible;
2
3use titan_http::{body::Body, header, Response, ResponseBuilder, StatusCode};
4
5/// A trait for types that can be converted into an HTTP response.
6///
7/// The `Respondable` trait is intended for types that can be transformed into a valid HTTP response.
8/// Any type returned by a handler (or any function) that implements this trait can be converted into
9/// a `Response<Body>`. This is typically used to ensure that different types can be unified into
10/// a standard response format for HTTP APIs or services.
11///
12/// This method converts the implementing type into an HTTP response represented by a `Response<Body>`.
13/// It allows various types (e.g., structs, enums, or other custom types) to be returned from a handler
14/// and automatically converted into HTTP responses.
15///
16/// # Implementing `Respondable`
17///
18/// To implement the `Respondable` trait, you need to define how your custom type can be turned into
19/// an HTTP response. This is commonly done by converting your type into a response body (e.g., a string,
20/// JSON, or some binary data) and returning it wrapped in a `Response<Body>`.
21///
22/// # Example
23///
24/// ```
25/// use titan_http::{body::Body, Response};
26/// use titan_core::Respondable;
27///
28/// // Define a type that represents a response body.
29/// pub struct MyResponse {
30/// message: String,
31/// }
32///
33/// // Implement `Respondable` for `MyResponse`.
34/// impl Respondable for MyResponse {
35/// fn respond(self) -> Response<Body> {
36/// // Convert the struct into an HTTP response with the message in the body.
37/// Response::new(Body::from(self.message))
38/// }
39/// }
40///
41/// // Now you can return `MyResponse` from a handler, and it will be automatically
42/// // converted into an HTTP response.
43/// ```
44pub trait Respondable {
45 fn respond(self) -> Response;
46}
47
48impl<T, E> Respondable for Result<T, E>
49where
50 T: Respondable,
51 E: Respondable,
52{
53 fn respond(self) -> Response {
54 match self {
55 Ok(t) => t.respond(),
56 Err(e) => e.respond(),
57 }
58 }
59}
60
61impl Respondable for Response {
62 fn respond(self) -> Response {
63 self
64 }
65}
66
67impl Respondable for Infallible {
68 fn respond(self) -> Response {
69 panic!("Not fallible :(")
70 }
71}
72
73impl Respondable for () {
74 fn respond(self) -> Response {
75 ResponseBuilder::new().status(204).body(Body::from(())).unwrap()
76 }
77}
78
79impl<T> Respondable for (StatusCode, T)
80where
81 T: Respondable,
82{
83 fn respond(self) -> Response {
84 let (status, body) = self;
85 let mut res = body.respond();
86
87 *res.status_mut() = status;
88 res
89 }
90}
91
92impl Respondable for titan_html::tags::html::Html {
93 fn respond(self) -> Response<Body> {
94 let response = ResponseBuilder::new().status(200);
95
96 let mut head_response = response.header(header::CONTENT_TYPE, "text/html");
97
98 if let Some(nonce) = self.with_csp_nonce.clone() {
99 head_response
100 .headers_mut()
101 .unwrap()
102 .insert(header::CONTENT_SECURITY_POLICY, format!("script-src 'self' 'nonce-{nonce}'; style-src 'self' 'nonce-{nonce}';").parse().unwrap());
103 }
104
105 let str = titan_html::render(self);
106 head_response.body(Body::from(str)).unwrap()
107 }
108}
109
110macro_rules! impl_respondable_for_int {
111 ($($t:ty)*) => {
112 $(
113 impl Respondable for $t {
114 fn respond(self) -> Response {
115 let body = Body::from(self);
116
117 let mut res = Response::new(body);
118 let headers = res.headers_mut();
119
120 headers.insert("content-type", "text/plain".parse().unwrap());
121
122 res
123 }
124 }
125 )*
126 };
127}
128
129impl_respondable_for_int!(String &str i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 usize isize);