1use crate::{DirectHandlerImpl, DirectResponse};
4use alux_http::{
5 BytesOutAlg, ChunksAlg, ChunksExt, EmptyOutAlg, FileOutAlg, HeaderNameAlg, HeaderOutAlg, HtmlOutAlg, HttpErrorAlg,
6 HttpStatus, JsonOutAlg, OutputAlg, RedirectOutAlg, ResultOutAlg, StatusOutAlg, StreamOutAlg, TextOutAlg,
7};
8use core::fmt::Display;
9use core::marker::PhantomData;
10use futures::TryStreamExt;
11use serde::Serialize;
12use std::io::Error as IoError;
13
14pub struct DirectJsonOutput;
16
17impl<From> OutputAlg<From> for DirectJsonOutput
18where
19 From: Serialize,
20{
21 type Output = DirectResponse;
22
23 fn output(from: From) -> Self::Output {
24 match serde_json::to_vec(&from) {
25 Ok(body) => DirectResponse::content(HttpStatus::OK, "application/json", body),
26 Err(error) => DirectResponse::content(HttpStatus::INTERNAL, "text/plain; charset=utf-8", error.to_string()),
27 }
28 }
29}
30
31pub struct DirectTextOutput;
33
34impl<From> OutputAlg<From> for DirectTextOutput
35where
36 From: Display,
37{
38 type Output = DirectResponse;
39
40 fn output(from: From) -> Self::Output {
41 DirectResponse::content(HttpStatus::OK, "text/plain; charset=utf-8", from.to_string())
42 }
43}
44
45pub struct DirectHtmlOutput;
47
48impl<From> OutputAlg<From> for DirectHtmlOutput
49where
50 From: Display,
51{
52 type Output = DirectResponse;
53
54 fn output(from: From) -> Self::Output {
55 DirectResponse::content(HttpStatus::OK, "text/html; charset=utf-8", from.to_string())
56 }
57}
58
59pub struct DirectBytesOutput;
61
62impl<From> OutputAlg<From> for DirectBytesOutput
63where
64 From: Into<Vec<u8>>,
65{
66 type Output = DirectResponse;
67
68 fn output(from: From) -> Self::Output {
69 DirectResponse::content(HttpStatus::OK, "application/octet-stream", from)
70 }
71}
72
73pub struct DirectEmptyOutput;
75
76impl OutputAlg<()> for DirectEmptyOutput {
77 type Output = DirectResponse;
78
79 fn output((): ()) -> Self::Output {
80 DirectResponse::new(HttpStatus::NO_CONTENT)
81 }
82}
83
84pub struct DirectRedirectOutput;
86
87impl<From> OutputAlg<From> for DirectRedirectOutput
88where
89 From: Display,
90{
91 type Output = DirectResponse;
92
93 fn output(from: From) -> Self::Output {
94 DirectResponse::new(HttpStatus::SEE_OTHER).with_header("location", &from.to_string())
95 }
96}
97
98pub struct DirectFileOutput;
100
101impl<File, Error> OutputAlg<(Result<File, Error>, String)> for DirectFileOutput
102where
103 File: Into<Vec<u8>>,
104 Error: HttpErrorAlg,
105{
106 type Output = DirectResponse;
107
108 fn output((file, name): (Result<File, Error>, String)) -> Self::Output {
109 match file {
110 Ok(file) => {
111 let name = name.replace(['\r', '\n', '"'], "_");
112 DirectResponse::content(HttpStatus::OK, "application/octet-stream", file)
113 .with_header("content-disposition", &format!("attachment; filename=\"{name}\""))
114 }
115 Err(error) => DirectResponse::new(error.http_status()),
116 }
117 }
118}
119
120pub struct DirectStreamOutput;
125
126impl<From> OutputAlg<From> for DirectStreamOutput
127where
128 From: ChunksAlg + Send + 'static,
129 From::Chunk: Into<Vec<u8>> + Send,
130 From::Error: Display,
131{
132 type Output = DirectResponse;
133
134 fn output(from: From) -> Self::Output {
135 let moving = from.moving().map_ok(Into::into).map_err(|error| IoError::other(error.to_string()));
136
137 DirectResponse::new(HttpStatus::OK)
138 .with_header("content-type", "application/octet-stream")
139 .with_chunks(Box::pin(moving))
140 }
141}
142
143pub struct DirectHeaderOutput<Inner, Name>(PhantomData<fn(Inner, Name)>);
145
146impl<Inner, Name, Value, Rest> OutputAlg<(Value, Rest)> for DirectHeaderOutput<Inner, Name>
147where
148 Inner: OutputAlg<Rest, Output = DirectResponse>,
149 Name: HeaderNameAlg,
150 Value: Display,
151{
152 type Output = DirectResponse;
153
154 fn output((value, rest): (Value, Rest)) -> Self::Output {
155 Inner::output(rest).with_header(Name::HEADER_NAME, &value.to_string())
156 }
157}
158
159pub struct DirectStatusOutput<Inner, const CODE: u16>(PhantomData<Inner>);
161
162impl<Inner, From, const CODE: u16> OutputAlg<From> for DirectStatusOutput<Inner, CODE>
163where
164 Inner: OutputAlg<From, Output = DirectResponse>,
165{
166 type Output = DirectResponse;
167
168 fn output(from: From) -> Self::Output {
169 Inner::output(from).with_status(HttpStatus::new(CODE))
170 }
171}
172
173pub struct DirectResultOutput<Inner, Error>(PhantomData<fn(Inner, Error)>);
175
176impl<Inner, Error, Value> OutputAlg<Result<Value, Error>> for DirectResultOutput<Inner, Error>
177where
178 Inner: OutputAlg<Value, Output = DirectResponse>,
179 Error: HttpErrorAlg,
180{
181 type Output = DirectResponse;
182
183 fn output(from: Result<Value, Error>) -> Self::Output {
184 match from {
185 Ok(value) => Inner::output(value),
186 Err(error) => {
187 DirectResponse::content(error.http_status(), "text/plain; charset=utf-8", error.http_message())
188 }
189 }
190 }
191}
192
193macro_rules! direct_outputs {
194 ($($alg:ident => $selected:ident, $output:ty),+ $(,)?) => {
195 $(
196 impl<Context> $alg for DirectHandlerImpl<Context> {
197 type $selected<From> = $output;
198 }
199 )+
200 };
201}
202
203direct_outputs! {
204 JsonOutAlg => Json, DirectJsonOutput,
205 FileOutAlg => File, DirectFileOutput,
206 TextOutAlg => Text, DirectTextOutput,
207 HtmlOutAlg => Html, DirectHtmlOutput,
208 BytesOutAlg => Bytes, DirectBytesOutput,
209 EmptyOutAlg => Empty, DirectEmptyOutput,
210 RedirectOutAlg => Redirect, DirectRedirectOutput,
211 StreamOutAlg => Stream, DirectStreamOutput,
212}
213
214impl<Context> HeaderOutAlg for DirectHandlerImpl<Context> {
215 type Header<Inner, Name> = DirectHeaderOutput<Inner, Name>;
216}
217
218impl<Context> StatusOutAlg for DirectHandlerImpl<Context> {
219 type Status<Inner, const CODE: u16> = DirectStatusOutput<Inner, CODE>;
220}
221
222impl<Context> ResultOutAlg for DirectHandlerImpl<Context> {
223 type Result<Inner, Error> = DirectResultOutput<Inner, Error>;
224}