Skip to main content

alux_http_direct/
output.rs

1//! Renders each output kind as the answer it states.
2
3use 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
14/// Renders a semantic result as a JSON answer.
15pub 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
31/// Renders a semantic result as a plain-text answer.
32pub 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
45/// Renders a semantic result as an HTML answer.
46pub 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
59/// Renders a semantic result as a raw-byte answer.
60pub 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
73/// Renders a handler that returns nothing as an answer with no body.
74pub 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
84/// Renders a semantic location as a redirect.
85pub 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
98/// Renders a semantic file result as a downloadable answer.
99pub 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
120/// Answers with a body produced over time.
121///
122/// Nothing is collected here. This interpretation carries no transport, so the answer carries what
123/// produces the bytes and whatever moves them drives it.
124pub 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
143/// Answers with a header the handler stated, beside the body it stated.
144pub 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
159/// Answers with the status an endpoint declared, around the body it already states.
160pub 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
173/// Answers with what a failure means when the handler failed, and with the body it states otherwise.
174pub 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}