1mod associated;
4mod draw;
5mod modify;
6mod single;
7
8use std::marker::PhantomData;
9use std::panic::RefUnwindSafe;
10
11use hyper::{Body, StatusCode};
12
13use crate::extractor::{
14 NoopPathExtractor, NoopQueryStringExtractor, PathExtractor, QueryStringExtractor,
15};
16use crate::pipeline::{finalize_pipeline_set, new_pipeline_set, PipelineHandleChain, PipelineSet};
17use crate::router::response::{ResponseExtender, ResponseFinalizerBuilder};
18use crate::router::route::dispatch::DispatcherImpl;
19use crate::router::route::matcher::{AndRouteMatcher, RouteMatcher};
20use crate::router::route::{Delegation, Extractors, RouteImpl};
21use crate::router::tree::node::Node;
22use crate::router::tree::Tree;
23use crate::router::Router;
24
25pub use self::associated::{AssociatedRouteBuilder, AssociatedSingleRouteBuilder};
26pub use self::draw::DrawRoutes;
27pub use self::modify::{ExtendRouteMatcher, ReplacePathExtractor, ReplaceQueryStringExtractor};
28pub use self::single::DefineSingleRoute;
29
30pub fn build_router<C, P, F>(pipeline_chain: C, pipelines: PipelineSet<P>, f: F) -> Router
79where
80 C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
81 P: Send + Sync + 'static,
82 F: FnOnce(&mut RouterBuilder<'_, C, P>),
83{
84 let mut tree = Tree::new();
85
86 let response_finalizer = {
87 let mut builder = RouterBuilder {
88 node_builder: tree.borrow_root_mut(),
89 pipeline_chain,
90 pipelines,
91 response_finalizer_builder: ResponseFinalizerBuilder::new(),
92 };
93
94 f(&mut builder);
95
96 builder.response_finalizer_builder.finalize()
97 };
98
99 Router::new(tree, response_finalizer)
100}
101
102pub fn build_simple_router<F>(f: F) -> Router
136where
137 F: FnOnce(&mut RouterBuilder<'_, (), ()>),
138{
139 let pipelines = finalize_pipeline_set(new_pipeline_set());
140 build_router((), pipelines, f)
141}
142
143pub struct RouterBuilder<'a, C, P>
146where
147 C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
148 P: Send + Sync + 'static,
149{
150 node_builder: &'a mut Node,
151 pipeline_chain: C,
152 pipelines: PipelineSet<P>,
153 response_finalizer_builder: ResponseFinalizerBuilder,
154}
155
156impl<'a, C, P> RouterBuilder<'a, C, P>
157where
158 C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
159 P: Send + Sync + 'static,
160{
161 pub fn add_response_extender<E>(&mut self, status_code: StatusCode, extender: E)
209 where
210 E: ResponseExtender<Body> + Send + Sync + 'static,
211 {
212 self.response_finalizer_builder
213 .add(status_code, Box::new(extender))
214 }
215}
216
217pub struct ScopeBuilder<'a, C, P>
220where
221 C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
222 P: Send + Sync + 'static,
223{
224 node_builder: &'a mut Node,
225 pipeline_chain: C,
226 pipelines: PipelineSet<P>,
227}
228
229pub struct DelegateRouteBuilder<'a, M, C, P>
232where
233 M: RouteMatcher + Send + Sync + 'static,
234 C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
235 P: Send + Sync + 'static,
236{
237 matcher: M,
238 node_builder: &'a mut Node,
239 pipeline_chain: C,
240 pipelines: PipelineSet<P>,
241}
242
243type DelegatedRoute<M> = RouteImpl<M, NoopPathExtractor, NoopQueryStringExtractor>;
244
245impl<'a, M, C, P> DelegateRouteBuilder<'a, M, C, P>
246where
247 M: RouteMatcher + Send + Sync + 'static,
248 C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
249 P: RefUnwindSafe + Send + Sync + 'static,
250{
251 pub fn to_router(self, router: Router) {
253 let dispatcher = DispatcherImpl::new(router, self.pipeline_chain, self.pipelines);
254 let route: DelegatedRoute<M> = DelegatedRoute::new(
255 self.matcher,
256 Box::new(dispatcher),
257 Extractors::new(),
258 Delegation::External,
259 );
260
261 self.node_builder.add_route(Box::new(route));
262 }
263
264 pub fn add_route_matcher<NM: RouteMatcher + Send + Sync + 'static>(
266 self,
267 matcher: NM,
268 ) -> DelegateRouteBuilder<'a, AndRouteMatcher<M, NM>, C, P> {
269 DelegateRouteBuilder {
270 matcher: AndRouteMatcher::<M, NM>::new(self.matcher, matcher),
271 node_builder: self.node_builder,
272 pipeline_chain: self.pipeline_chain,
273 pipelines: self.pipelines,
274 }
275 }
276}
277
278pub struct SingleRouteBuilder<'a, M, C, P, PE, QSE>
281where
282 M: RouteMatcher + Send + Sync + 'static,
283 C: PipelineHandleChain<P> + Send + Sync + 'static,
284 P: Send + Sync + 'static,
285 PE: PathExtractor<Body> + Send + Sync + 'static,
286 QSE: QueryStringExtractor<Body> + Send + Sync + 'static,
287{
288 node_builder: &'a mut Node,
289 matcher: M,
290 pipeline_chain: C,
291 pipelines: PipelineSet<P>,
292 phantom: PhantomData<(PE, QSE)>,
293}
294
295impl<'a, M, C, P, PE, QSE> SingleRouteBuilder<'a, M, C, P, PE, QSE>
297where
298 M: RouteMatcher + Send + Sync + 'static,
299 C: PipelineHandleChain<P> + Send + Sync + 'static,
300 P: Send + Sync + 'static,
301 PE: PathExtractor<Body> + Send + Sync + 'static,
302 QSE: QueryStringExtractor<Body> + Send + Sync + 'static,
303{
304 fn coerce<NPE, NQSE>(self) -> SingleRouteBuilder<'a, M, C, P, NPE, NQSE>
307 where
308 NPE: PathExtractor<Body> + Send + Sync + 'static,
309 NQSE: QueryStringExtractor<Body> + Send + Sync + 'static,
310 {
311 SingleRouteBuilder {
312 node_builder: self.node_builder,
313 matcher: self.matcher,
314 pipeline_chain: self.pipeline_chain,
315 pipelines: self.pipelines,
316 phantom: PhantomData,
317 }
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 use hyper::service::Service;
326 use hyper::{body, Body, Request, Response, StatusCode};
327 use serde::Deserialize;
328
329 use crate::middleware::cookie::CookieParser;
330 use crate::pipeline::new_pipeline;
331 use crate::router::response::StaticResponseExtender;
332 use crate::service::GothamService;
333 use crate::state::{State, StateData};
334
335 #[derive(Deserialize)]
336 struct SalutationParams {
337 name: String,
338 }
339
340 impl StateData for SalutationParams {}
341
342 impl StaticResponseExtender for SalutationParams {
343 type ResBody = Body;
344 fn extend(_: &mut State, _: &mut Response<Body>) {}
345 }
346
347 #[derive(Deserialize)]
348 struct AddParams {
349 x: u64,
350 y: u64,
351 }
352
353 impl StateData for AddParams {}
354
355 impl StaticResponseExtender for AddParams {
356 type ResBody = Body;
357 fn extend(_: &mut State, _: &mut Response<Body>) {}
358 }
359
360 mod welcome {
361 use super::*;
362 pub(crate) fn index(state: State) -> (State, Response<Body>) {
363 (
364 state,
365 Response::builder()
366 .status(StatusCode::OK)
367 .body(Body::empty())
368 .unwrap(),
369 )
370 }
371
372 pub(crate) fn literal(state: State) -> (State, Response<Body>) {
373 (
374 state,
375 Response::builder()
376 .status(StatusCode::CREATED)
377 .body(Body::empty())
378 .unwrap(),
379 )
380 }
381
382 pub(crate) fn hello(mut state: State) -> (State, Response<Body>) {
383 let params = state.take::<SalutationParams>();
384 let response = Response::builder()
385 .status(StatusCode::OK)
386 .body(format!("Hello, {}!", params.name).into())
387 .unwrap();
388 (state, response)
389 }
390
391 pub(crate) fn globbed(state: State) -> (State, Response<Body>) {
392 let response = Response::builder()
393 .status(StatusCode::OK)
394 .body("Globbed".into())
395 .unwrap();
396 (state, response)
397 }
398
399 pub(crate) fn delegated(state: State) -> (State, Response<Body>) {
400 let response = Response::builder()
401 .status(StatusCode::OK)
402 .body("Delegated".into())
403 .unwrap();
404 (state, response)
405 }
406
407 pub(crate) fn goodbye(mut state: State) -> (State, Response<Body>) {
408 let params = state.take::<SalutationParams>();
409 let response = Response::builder()
410 .status(StatusCode::OK)
411 .body(format!("Goodbye, {}!", params.name).into())
412 .unwrap();
413 (state, response)
414 }
415
416 pub(crate) fn add(mut state: State) -> (State, Response<Body>) {
417 let params = state.take::<AddParams>();
418 let response = Response::builder()
419 .status(StatusCode::OK)
420 .body(format!("{} + {} = {}", params.x, params.y, params.x + params.y,).into())
421 .unwrap();
422 (state, response)
423 }
424
425 pub(crate) fn trailing_slash(state: State) -> (State, Response<Body>) {
426 let response = Response::builder()
427 .status(StatusCode::OK)
428 .body("Trailing slash!".into())
429 .unwrap();
430 (state, response)
431 }
432 }
433
434 mod resource {
435 use super::*;
436 pub(crate) fn create(state: State) -> (State, Response<Body>) {
437 let response = Response::builder()
438 .status(StatusCode::CREATED)
439 .body(Body::empty())
440 .unwrap();
441 (state, response)
442 }
443
444 pub(crate) fn destroy(state: State) -> (State, Response<Body>) {
445 let response = Response::builder()
446 .status(StatusCode::ACCEPTED)
447 .body(Body::empty())
448 .unwrap();
449 (state, response)
450 }
451
452 pub(crate) fn show(state: State) -> (State, Response<Body>) {
453 let response = Response::builder()
454 .status(StatusCode::OK)
455 .body("It's a resource.".into())
456 .unwrap();
457 (state, response)
458 }
459
460 pub(crate) fn update(state: State) -> (State, Response<Body>) {
461 let response = Response::builder()
462 .status(StatusCode::ACCEPTED)
463 .body(Body::empty())
464 .unwrap();
465 (state, response)
466 }
467 }
468
469 mod api {
470 use super::*;
471 pub(crate) fn submit(state: State) -> (State, Response<Body>) {
472 (
473 state,
474 Response::builder()
475 .status(StatusCode::ACCEPTED)
476 .body(Body::empty())
477 .unwrap(),
478 )
479 }
480 }
481
482 #[test]
483 fn build_router_test() {
484 let pipelines = new_pipeline_set();
485 let (pipelines, default) = pipelines.add(new_pipeline().add(CookieParser).build());
486
487 let pipelines = finalize_pipeline_set(pipelines);
488
489 let default_pipeline_chain = (default, ());
490
491 let delegated_router = build_simple_router(|route| {
492 route.get("/b").to(welcome::delegated);
493 });
494
495 let router = build_router(default_pipeline_chain, pipelines, |route| {
496 route.get("/").to(welcome::index);
497
498 route
499 .get("/hello/:name")
500 .with_path_extractor::<SalutationParams>()
501 .to(welcome::hello);
502
503 route
504 .get("/hello/:name/*")
505 .with_path_extractor::<SalutationParams>()
506 .to(welcome::globbed);
507
508 route
509 .get("/goodbye/:name:[a-zA-Z]+")
510 .with_path_extractor::<SalutationParams>()
511 .to(welcome::goodbye);
512
513 route
514 .get("/add")
515 .with_query_string_extractor::<AddParams>()
516 .to(welcome::add);
517
518 route.get(r"/literal/\:param/\*").to(welcome::literal);
519
520 route.scope("/api", |route| {
521 route.post("/submit").to(api::submit);
522 });
523
524 route.associate("/resource", |route| {
525 route.post().to(resource::create);
526 route.patch().to(resource::update);
527 route.delete().to(resource::destroy);
528 route.get_or_head().to(resource::show);
529 });
530
531 route.delegate("/delegated").to_router(delegated_router);
532
533 route.get("/trailing-slash/").to(welcome::trailing_slash);
534 });
535
536 let new_service = GothamService::new(router);
537
538 let call = move |req| {
539 let mut service = new_service.connect("127.0.0.1:10000".parse().unwrap());
540 futures_executor::block_on(service.call(req)).unwrap()
541 };
542
543 let response = call(Request::get("/").body(Body::empty()).unwrap());
544 assert_eq!(response.status(), StatusCode::OK);
545
546 let response = call(Request::post("/api/submit").body(Body::empty()).unwrap());
547 assert_eq!(response.status(), StatusCode::ACCEPTED);
548
549 let response = call(Request::get("/hello/world").body(Body::empty()).unwrap());
550 assert_eq!(response.status(), StatusCode::OK);
551 let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
552 .unwrap()
553 .to_vec();
554 assert_eq!(&String::from_utf8(response_bytes).unwrap(), "Hello, world!");
555
556 let response = call(
557 Request::get("/hello/world/more/path/here/handled/by/glob")
558 .body(Body::empty())
559 .unwrap(),
560 );
561 assert_eq!(response.status(), StatusCode::OK);
562 let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
563 .unwrap()
564 .to_vec();
565 assert_eq!(&String::from_utf8(response_bytes).unwrap(), "Globbed");
566
567 let response = call(Request::get("/delegated/b").body(Body::empty()).unwrap());
568 assert_eq!(response.status(), StatusCode::OK);
569 let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
570 .unwrap()
571 .to_vec();
572 assert_eq!(&String::from_utf8(response_bytes).unwrap(), "Delegated");
573
574 let response = call(Request::get("/goodbye/world").body(Body::empty()).unwrap());
575 assert_eq!(response.status(), StatusCode::OK);
576 let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
577 .unwrap()
578 .to_vec();
579 assert_eq!(
580 &String::from_utf8(response_bytes).unwrap(),
581 "Goodbye, world!"
582 );
583
584 let response = call(Request::get("/goodbye/9875").body(Body::empty()).unwrap());
585 assert_eq!(response.status(), StatusCode::NOT_FOUND);
586
587 let response = call(
588 Request::get("/literal/:param/*")
589 .body(Body::empty())
590 .unwrap(),
591 );
592 assert_eq!(response.status(), StatusCode::CREATED);
593
594 let response = call(Request::get("/literal/a/b").body(Body::empty()).unwrap());
595 assert_eq!(response.status(), StatusCode::NOT_FOUND);
596
597 let response = call(Request::get("/add?x=16&y=71").body(Body::empty()).unwrap());
598 assert_eq!(response.status(), StatusCode::OK);
599 let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
600 .unwrap()
601 .to_vec();
602 assert_eq!(&String::from_utf8(response_bytes).unwrap(), "16 + 71 = 87");
603
604 let response = call(Request::post("/resource").body(Body::empty()).unwrap());
605 assert_eq!(response.status(), StatusCode::CREATED);
606
607 let response = call(Request::patch("/resource").body(Body::empty()).unwrap());
608 assert_eq!(response.status(), StatusCode::ACCEPTED);
609
610 let response = call(Request::delete("/resource").body(Body::empty()).unwrap());
611 assert_eq!(response.status(), StatusCode::ACCEPTED);
612
613 let response = call(Request::get("/resource").body(Body::empty()).unwrap());
614 assert_eq!(response.status(), StatusCode::OK);
615 let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
616 .unwrap()
617 .to_vec();
618 assert_eq!(&response_bytes[..], b"It's a resource.");
619
620 let response = call(
621 Request::get("/trailing-slash/")
622 .body(Body::empty())
623 .unwrap(),
624 );
625 assert_eq!(response.status(), StatusCode::OK);
626 let response = call(Request::get("/trailing-slash").body(Body::empty()).unwrap());
627 assert_eq!(response.status(), StatusCode::OK);
628 }
629}