1use crate::AxumHandlerImpl;
2use alux_http::{
3 BytesOutAlg, ChunksAlg, ChunksExt, EmptyOutAlg, FileOutAlg, HeaderNameAlg, HeaderOutAlg, HtmlOutAlg, HttpErrorAlg,
4 HttpStatus, JsonOutAlg, OutputAlg, RedirectOutAlg, ResultOutAlg, StatusOutAlg, StreamOutAlg, TextOutAlg,
5};
6use axum::Json;
7use axum::body::Body;
8use axum::http::{HeaderName, HeaderValue, StatusCode, header};
9use axum::response::{Html, IntoResponse, Redirect, Response};
10use core::fmt::Display;
11use core::marker::PhantomData;
12use futures::{Stream, TryStreamExt};
13use std::io::Error as IoError;
14
15pub fn axum_status(status: HttpStatus) -> StatusCode {
17 StatusCode::from_u16(status.code()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
18}
19
20pub struct AxumJsonOutput;
22
23impl<From> OutputAlg<From> for AxumJsonOutput {
24 type Output = Json<From>;
25
26 fn output(from: From) -> Self::Output {
27 Json(from)
28 }
29}
30
31pub struct AxumTextOutput;
33
34impl<From> OutputAlg<From> for AxumTextOutput
35where
36 From: Display,
37{
38 type Output = String;
39
40 fn output(from: From) -> Self::Output {
41 from.to_string()
42 }
43}
44
45pub struct AxumHtmlOutput;
47
48impl<From> OutputAlg<From> for AxumHtmlOutput {
49 type Output = Html<From>;
50
51 fn output(from: From) -> Self::Output {
52 Html(from)
53 }
54}
55
56pub struct AxumBytesOutput;
58
59impl<From> OutputAlg<From> for AxumBytesOutput
60where
61 From: Into<Vec<u8>>,
62{
63 type Output = Vec<u8>;
64
65 fn output(from: From) -> Self::Output {
66 from.into()
67 }
68}
69
70pub struct AxumEmptyOutput;
72
73impl OutputAlg<()> for AxumEmptyOutput {
74 type Output = StatusCode;
75
76 fn output((): ()) -> Self::Output {
77 StatusCode::NO_CONTENT
78 }
79}
80
81pub struct AxumRedirectOutput;
83
84impl<From> OutputAlg<From> for AxumRedirectOutput
85where
86 From: Display,
87{
88 type Output = Redirect;
89
90 fn output(from: From) -> Self::Output {
91 Redirect::to(&from.to_string())
92 }
93}
94
95pub struct AxumStreamOutput;
97
98fn moving<Chunks>(chunks: Chunks) -> impl Stream<Item = Result<Vec<u8>, IoError>> + Send
100where
101 Chunks: ChunksAlg + Send + 'static,
102 Chunks::Chunk: Into<Vec<u8>> + Send,
103 Chunks::Error: Display,
104{
105 chunks.moving().map_ok(Into::into).map_err(|error| IoError::other(error.to_string()))
106}
107
108impl<From> OutputAlg<From> for AxumStreamOutput
109where
110 From: ChunksAlg + Send + 'static,
111 From::Chunk: Into<Vec<u8>> + Send,
112 From::Error: Display,
113{
114 type Output = Body;
115
116 fn output(from: From) -> Self::Output {
117 Body::from_stream(moving(from))
118 }
119}
120
121pub struct AxumHeaderOutput<Inner, Name>(PhantomData<fn(Inner, Name)>);
123
124pub struct AxumCarrying<Output, Name> {
126 body: Output,
127 value: String,
128 name: PhantomData<fn(Name)>,
129}
130
131impl<Inner, Name, Value, Rest> OutputAlg<(Value, Rest)> for AxumHeaderOutput<Inner, Name>
132where
133 Inner: OutputAlg<Rest>,
134 Value: Display,
135{
136 type Output = AxumCarrying<Inner::Output, Name>;
137
138 fn output((value, rest): (Value, Rest)) -> Self::Output {
139 AxumCarrying { body: Inner::output(rest), value: value.to_string(), name: PhantomData }
140 }
141}
142
143impl<Output, Name> IntoResponse for AxumCarrying<Output, Name>
144where
145 Output: IntoResponse,
146 Name: HeaderNameAlg,
147{
148 fn into_response(self) -> Response {
149 let mut response = self.body.into_response();
150 if let Ok(value) = HeaderValue::from_str(&self.value) {
151 response.headers_mut().insert(HeaderName::from_static(Name::HEADER_NAME), value);
152 }
153
154 response
155 }
156}
157
158pub struct AxumStatusOutput<Inner, const CODE: u16>(PhantomData<Inner>);
160
161pub struct AxumStatusResponse<Output, const CODE: u16>(Output);
163
164impl<Inner, From, const CODE: u16> OutputAlg<From> for AxumStatusOutput<Inner, CODE>
165where
166 Inner: OutputAlg<From>,
167{
168 type Output = AxumStatusResponse<Inner::Output, CODE>;
169
170 fn output(from: From) -> Self::Output {
171 AxumStatusResponse(Inner::output(from))
172 }
173}
174
175impl<Output, const CODE: u16> IntoResponse for AxumStatusResponse<Output, CODE>
176where
177 Output: IntoResponse,
178{
179 fn into_response(self) -> Response {
180 let mut response = self.0.into_response();
181 *response.status_mut() = axum_status(HttpStatus::new(CODE));
182
183 response
184 }
185}
186
187pub struct AxumResultOutput<Inner, Error>(PhantomData<fn(Inner, Error)>);
189
190pub struct AxumResultResponse<Output>(Result<Output, (HttpStatus, String)>);
192
193impl<Inner, Error, Value> OutputAlg<Result<Value, Error>> for AxumResultOutput<Inner, Error>
194where
195 Inner: OutputAlg<Value>,
196 Error: HttpErrorAlg,
197{
198 type Output = AxumResultResponse<Inner::Output>;
199
200 fn output(from: Result<Value, Error>) -> Self::Output {
201 AxumResultResponse(from.map(Inner::output).map_err(|error| (error.http_status(), error.http_message())))
202 }
203}
204
205impl<Output> IntoResponse for AxumResultResponse<Output>
206where
207 Output: IntoResponse,
208{
209 fn into_response(self) -> Response {
210 match self.0 {
211 Ok(output) => output.into_response(),
212 Err((status, message)) => (axum_status(status), message).into_response(),
213 }
214 }
215}
216
217pub struct AxumFileOutput;
219
220pub struct AxumFileResponse<From>(From);
222
223impl<From> OutputAlg<From> for AxumFileOutput {
224 type Output = AxumFileResponse<From>;
225
226 fn output(from: From) -> Self::Output {
227 AxumFileResponse(from)
228 }
229}
230
231impl<File, Error> IntoResponse for AxumFileResponse<(Result<File, Error>, String)>
232where
233 File: IntoResponse,
234 Error: HttpErrorAlg + Send,
235{
236 fn into_response(self) -> Response {
237 let (file, name) = self.0;
238 match file {
239 Ok(file) => {
240 let mut response = file.into_response();
241 let name = name.replace(['\r', '\n', '"'], "_");
242 if let Ok(value) = HeaderValue::from_str(&format!("attachment; filename=\"{name}\"")) {
243 response.headers_mut().insert(header::CONTENT_DISPOSITION, value);
244 }
245 response
246 }
247 Err(error) => axum_status(error.http_status()).into_response(),
248 }
249 }
250}
251
252macro_rules! axum_outputs {
253 ($($alg:ident => $selected:ident, $output:ty),+ $(,)?) => {
254 $(
255 impl<Context> $alg for AxumHandlerImpl<Context> {
256 type $selected<From> = $output;
257 }
258 )+
259 };
260}
261
262axum_outputs! {
263 JsonOutAlg => Json, AxumJsonOutput,
264 FileOutAlg => File, AxumFileOutput,
265 TextOutAlg => Text, AxumTextOutput,
266 HtmlOutAlg => Html, AxumHtmlOutput,
267 BytesOutAlg => Bytes, AxumBytesOutput,
268 EmptyOutAlg => Empty, AxumEmptyOutput,
269 RedirectOutAlg => Redirect, AxumRedirectOutput,
270 StreamOutAlg => Stream, AxumStreamOutput,
271}
272
273impl<Context> HeaderOutAlg for AxumHandlerImpl<Context> {
274 type Header<Inner, Name> = AxumHeaderOutput<Inner, Name>;
275}
276
277impl<Context> StatusOutAlg for AxumHandlerImpl<Context> {
278 type Status<Inner, const CODE: u16> = AxumStatusOutput<Inner, CODE>;
279}
280
281impl<Context> ResultOutAlg for AxumHandlerImpl<Context> {
282 type Result<Inner, Error> = AxumResultOutput<Inner, Error>;
283}