1use std::{mem, rc::Rc};
2
3use actix_http::{body::MessageBody, Method};
4use actix_service::{
5 apply,
6 boxed::{self, BoxService},
7 fn_service, Service, ServiceFactory, ServiceFactoryExt, Transform,
8};
9use futures_core::future::LocalBoxFuture;
10
11use crate::{
12 guard::{self, Guard},
13 handler::{handler_service, Handler},
14 middleware::Compat,
15 service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse},
16 Error, FromRequest, HttpResponse, Responder,
17};
18
19pub struct Route {
24 service: BoxedHttpServiceFactory,
25 guards: Rc<Vec<Box<dyn Guard>>>,
26}
27
28impl Route {
29 #[allow(clippy::new_without_default)]
31 pub fn new() -> Route {
32 Route {
33 service: boxed::factory(fn_service(|req: ServiceRequest| async {
34 Ok(req.into_response(HttpResponse::NotFound()))
35 })),
36 guards: Rc::new(Vec::new()),
37 }
38 }
39
40 #[doc(alias = "middleware")]
47 #[doc(alias = "use")] pub fn wrap<M, B>(self, mw: M) -> Route
49 where
50 M: Transform<
51 BoxService<ServiceRequest, ServiceResponse, Error>,
52 ServiceRequest,
53 Response = ServiceResponse<B>,
54 Error = Error,
55 InitError = (),
56 > + 'static,
57 B: MessageBody + 'static,
58 {
59 Route {
60 service: boxed::factory(apply(Compat::new(mw), self.service)),
61 guards: self.guards,
62 }
63 }
64
65 pub(crate) fn take_guards(&mut self) -> Vec<Box<dyn Guard>> {
66 mem::take(Rc::get_mut(&mut self.guards).unwrap())
67 }
68}
69
70impl ServiceFactory<ServiceRequest> for Route {
71 type Response = ServiceResponse;
72 type Error = Error;
73 type Config = ();
74 type Service = RouteService;
75 type InitError = ();
76 type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
77
78 fn new_service(&self, _: ()) -> Self::Future {
79 let fut = self.service.new_service(());
80 let guards = Rc::clone(&self.guards);
81
82 Box::pin(async move {
83 let service = fut.await?;
84 Ok(RouteService { service, guards })
85 })
86 }
87}
88
89pub struct RouteService {
90 service: BoxService<ServiceRequest, ServiceResponse, Error>,
91 guards: Rc<Vec<Box<dyn Guard>>>,
92}
93
94impl RouteService {
95 #[allow(clippy::needless_pass_by_ref_mut)]
97 pub fn check(&self, req: &mut ServiceRequest) -> bool {
98 let guard_ctx = req.guard_ctx();
99
100 for guard in self.guards.iter() {
101 if !guard.check(&guard_ctx) {
102 return false;
103 }
104 }
105 true
106 }
107}
108
109impl Service<ServiceRequest> for RouteService {
110 type Response = ServiceResponse;
111 type Error = Error;
112 type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
113
114 actix_service::forward_ready!(service);
115
116 fn call(&self, req: ServiceRequest) -> Self::Future {
117 self.service.call(req)
118 }
119}
120
121impl Route {
122 pub fn method(mut self, method: Method) -> Self {
137 Rc::get_mut(&mut self.guards)
138 .unwrap()
139 .push(Box::new(guard::Method(method)));
140 self
141 }
142
143 pub fn guard<F: Guard + 'static>(mut self, f: F) -> Self {
158 Rc::get_mut(&mut self.guards).unwrap().push(Box::new(f));
159 self
160 }
161
162 #[cfg(feature = "experimental-introspection")]
163 pub(crate) fn guards(&self) -> &Vec<Box<dyn Guard>> {
164 &self.guards
165 }
166
167 pub fn to<F, Args>(mut self, handler: F) -> Self
216 where
217 F: Handler<Args>,
218 Args: FromRequest + 'static,
219 F::Output: Responder + 'static,
220 {
221 self.service = handler_service(handler);
222 self
223 }
224
225 pub fn service<S, E>(mut self, service_factory: S) -> Self
258 where
259 S: ServiceFactory<
260 ServiceRequest,
261 Response = ServiceResponse,
262 Error = E,
263 InitError = (),
264 Config = (),
265 > + 'static,
266 E: Into<Error> + 'static,
267 {
268 self.service = boxed::factory(service_factory.map_err(Into::into));
269 self
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use std::{convert::Infallible, time::Duration};
276
277 use actix_rt::time::sleep;
278 use bytes::Bytes;
279 use futures_core::future::LocalBoxFuture;
280 use serde::Serialize;
281
282 use crate::{
283 dev::{always_ready, fn_factory, fn_service, Service},
284 error,
285 http::{header, Method, StatusCode},
286 middleware::{DefaultHeaders, Logger},
287 service::{ServiceRequest, ServiceResponse},
288 test::{call_service, init_service, read_body, TestRequest},
289 web, App, HttpResponse,
290 };
291
292 #[derive(Serialize, PartialEq, Debug)]
293 struct MyObject {
294 name: String,
295 }
296
297 #[actix_rt::test]
298 async fn test_route() {
299 let srv =
300 init_service(
301 App::new()
302 .service(
303 web::resource("/test")
304 .route(web::get().to(HttpResponse::Ok))
305 .route(web::put().to(|| async {
306 Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
307 }))
308 .route(web::post().to(|| async {
309 sleep(Duration::from_millis(100)).await;
310 Ok::<_, Infallible>(HttpResponse::Created())
311 }))
312 .route(web::delete().to(|| async {
313 sleep(Duration::from_millis(100)).await;
314 Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
315 })),
316 )
317 .service(web::resource("/json").route(web::get().to(|| async {
318 sleep(Duration::from_millis(25)).await;
319 web::Json(MyObject {
320 name: "test".to_string(),
321 })
322 }))),
323 )
324 .await;
325
326 let req = TestRequest::with_uri("/test")
327 .method(Method::GET)
328 .to_request();
329 let resp = call_service(&srv, req).await;
330 assert_eq!(resp.status(), StatusCode::OK);
331
332 let req = TestRequest::with_uri("/test")
333 .method(Method::POST)
334 .to_request();
335 let resp = call_service(&srv, req).await;
336 assert_eq!(resp.status(), StatusCode::CREATED);
337
338 let req = TestRequest::with_uri("/test")
339 .method(Method::PUT)
340 .to_request();
341 let resp = call_service(&srv, req).await;
342 assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
343
344 let req = TestRequest::with_uri("/test")
345 .method(Method::DELETE)
346 .to_request();
347 let resp = call_service(&srv, req).await;
348 assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
349
350 let req = TestRequest::with_uri("/test")
351 .method(Method::HEAD)
352 .to_request();
353 let resp = call_service(&srv, req).await;
354 assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
355
356 let req = TestRequest::with_uri("/json").to_request();
357 let resp = call_service(&srv, req).await;
358 assert_eq!(resp.status(), StatusCode::OK);
359
360 let body = read_body(resp).await;
361 assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}"));
362 }
363
364 #[actix_rt::test]
365 async fn route_middleware() {
366 let srv = init_service(
367 App::new()
368 .route("/", web::get().to(HttpResponse::Ok).wrap(Logger::default()))
369 .service(
370 web::resource("/test")
371 .route(web::get().to(HttpResponse::Ok))
372 .route(
373 web::post()
374 .to(HttpResponse::Created)
375 .wrap(DefaultHeaders::new().add(("x-test", "x-posted"))),
376 )
377 .route(
378 web::delete()
379 .to(HttpResponse::Accepted)
380 .wrap(Logger::default()),
382 ),
383 ),
384 )
385 .await;
386
387 let req = TestRequest::get().uri("/test").to_request();
388 let res = call_service(&srv, req).await;
389 assert_eq!(res.status(), StatusCode::OK);
390 assert!(!res.headers().contains_key("x-test"));
391
392 let req = TestRequest::post().uri("/test").to_request();
393 let res = call_service(&srv, req).await;
394 assert_eq!(res.status(), StatusCode::CREATED);
395 assert_eq!(res.headers().get("x-test").unwrap(), "x-posted");
396
397 let req = TestRequest::delete().uri("/test").to_request();
398 let res = call_service(&srv, req).await;
399 assert_eq!(res.status(), StatusCode::ACCEPTED);
400 }
401
402 #[actix_rt::test]
403 async fn test_service_handler() {
404 struct HelloWorld;
405
406 impl Service<ServiceRequest> for HelloWorld {
407 type Response = ServiceResponse;
408 type Error = crate::Error;
409 type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
410
411 always_ready!();
412
413 fn call(&self, req: ServiceRequest) -> Self::Future {
414 let (req, _) = req.into_parts();
415
416 let res = HttpResponse::Ok()
417 .insert_header(header::ContentType::plaintext())
418 .body("Hello world!");
419
420 Box::pin(async move { Ok(ServiceResponse::new(req, res)) })
421 }
422 }
423
424 let srv = init_service(
425 App::new()
426 .route(
427 "/hello",
428 web::get().service(fn_factory(|| async { Ok(HelloWorld) })),
429 )
430 .route(
431 "/bye",
432 web::get().service(fn_factory(|| async {
433 Ok::<_, ()>(fn_service(|req: ServiceRequest| async {
434 let (req, _) = req.into_parts();
435
436 let res = HttpResponse::Ok()
437 .insert_header(header::ContentType::plaintext())
438 .body("Goodbye, and thanks for all the fish!");
439
440 Ok::<_, Infallible>(ServiceResponse::new(req, res))
441 }))
442 })),
443 ),
444 )
445 .await;
446
447 let req = TestRequest::get().uri("/hello").to_request();
448 let resp = call_service(&srv, req).await;
449 assert_eq!(resp.status(), StatusCode::OK);
450 let body = read_body(resp).await;
451 assert_eq!(body, Bytes::from_static(b"Hello world!"));
452
453 let req = TestRequest::get().uri("/bye").to_request();
454 let resp = call_service(&srv, req).await;
455 assert_eq!(resp.status(), StatusCode::OK);
456 let body = read_body(resp).await;
457 assert_eq!(
458 body,
459 Bytes::from_static(b"Goodbye, and thanks for all the fish!")
460 );
461 }
462}