1use crate::response::Response;
2
3#[cfg(not(target_arch = "wasm32"))]
4pub trait IntoResponseBounds: Send {}
5#[cfg(not(target_arch = "wasm32"))]
6impl<T: Send + ?Sized> IntoResponseBounds for T {}
7
8#[cfg(target_arch = "wasm32")]
9pub trait IntoResponseBounds {}
10#[cfg(target_arch = "wasm32")]
11impl<T: ?Sized> IntoResponseBounds for T {}
12
13pub trait IntoResponse: IntoResponseBounds {
37 fn into_response(self) -> Response;
39}
40
41impl IntoResponse for String {
43 fn into_response(self) -> Response {
44 Response::text(self)
45 }
46}
47
48impl IntoResponse for &'static str {
49 fn into_response(self) -> Response {
50 Response::text(self)
51 }
52}
53
54impl IntoResponse for std::borrow::Cow<'static, str> {
55 fn into_response(self) -> Response {
56 Response::text(self)
57 }
58}
59
60impl IntoResponse for Response {
62 fn into_response(self) -> Response {
63 self
64 }
65}
66
67impl IntoResponse for () {
69 fn into_response(self) -> Response {
70 Response::empty()
71 }
72}
73
74impl<T: IntoResponse> IntoResponse for Option<T> {
76 fn into_response(self) -> Response {
77 match self {
78 Some(value) => value.into_response(),
79 None => Response::empty(),
80 }
81 }
82}
83
84impl<T, E> IntoResponse for Result<T, E>
89where
90 T: IntoResponse,
91 E: std::fmt::Display + IntoResponseBounds,
92{
93 fn into_response(self) -> Response {
94 match self {
95 Ok(value) => value.into_response(),
96 Err(e) => Response::text(format!("Error: {e}")),
97 }
98 }
99}
100
101impl IntoResponse for async_fs::File {
103 fn into_response(self) -> Response {
104 Response::file(self)
105 }
106}
107
108impl IntoResponse for std::path::PathBuf {
109 fn into_response(self) -> Response {
110 Response::path(self)
111 }
112}
113
114macro_rules! impl_captioned_file {
120 ($ty:ty) => {
121 impl<C: Into<String> + IntoResponseBounds> IntoResponse for ($ty, C) {
122 fn into_response(self) -> Response {
123 let (file, caption) = self;
124 file.into_response().with_caption(caption)
125 }
126 }
127 };
128}
129
130impl_captioned_file!(async_fs::File);
131impl_captioned_file!(std::path::PathBuf);
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn strings_become_text_responses() {
139 assert_eq!("hi".into_response().content(), Some("hi"));
140 assert_eq!(String::from("hi").into_response().content(), Some("hi"));
141 assert_eq!(
142 std::borrow::Cow::Borrowed("hi").into_response().content(),
143 Some("hi")
144 );
145 }
146
147 #[test]
148 fn unit_and_none_are_empty() {
149 assert!(().into_response().is_empty());
150 assert!(Option::<String>::None.into_response().is_empty());
151 assert_eq!(Some("hi").into_response().content(), Some("hi"));
152 }
153
154 #[test]
155 fn errors_render_their_display() {
156 let result: Result<&str, std::fmt::Error> = Err(std::fmt::Error);
157 assert_eq!(
158 result.into_response().content(),
159 Some("Error: an error occurred when formatting an argument")
160 );
161 }
162
163 #[test]
164 fn ok_passes_the_inner_value_through() {
165 let result: Result<&str, std::fmt::Error> = Ok("fine");
166 assert_eq!(result.into_response().content(), Some("fine"));
167 }
168
169 #[test]
170 fn captions_attach_to_file_responses() {
171 let mut response = (std::path::PathBuf::from("/tmp/a.txt"), "caption").into_response();
172 let file = response.take_file().unwrap();
173 assert_eq!(file.caption.as_deref(), Some("caption"));
174 assert_eq!(file.filename.as_deref(), Some("a.txt"));
175 }
176}