1use crate::ActixHandlerImpl;
4use actix_web::HttpResponse;
5use actix_web::http::StatusCode;
6use actix_web::http::header::{self, HeaderName, HeaderValue};
7use actix_web::web::Bytes;
8use alux_http::{
9 BytesOutAlg, ChunksAlg, ChunksExt, EmptyOutAlg, FileOutAlg, HeaderNameAlg, HeaderOutAlg, HtmlOutAlg, HttpErrorAlg,
10 HttpStatus, JsonOutAlg, OutputAlg, RedirectOutAlg, ResultOutAlg, StatusOutAlg, StreamOutAlg, TextOutAlg,
11};
12use core::fmt::Display;
13use core::marker::PhantomData;
14use futures::{Stream, TryStreamExt};
15use serde::Serialize;
16use std::io::Error as IoError;
17
18pub fn actix_status(status: HttpStatus) -> StatusCode {
20 StatusCode::from_u16(status.code()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
21}
22
23pub struct ActixJsonOutput;
25
26impl<From> OutputAlg<From> for ActixJsonOutput
27where
28 From: Serialize,
29{
30 type Output = HttpResponse;
31
32 fn output(from: From) -> Self::Output {
33 HttpResponse::Ok().json(from)
34 }
35}
36
37macro_rules! actix_text_outputs {
38 ($($output:ident => $content_type:literal, $meaning:literal),+ $(,)?) => {
39 $(
40 #[doc = concat!("Renders a semantic result as ", $meaning, ".")]
41 pub struct $output;
42
43 impl<From> OutputAlg<From> for $output
44 where
45 From: Display,
46 {
47 type Output = HttpResponse;
48
49 fn output(from: From) -> Self::Output {
50 HttpResponse::Ok().content_type($content_type).body(from.to_string())
51 }
52 }
53 )+
54 };
55}
56
57actix_text_outputs! {
58 ActixTextOutput => "text/plain; charset=utf-8", "a plain-text response",
59 ActixHtmlOutput => "text/html; charset=utf-8", "an HTML response",
60}
61
62pub struct ActixBytesOutput;
64
65impl<From> OutputAlg<From> for ActixBytesOutput
66where
67 From: Into<Vec<u8>>,
68{
69 type Output = HttpResponse;
70
71 fn output(from: From) -> Self::Output {
72 HttpResponse::Ok().content_type("application/octet-stream").body(from.into())
73 }
74}
75
76pub struct ActixEmptyOutput;
78
79impl OutputAlg<()> for ActixEmptyOutput {
80 type Output = HttpResponse;
81
82 fn output((): ()) -> Self::Output {
83 HttpResponse::NoContent().finish()
84 }
85}
86
87pub struct ActixRedirectOutput;
89
90impl<From> OutputAlg<From> for ActixRedirectOutput
91where
92 From: Display,
93{
94 type Output = HttpResponse;
95
96 fn output(from: From) -> Self::Output {
97 let mut answer = HttpResponse::SeeOther().finish();
98 if let Ok(value) = HeaderValue::from_str(&from.to_string()) {
99 answer.headers_mut().insert(header::LOCATION, value);
100 }
101
102 answer
103 }
104}
105
106pub struct ActixFileOutput;
108
109impl<File, Error> OutputAlg<(Result<File, Error>, String)> for ActixFileOutput
110where
111 File: Into<Vec<u8>>,
112 Error: HttpErrorAlg,
113{
114 type Output = HttpResponse;
115
116 fn output((file, name): (Result<File, Error>, String)) -> Self::Output {
117 match file {
118 Ok(file) => {
119 let name = name.replace(['\r', '\n', '"'], "_");
120 HttpResponse::Ok()
121 .content_type("application/octet-stream")
122 .insert_header((header::CONTENT_DISPOSITION, format!("attachment; filename=\"{name}\"")))
123 .body(file.into())
124 }
125 Err(error) => HttpResponse::new(actix_status(error.http_status())),
126 }
127 }
128}
129
130fn moving<Chunks>(chunks: Chunks) -> impl Stream<Item = Result<Vec<u8>, IoError>> + Send
132where
133 Chunks: ChunksAlg + Send + 'static,
134 Chunks::Chunk: Into<Vec<u8>> + Send,
135 Chunks::Error: Display,
136{
137 chunks.moving().map_ok(Into::into).map_err(|error| IoError::other(error.to_string()))
138}
139
140pub struct ActixStreamOutput;
142
143impl<From> OutputAlg<From> for ActixStreamOutput
144where
145 From: ChunksAlg + Send + 'static,
146 From::Chunk: Into<Vec<u8>> + Send,
147 From::Error: Display,
148{
149 type Output = HttpResponse;
150
151 fn output(from: From) -> Self::Output {
152 let moving = moving(from).map_ok(Bytes::from);
153
154 HttpResponse::Ok().content_type("application/octet-stream").streaming(moving)
155 }
156}
157
158pub struct ActixHeaderOutput<Inner, Name>(PhantomData<fn(Inner, Name)>);
160
161impl<Inner, Name, Value, Rest> OutputAlg<(Value, Rest)> for ActixHeaderOutput<Inner, Name>
162where
163 Inner: OutputAlg<Rest, Output = HttpResponse>,
164 Name: HeaderNameAlg,
165 Value: Display,
166{
167 type Output = HttpResponse;
168
169 fn output((value, rest): (Value, Rest)) -> Self::Output {
170 let mut answer = Inner::output(rest);
171 if let Ok(value) = HeaderValue::from_str(&value.to_string()) {
172 answer.headers_mut().insert(HeaderName::from_static(Name::HEADER_NAME), value);
173 }
174
175 answer
176 }
177}
178
179pub struct ActixStatusOutput<Inner, const CODE: u16>(PhantomData<Inner>);
181
182impl<Inner, From, const CODE: u16> OutputAlg<From> for ActixStatusOutput<Inner, CODE>
183where
184 Inner: OutputAlg<From, Output = HttpResponse>,
185{
186 type Output = HttpResponse;
187
188 fn output(from: From) -> Self::Output {
189 let mut answer = Inner::output(from);
190 *answer.status_mut() = actix_status(HttpStatus::new(CODE));
191
192 answer
193 }
194}
195
196pub struct ActixResultOutput<Inner, Error>(PhantomData<fn(Inner, Error)>);
198
199impl<Inner, Error, Value> OutputAlg<Result<Value, Error>> for ActixResultOutput<Inner, Error>
200where
201 Inner: OutputAlg<Value, Output = HttpResponse>,
202 Error: HttpErrorAlg,
203{
204 type Output = HttpResponse;
205
206 fn output(from: Result<Value, Error>) -> Self::Output {
207 match from {
208 Ok(value) => Inner::output(value),
209 Err(error) => HttpResponse::build(actix_status(error.http_status()))
210 .content_type("text/plain; charset=utf-8")
211 .body(error.http_message()),
212 }
213 }
214}
215
216macro_rules! actix_outputs {
217 ($($alg:ident => $selected:ident, $output:ty),+ $(,)?) => {
218 $(
219 impl<Context> $alg for ActixHandlerImpl<Context> {
220 type $selected<From> = $output;
221 }
222 )+
223 };
224}
225
226actix_outputs! {
227 JsonOutAlg => Json, ActixJsonOutput,
228 FileOutAlg => File, ActixFileOutput,
229 TextOutAlg => Text, ActixTextOutput,
230 HtmlOutAlg => Html, ActixHtmlOutput,
231 BytesOutAlg => Bytes, ActixBytesOutput,
232 EmptyOutAlg => Empty, ActixEmptyOutput,
233 RedirectOutAlg => Redirect, ActixRedirectOutput,
234 StreamOutAlg => Stream, ActixStreamOutput,
235}
236
237impl<Context> HeaderOutAlg for ActixHandlerImpl<Context> {
238 type Header<Inner, Name> = ActixHeaderOutput<Inner, Name>;
239}
240
241impl<Context> StatusOutAlg for ActixHandlerImpl<Context> {
242 type Status<Inner, const CODE: u16> = ActixStatusOutput<Inner, CODE>;
243}
244
245impl<Context> ResultOutAlg for ActixHandlerImpl<Context> {
246 type Result<Inner, Error> = ActixResultOutput<Inner, Error>;
247}