rama_http/service/web/endpoint/
mod.rs1#![expect(
2 clippy::allow_attributes,
3 reason = "macro-generated `#[allow]` attributes whose underlying lints fire only for some expansions"
4)]
5
6use std::convert::Infallible;
7
8use rama_core::{
9 Service,
10 service::{BoxService, StaticOutput},
11};
12
13use crate::{Body, Request, Response, matcher::HttpMatcher};
14
15pub mod extract;
16pub mod response;
17
18use response::IntoResponse;
19
20#[derive(Debug, Clone)]
21pub(crate) struct Endpoint {
22 pub(crate) matcher: HttpMatcher<Body>,
23 pub(crate) service: BoxService<Request, Response, Infallible>,
24}
25
26pub trait IntoEndpointService<T>: private::Sealed<T, ()> {
28 type Service: Service<Request>;
29
30 fn into_endpoint_service(self) -> Self::Service;
32}
33
34pub trait IntoEndpointServiceWithState<T, State>: private::Sealed<T, State> {
35 type Service: Service<Request>;
36
37 fn into_endpoint_service_with_state(self, state: State) -> Self::Service;
39}
40
41impl<S> IntoEndpointService<(S,)> for S
42where
43 S: Service<Request>,
44{
45 type Service = Self;
46
47 #[inline(always)]
48 fn into_endpoint_service(self) -> Self::Service {
49 self
50 }
51}
52
53impl<S, State> IntoEndpointServiceWithState<(S,), State> for S
54where
55 S: Service<Request>,
56{
57 type Service = Self;
58
59 fn into_endpoint_service_with_state(self, _state: State) -> Self::Service {
60 self
61 }
62}
63
64impl<O> IntoEndpointService<()> for Result<O, Infallible>
65where
66 O: Clone + Send + Sync + 'static,
67{
68 type Service = StaticOutput<O>;
69
70 fn into_endpoint_service(self) -> Self::Service {
71 StaticOutput::new(self.unwrap())
72 }
73}
74
75impl<O> IntoEndpointService<Response> for O
76where
77 O: IntoResponse + Clone + Send + Sync + 'static,
78{
79 type Service = StaticOutput<O>;
80
81 fn into_endpoint_service(self) -> Self::Service {
82 StaticOutput::new(self)
83 }
84}
85
86impl<O, State> IntoEndpointServiceWithState<(), State> for Result<O, Infallible>
87where
88 O: Clone + Send + Sync + 'static,
89{
90 type Service = StaticOutput<O>;
91
92 fn into_endpoint_service_with_state(self, _state: State) -> Self::Service {
93 self.into_endpoint_service()
94 }
95}
96
97impl<O, State> IntoEndpointServiceWithState<Response, State> for O
98where
99 O: IntoResponse + Clone + Send + Sync + 'static,
100{
101 type Service = StaticOutput<O>;
102
103 fn into_endpoint_service_with_state(self, _state: State) -> Self::Service {
104 self.into_endpoint_service()
105 }
106}
107
108mod service;
109#[doc(inline)]
110pub use service::EndpointServiceFn;
111
112pub struct EndpointServiceFnWrapper<F, T, State> {
114 inner: F,
115 _marker: std::marker::PhantomData<fn(T)>,
116 state: State,
117}
118
119impl<F: std::fmt::Debug, T, State: std::fmt::Debug> std::fmt::Debug
120 for EndpointServiceFnWrapper<F, T, State>
121{
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 f.debug_struct("EndpointServiceFnWrapper")
124 .field("inner", &self.inner)
125 .field("state", &self.state)
126 .field(
127 "_marker",
128 &format_args!("{}", std::any::type_name::<fn(T)>()),
129 )
130 .finish()
131 }
132}
133
134impl<F, T, State> Clone for EndpointServiceFnWrapper<F, T, State>
135where
136 F: Clone,
137 State: Clone,
138{
139 fn clone(&self) -> Self {
140 Self {
141 inner: self.inner.clone(),
142 _marker: std::marker::PhantomData,
143 state: self.state.clone(),
144 }
145 }
146}
147
148impl<F, T, State> Service<Request> for EndpointServiceFnWrapper<F, T, State>
149where
150 F: EndpointServiceFn<T, State>,
151 T: Send + 'static,
152 State: Send + Sync + Clone + 'static,
153{
154 type Output = F::Output;
155 type Error = F::Error;
156
157 async fn serve(&self, req: Request) -> Result<Self::Output, Self::Error> {
158 self.inner.call(req, &self.state).await
159 }
160}
161
162impl<F, T> IntoEndpointService<(F, T)> for F
163where
164 F: EndpointServiceFn<T, ()>,
165 T: Send + 'static,
166{
167 type Service = EndpointServiceFnWrapper<F, T, ()>;
168
169 fn into_endpoint_service(self) -> Self::Service {
170 EndpointServiceFnWrapper {
171 inner: self,
172 _marker: std::marker::PhantomData,
173 state: (),
174 }
175 }
176}
177
178impl<F, T, State> IntoEndpointServiceWithState<(F, T), State> for F
179where
180 F: EndpointServiceFn<T, State>,
181 T: Send + 'static,
182 State: Send + Sync + Clone + 'static,
183{
184 type Service = EndpointServiceFnWrapper<F, T, State>;
185
186 fn into_endpoint_service_with_state(self, state: State) -> Self::Service {
187 EndpointServiceFnWrapper {
188 inner: self,
189 _marker: std::marker::PhantomData,
190 state,
191 }
192 }
193}
194
195mod private {
196 use super::*;
197
198 pub trait Sealed<T, State> {}
199
200 impl<S, State> Sealed<(S,), State> for S where S: Service<Request> {}
201
202 impl<O, State> Sealed<(), State> for Result<O, Infallible> {}
203
204 impl<O, State> Sealed<Response, State> for O {}
205
206 impl<F, T, State> Sealed<(F, T), State> for F where F: EndpointServiceFn<T, State> {}
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212 use crate::{Body, Method, Request, StatusCode, body::util::BodyExt};
213 use extract::*;
214 use rama_core::conversion::FromRef;
215
216 fn assert_into_endpoint_service<T, I>(_: I)
217 where
218 I: IntoEndpointService<T>,
219 {
220 }
221
222 #[test]
223 fn test_into_endpoint_service_static() {
224 assert_into_endpoint_service(StatusCode::OK);
225 assert_into_endpoint_service("hello");
226 assert_into_endpoint_service("hello".to_owned());
227 }
228
229 #[tokio::test]
230 async fn test_into_endpoint_service_impl() {
231 #[derive(Debug, Clone)]
232 struct OkService;
233
234 impl Service<Request> for OkService {
235 type Output = StatusCode;
236 type Error = Infallible;
237
238 async fn serve(&self, _req: Request) -> Result<Self::Output, Self::Error> {
239 Ok(StatusCode::OK)
240 }
241 }
242
243 let svc = OkService;
244 let resp = svc
245 .serve(
246 Request::builder()
247 .uri("http://example.com")
248 .body(Body::empty())
249 .unwrap(),
250 )
251 .await
252 .unwrap();
253 assert_eq!(resp, StatusCode::OK);
254
255 assert_into_endpoint_service(svc)
256 }
257
258 #[test]
259 fn test_into_endpoint_service_fn_no_param() {
260 assert_into_endpoint_service(async || StatusCode::OK);
261 assert_into_endpoint_service(async || "hello");
262 }
263
264 #[tokio::test]
265 async fn test_service_fn_wrapper_no_param() {
266 let svc = async || StatusCode::OK;
267 let svc = svc.into_endpoint_service();
268
269 let res = svc
270 .serve(
271 Request::builder()
272 .uri("http://example.com")
273 .body(Body::empty())
274 .unwrap(),
275 )
276 .await
277 .unwrap();
278 assert_eq!(res, StatusCode::OK);
279 }
280
281 #[tokio::test]
282 async fn test_service_fn_wrapper_single_param_request() {
283 let svc = async |req: Request| req.uri().to_string();
284 let svc = svc.into_endpoint_service();
285
286 let res = svc
287 .serve(
288 Request::builder()
289 .uri("http://example.com")
290 .body(Body::empty())
291 .unwrap(),
292 )
293 .await
294 .unwrap();
295 assert_eq!(res, "http://example.com")
297 }
298
299 #[tokio::test]
300 async fn test_service_fn_wrapper_with_state() {
301 let state = "test_string".to_owned();
302 let svc = async |State(state): State<String>| state;
303 let svc = svc.into_endpoint_service_with_state(state.clone());
304
305 let res = svc
306 .serve(
307 Request::builder()
308 .uri("http://example.com")
309 .body(Body::empty())
310 .unwrap(),
311 )
312 .await
313 .unwrap();
314 assert_eq!(res, "test_string");
315 }
316
317 #[tokio::test]
318 async fn test_service_fn_wrapper_with_derived_state() {
319 #[derive(Clone, Debug, Default, FromRef)]
320 #[allow(dead_code)]
321 struct GlobalState {
322 numbers: u8,
323 text: String,
324 }
325
326 let state = GlobalState {
327 text: "test_string".to_owned(),
328 ..Default::default()
329 };
330
331 let svc = async |State(state): State<GlobalState>| state.text;
332 let svc = svc.into_endpoint_service_with_state(state.clone());
333
334 let res = svc
335 .serve(
336 Request::builder()
337 .uri("http://example.com")
338 .body(Body::empty())
339 .unwrap(),
340 )
341 .await
342 .unwrap();
343 assert_eq!(res, "test_string");
344 }
345
346 #[tokio::test]
347 async fn test_service_fn_wrapper_single_param_host() {
348 let svc = async |Host(host): Host| host.to_string();
349 let svc = svc.into_endpoint_service();
350
351 let res = svc
352 .serve(
353 Request::builder()
354 .uri("http://example.com")
355 .body(Body::empty())
356 .unwrap(),
357 )
358 .await
359 .unwrap();
360 assert_eq!(res, "example.com")
361 }
362
363 #[tokio::test]
364 async fn test_service_fn_wrapper_multi_param_host() {
365 #[derive(Debug, Clone, serde::Deserialize)]
366 struct Params {
367 foo: String,
368 }
369
370 let svc = crate::service::web::WebService::default().with_get(
371 "/{foo}/bar",
372 async |Host(host): Host, Path(params): Path<Params>| {
373 format!("{} => {}", host, params.foo)
374 },
375 );
376 let svc = svc.into_endpoint_service();
377
378 let resp = svc
379 .serve(
380 Request::builder()
381 .uri("http://example.com/42/bar")
382 .body(Body::empty())
383 .unwrap(),
384 )
385 .await
386 .unwrap();
387 assert_eq!(resp.status(), StatusCode::OK);
388 let body = resp.into_body().collect().await.unwrap().to_bytes();
389 assert_eq!(body, "example.com => 42")
390 }
391
392 #[test]
393 fn test_into_endpoint_service_fn_single_param() {
394 #[derive(Debug, Clone, serde::Deserialize)]
395 struct Params {
396 foo: String,
397 }
398
399 assert_into_endpoint_service(async |_path: Path<Params>| StatusCode::OK);
400 assert_into_endpoint_service(async |Path(params): Path<Params>| params.foo);
401 assert_into_endpoint_service(async |Query(query): Query<Params>| query.foo);
402 assert_into_endpoint_service(async |method: Method| method.to_string());
403 assert_into_endpoint_service(async |req: Request| req.uri().to_string());
404 assert_into_endpoint_service(async |_host: Host| StatusCode::OK);
405 assert_into_endpoint_service(async |Host(_host): Host| StatusCode::OK);
406 }
407}