1use crate::{RocketAnswer, RocketBody, RocketRequest};
4use alux_http::{
5 HttpMethod, HttpSelectorAlg, PathSegment, PathSyntaxAlg, RouteAlg, RoutePath, SelectorAlg, compose_path,
6 describe_path,
7};
8use bytes::Bytes;
9use core::future::Future;
10use core::pin::Pin;
11use futures::TryStreamExt;
12use rocket::data::{ByteUnit, Data};
13use rocket::http::{HeaderMap, Method, Status};
14use rocket::response::Response;
15use rocket::route::{Handler, Outcome, Route};
16use rocket::{Build, Request, Rocket, async_trait};
17use std::io::Cursor;
18use std::sync::Arc;
19use tokio_util::io::StreamReader;
20
21const READS: ByteUnit = ByteUnit::Mebibyte(8);
27
28const TOO_LARGE: Status = Status { code: 413 };
30
31const UNREADABLE: Status = Status { code: 400 };
33
34struct RocketPath;
36
37impl PathSyntaxAlg for RocketPath {
38 fn param(&self, name: &str) -> String {
39 format!("<{name}>")
40 }
41
42 fn tail(&self, name: &str) -> String {
43 format!("<{name}..>")
44 }
45}
46
47fn rocket_method(method: HttpMethod) -> Method {
49 match method {
50 HttpMethod::Get => Method::Get,
51 HttpMethod::Post => Method::Post,
52 HttpMethod::Put => Method::Put,
53 HttpMethod::Patch => Method::Patch,
54 HttpMethod::Delete => Method::Delete,
55 HttpMethod::Head => Method::Head,
56 HttpMethod::Options => Method::Options,
57 HttpMethod::Trace => Method::Trace,
58 HttpMethod::Connect => Method::Connect,
59 }
60}
61
62pub type Answer = Pin<Box<dyn Future<Output = RocketAnswer> + Send>>;
64
65#[derive(Debug, Clone, PartialEq, Eq)]
66enum RocketSelectorPart {
67 Method(HttpMethod),
68 Path(RoutePath),
69 Prefix(RoutePath),
70}
71
72#[derive(Debug, Clone, Default, PartialEq, Eq)]
74pub struct RocketSelector {
75 parts: Vec<RocketSelectorPart>,
76}
77
78impl RocketSelector {
79 pub fn path(&self) -> String {
81 describe_path(self.paths())
82 }
83
84 pub(crate) fn rocket_path(&self) -> String {
86 compose_path(self.paths(), &RocketPath)
87 }
88
89 fn paths(&self) -> impl Iterator<Item = &RoutePath> {
90 self.parts.iter().filter_map(|part| match part {
91 RocketSelectorPart::Path(path) | RocketSelectorPart::Prefix(path) => Some(path),
92 RocketSelectorPart::Method(_) => None,
93 })
94 }
95
96 fn segments(&self) -> Vec<PathSegment> {
97 self.paths().flat_map(|path| path.segments().iter().cloned()).collect()
98 }
99
100 pub fn label(&self) -> String {
102 let method = self.method().map_or("*", HttpMethod::label);
103 format!("{method} {}", self.path())
104 }
105
106 pub(crate) fn method(&self) -> Option<HttpMethod> {
107 self.parts.iter().rev().find_map(|part| match part {
108 RocketSelectorPart::Method(method) => Some(*method),
109 RocketSelectorPart::Path(_) | RocketSelectorPart::Prefix(_) => None,
110 })
111 }
112
113 fn captures(&self, request: &Request<'_>) -> Vec<String> {
115 let mut captured = Vec::new();
116 for (index, segment) in self.segments().iter().enumerate() {
117 match segment {
118 PathSegment::Literal(_) => {}
119 PathSegment::Param(_) => {
120 captured.push(request.routed_segment(index).unwrap_or_default().to_owned());
121 }
122 PathSegment::Tail(_) => {
123 let tail = request.routed_segments(index..).collect::<Vec<_>>().join("/");
124 captured.push(tail);
125 }
126 }
127 }
128
129 captured
130 }
131}
132
133#[derive(Clone)]
135pub struct RocketEndpoint(Arc<dyn Fn(RocketRequest) -> Answer + Send + Sync>);
136
137impl RocketEndpoint {
138 pub fn new<Reach>(reach: Reach) -> Self
140 where
141 Reach: Fn(RocketRequest) -> Answer + Send + Sync + 'static,
142 {
143 Self(Arc::new(reach))
144 }
145}
146
147#[derive(Clone)]
148struct RocketRouteEntry {
149 selector: RocketSelector,
150 endpoint: RocketEndpoint,
151}
152
153#[derive(Clone, Default)]
155pub struct RocketRoute {
156 entries: Vec<RocketRouteEntry>,
157}
158
159impl RocketRoute {
160 pub fn labels(&self) -> Vec<String> {
162 self.entries.iter().map(|entry| entry.selector.label()).collect()
163 }
164
165 pub fn paths(&self) -> Vec<String> {
167 self.entries.iter().map(|entry| entry.selector.path()).collect()
168 }
169
170 pub fn into_rocket(self) -> Vec<Route> {
175 self.entries
176 .into_iter()
177 .filter_map(|entry| {
178 let method = rocket_method(entry.selector.method()?);
179 let path = entry.selector.rocket_path();
180 let reaching = Reaching { selector: entry.selector, endpoint: entry.endpoint };
181
182 Some(Route::new(method, &path, reaching))
183 })
184 .collect()
185 }
186
187 pub fn mount(self, rocket: Rocket<Build>) -> Rocket<Build> {
189 rocket.mount("/", self.into_rocket())
190 }
191}
192
193#[derive(Clone)]
195struct Reaching {
196 selector: RocketSelector,
197 endpoint: RocketEndpoint,
198}
199
200#[async_trait]
201impl Handler for Reaching {
202 async fn handle<'r>(&self, request: &'r Request<'_>, data: Data<'r>) -> Outcome<'r> {
203 let captures = self.selector.captures(request);
204 let query = request.uri().query().map(|query| query.as_str().to_owned()).unwrap_or_default();
205 let mut headers = HeaderMap::new();
206 for header in request.headers().iter() {
207 headers.add_raw(header.name().to_string(), header.value().to_string());
208 }
209 let body = match data.open(READS).into_bytes().await {
210 Ok(read) if !read.is_complete() => {
213 return Outcome::Success(refused(TOO_LARGE, "the body is larger than this service reads"));
214 }
215 Ok(read) => read.into_inner(),
216 Err(error) => {
217 return Outcome::Success(refused(UNREADABLE, &format!("the body could not be read: {error}")));
218 }
219 };
220 let answered = (self.endpoint.0)(RocketRequest { captures, query, headers, body }).await;
221
222 Outcome::Success(respond(answered))
223 }
224}
225
226fn refused<'r>(status: Status, message: &str) -> Response<'r> {
229 let said = message.to_owned();
230 let mut response = Response::build();
231 response.status(status);
232 response.raw_header("content-type", "text/plain; charset=utf-8");
233 response.sized_body(said.len(), Cursor::new(said));
234
235 response.finalize()
236}
237
238fn respond<'r>(answered: RocketAnswer) -> Response<'r> {
239 let mut response = Response::build();
240 response.status(Status::new(answered.status.code()));
241 for (name, value) in answered.headers {
242 response.raw_header(name, value);
243 }
244 match answered.body {
245 RocketBody::Stated(body) => response.sized_body(body.len(), Cursor::new(body)),
246 RocketBody::Produced(chunks) => response.streamed_body(StreamReader::new(chunks.map_ok(Bytes::from))),
248 };
249
250 response.finalize()
251}
252
253#[derive(Debug, Default)]
255pub struct RocketRouteImpl;
256
257impl SelectorAlg for RocketRouteImpl {
258 type Selector = RocketSelector;
259
260 fn identity(&self) -> RocketSelector {
261 RocketSelector::default()
262 }
263
264 fn compose(&self, mut first: RocketSelector, second: RocketSelector) -> RocketSelector {
265 first.parts.extend(second.parts);
266 first
267 }
268}
269
270impl RouteAlg for RocketRouteImpl {
271 type Route = RocketRoute;
272 type Selector = RocketSelector;
273 type Endpoint = RocketEndpoint;
274
275 fn initial(&self) -> RocketRoute {
276 RocketRoute::default()
277 }
278
279 fn coproduct(&self, mut left: RocketRoute, right: RocketRoute) -> RocketRoute {
280 left.entries.extend(right.entries);
281 left
282 }
283
284 fn precompose(&self, selector: RocketSelector, mut route: RocketRoute) -> RocketRoute {
285 for entry in &mut route.entries {
286 entry.selector = self.compose(selector.clone(), core::mem::take(&mut entry.selector));
287 }
288 route
289 }
290
291 fn lift(&self, endpoint: RocketEndpoint) -> RocketRoute {
292 RocketRoute { entries: vec![RocketRouteEntry { selector: self.identity(), endpoint }] }
293 }
294}
295
296impl HttpSelectorAlg for RocketRouteImpl {
297 type Selector = RocketSelector;
298
299 fn http_method(&self, method: HttpMethod) -> RocketSelector {
300 RocketSelector { parts: vec![RocketSelectorPart::Method(method)] }
301 }
302
303 fn http_path(&self, path: &RoutePath) -> RocketSelector {
304 RocketSelector { parts: vec![RocketSelectorPart::Path(path.clone())] }
305 }
306
307 fn http_prefix(&self, prefix: &RoutePath) -> RocketSelector {
308 RocketSelector { parts: vec![RocketSelectorPart::Prefix(prefix.clone())] }
309 }
310}