alux_http_parts/
message.rs1use alux_http::{HttpErrorAlg, HttpStatus};
4use core::error::Error;
5use core::fmt::{self, Debug, Display};
6use core::pin::Pin;
7use futures::{Stream, StreamExt};
8use std::io::Error as IoError;
9
10#[derive(Debug, Clone, Default, PartialEq, Eq)]
14pub struct DirectRequest {
15 method: Option<alux_http::HttpMethod>,
16 path: String,
17 query: String,
18 headers: Vec<(String, String)>,
19 body: Vec<u8>,
20}
21
22impl DirectRequest {
23 pub fn new(method: alux_http::HttpMethod, path: &str) -> Self {
25 Self { method: Some(method), path: path.to_owned(), ..Self::default() }
26 }
27
28 #[must_use]
30 pub fn with_query(mut self, query: &str) -> Self {
31 query.trim_start_matches('?').clone_into(&mut self.query);
32 self
33 }
34
35 #[must_use]
37 pub fn with_header(mut self, name: &str, value: &str) -> Self {
38 self.headers.push((name.to_lowercase(), value.to_owned()));
39 self
40 }
41
42 #[must_use]
44 pub fn with_body(mut self, body: impl Into<Vec<u8>>) -> Self {
45 self.body = body.into();
46 self
47 }
48
49 pub fn method(&self) -> Option<alux_http::HttpMethod> {
51 self.method
52 }
53
54 pub fn path(&self) -> &str {
56 &self.path
57 }
58
59 pub fn query(&self) -> &str {
61 &self.query
62 }
63
64 pub fn header(&self, name: &str) -> Option<&str> {
66 let name = name.to_lowercase();
67
68 self.headers.iter().find(|(header, _)| *header == name).map(|(_, value)| value.as_str())
69 }
70
71 pub fn headers(&self) -> impl Iterator<Item = (&str, &str)> {
73 self.headers.iter().map(|(name, value)| (name.as_str(), value.as_str()))
74 }
75
76 pub fn body(&self) -> &[u8] {
78 &self.body
79 }
80}
81
82pub enum DirectBody {
87 Stated(Vec<u8>),
89 Produced(Chunks),
91}
92
93impl Debug for DirectBody {
94 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
95 match self {
96 Self::Stated(body) => formatter.debug_tuple("Stated").field(body).finish(),
97 Self::Produced(_) => formatter.write_str("Produced(..)"),
98 }
99 }
100}
101
102impl Default for DirectBody {
103 fn default() -> Self {
104 Self::Stated(Vec::new())
105 }
106}
107
108pub type Chunks = Pin<Box<dyn Stream<Item = Result<Vec<u8>, IoError>> + Send>>;
110
111#[derive(Debug)]
113pub struct DirectResponse {
114 status: HttpStatus,
115 headers: Vec<(String, String)>,
116 body: DirectBody,
117}
118
119impl DirectResponse {
120 pub fn new(status: HttpStatus) -> Self {
122 Self { status, headers: Vec::new(), body: DirectBody::default() }
123 }
124
125 pub fn content(status: HttpStatus, content_type: &str, body: impl Into<Vec<u8>>) -> Self {
127 Self::new(status).with_header("content-type", content_type).with_body(body)
128 }
129
130 #[must_use]
132 pub fn with_header(mut self, name: &str, value: &str) -> Self {
133 self.headers.push((name.to_lowercase(), value.to_owned()));
134 self
135 }
136
137 #[must_use]
139 pub fn with_body(mut self, body: impl Into<Vec<u8>>) -> Self {
140 self.body = DirectBody::Stated(body.into());
141 self
142 }
143
144 #[must_use]
146 pub fn with_chunks(mut self, chunks: Chunks) -> Self {
147 self.body = DirectBody::Produced(chunks);
148 self
149 }
150
151 pub fn into_body(self) -> DirectBody {
153 self.body
154 }
155
156 pub async fn collected(mut self) -> Self {
161 let DirectBody::Produced(mut chunks) = self.body else {
162 return self;
163 };
164 let mut collected = Vec::new();
165 while let Some(chunk) = chunks.next().await {
166 match chunk {
167 Ok(chunk) => collected.extend(chunk),
168 Err(error) => {
169 self.body = DirectBody::Stated(error.to_string().into_bytes());
170
171 return self.with_status(HttpStatus::INTERNAL);
172 }
173 }
174 }
175 self.body = DirectBody::Stated(collected);
176
177 self
178 }
179
180 #[must_use]
182 pub fn with_status(mut self, status: HttpStatus) -> Self {
183 self.status = status;
184 self
185 }
186
187 pub fn status(&self) -> HttpStatus {
189 self.status
190 }
191
192 pub fn header(&self, name: &str) -> Option<&str> {
194 let name = name.to_lowercase();
195
196 self.headers.iter().find(|(header, _)| *header == name).map(|(_, value)| value.as_str())
197 }
198
199 pub fn headers(&self) -> impl Iterator<Item = (&str, &str)> {
201 self.headers.iter().map(|(name, value)| (name.as_str(), value.as_str()))
202 }
203
204 pub fn body(&self) -> &[u8] {
206 match &self.body {
207 DirectBody::Stated(body) => body,
208 DirectBody::Produced(_) => &[],
209 }
210 }
211
212 pub fn text(&self) -> String {
214 String::from_utf8_lossy(self.body()).into_owned()
215 }
216}
217
218#[derive(Debug, Clone, PartialEq, Eq)]
222pub struct DirectError {
223 status: HttpStatus,
224 message: String,
225}
226
227impl DirectError {
228 pub fn new(status: HttpStatus, message: impl Into<String>) -> Self {
230 Self { status, message: message.into() }
231 }
232
233 pub fn not_found(path: &str) -> Self {
235 Self::new(HttpStatus::NOT_FOUND, format!("nothing answers at `{path}`"))
236 }
237
238 pub fn method_not_allowed(path: &str) -> Self {
240 Self::new(HttpStatus::METHOD_NOT_ALLOWED, format!("`{path}` does not answer this method"))
241 }
242
243 pub fn unreadable(role: &str, reason: &str) -> Self {
245 Self::new(HttpStatus::BAD_REQUEST, format!("the {role} could not be read: {reason}"))
246 }
247}
248
249impl HttpErrorAlg for DirectError {
250 const HTTP_STATUSES: &'static [HttpStatus] =
252 &[HttpStatus::BAD_REQUEST, HttpStatus::NOT_FOUND, HttpStatus::METHOD_NOT_ALLOWED];
253
254 fn http_status(&self) -> HttpStatus {
255 self.status
256 }
257
258 fn http_message(&self) -> String {
259 self.message.clone()
260 }
261}
262
263impl Display for DirectError {
264 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
265 write!(formatter, "{} ({})", self.message, self.status.code())
266 }
267}
268
269impl Error for DirectError {}
270
271impl From<DirectError> for DirectResponse {
272 fn from(error: DirectError) -> Self {
273 Self::content(error.status, "text/plain; charset=utf-8", error.message)
274 }
275}