gotham/router/builder/
associated.rs

1use std::marker::PhantomData;
2use std::panic::RefUnwindSafe;
3
4use hyper::{Body, Method};
5
6use crate::extractor::{PathExtractor, QueryStringExtractor};
7use crate::pipeline::{PipelineHandleChain, PipelineSet};
8use crate::router::builder::SingleRouteBuilder;
9use crate::router::route::matcher::{
10    AndRouteMatcher, AnyRouteMatcher, MethodOnlyRouteMatcher, RouteMatcher,
11};
12use crate::router::tree::node::Node;
13
14pub(crate) type AssociatedRouteBuilderMatcher<M, NM> = AndRouteMatcher<M, NM>;
15pub(crate) type AssociatedRouteMatcher<M> = AndRouteMatcher<MethodOnlyRouteMatcher, M>;
16
17/// The default type returned when building a single associated route. See
18/// `router::builder::DefineSingleRoute` for an overview of the ways that a route can be specified.
19pub type AssociatedSingleRouteBuilder<'a, M, C, P, PE, QSE> =
20    SingleRouteBuilder<'a, M, C, P, PE, QSE>;
21
22/// Implements the methods required for associating a number of routes with a single path. This is
23/// used by `DrawRoutes::associated`.
24pub struct AssociatedRouteBuilder<'a, M, C, P, PE, QSE>
25where
26    M: RouteMatcher + Send + Sync + 'static,
27    C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
28    P: RefUnwindSafe + Send + Sync + 'static,
29    PE: PathExtractor<Body> + Send + Sync + 'static,
30    QSE: QueryStringExtractor<Body> + Send + Sync + 'static,
31{
32    node_builder: &'a mut Node,
33    matcher: M,
34    pipeline_chain: C,
35    pipelines: PipelineSet<P>,
36    phantom: PhantomData<(PE, QSE)>,
37}
38
39impl<'a, C, P, PE, QSE> AssociatedRouteBuilder<'a, AnyRouteMatcher, C, P, PE, QSE>
40where
41    C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
42    P: RefUnwindSafe + Send + Sync + 'static,
43    PE: PathExtractor<Body> + Send + Sync + 'static,
44    QSE: QueryStringExtractor<Body> + Send + Sync + 'static,
45{
46    /// Create an instance of AssociatedRouteBuilder
47    pub fn new(node_builder: &'a mut Node, pipeline_chain: C, pipelines: PipelineSet<P>) -> Self {
48        AssociatedRouteBuilder {
49            node_builder,
50            matcher: AnyRouteMatcher::new(),
51            pipeline_chain,
52            pipelines,
53            phantom: PhantomData,
54        }
55    }
56}
57
58impl<'a, M, C, P, PE, QSE> AssociatedRouteBuilder<'a, M, C, P, PE, QSE>
59where
60    M: RouteMatcher + Send + Sync + 'static,
61    C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
62    P: RefUnwindSafe + Send + Sync + 'static,
63    PE: PathExtractor<Body> + Send + Sync + 'static,
64    QSE: QueryStringExtractor<Body> + Send + Sync + 'static,
65{
66    /// Adds aadditional `RouteMatcher` requirements to all subsequently associated routes.
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// # use hyper::{Body, Response, StatusCode};
72    /// # use hyper::header::ACCEPT;
73    /// # use gotham::state::State;
74    /// # use gotham::router::route::matcher::AcceptHeaderRouteMatcher;
75    /// # use gotham::router::{build_simple_router, Router};
76    /// # use gotham::prelude::*;
77    /// # use gotham::test::TestServer;
78    /// #
79    /// # fn my_handler(state: State) -> (State, Response<Body>) {
80    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
81    /// # }
82    /// #
83    /// # fn router() -> Router {
84    /// build_simple_router(|route| {
85    ///     let matcher = AcceptHeaderRouteMatcher::new(vec![mime::APPLICATION_JSON]);
86    ///
87    ///     route.associate("/resource/path", |assoc| {
88    ///         let mut assoc = assoc.add_route_matcher(matcher);
89    ///
90    ///         assoc.get().to(my_handler);
91    ///     });
92    /// })
93    /// # }
94    /// #
95    /// # fn main() {
96    /// #   let test_server = TestServer::new(router()).unwrap();
97    /// #
98    /// #   let response = test_server.client()
99    /// #       .get("https://example.com/resource/path")
100    /// #       .with_header(ACCEPT, mime::APPLICATION_JSON.to_string().parse().unwrap())
101    /// #       .perform()
102    /// #       .unwrap();
103    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
104    /// #
105    /// #   let response = test_server.client()
106    /// #       .get("https://example.com/resource/path")
107    /// #       .with_header(ACCEPT, mime::TEXT_PLAIN.to_string().parse().unwrap())
108    /// #       .perform()
109    /// #       .unwrap();
110    /// #   assert_eq!(response.status(), StatusCode::NOT_ACCEPTABLE);
111    /// # }
112    /// ```
113    pub fn add_route_matcher<'b, NM>(
114        &'b mut self,
115        matcher: NM,
116    ) -> AssociatedRouteBuilder<'b, AssociatedRouteBuilderMatcher<M, NM>, C, P, PE, QSE>
117    where
118        NM: RouteMatcher + Send + Sync + 'static,
119    {
120        let matcher = AndRouteMatcher::new(self.matcher.clone(), matcher);
121        AssociatedRouteBuilder {
122            node_builder: self.node_builder,
123            matcher,
124            pipeline_chain: self.pipeline_chain,
125            pipelines: self.pipelines.clone(),
126            phantom: PhantomData,
127        }
128    }
129
130    /// Binds a new `PathExtractor` to the associated routes.
131    ///
132    /// # Examples
133    ///
134    /// ```rust
135    /// # use hyper::{Body, Response, StatusCode};
136    /// # use gotham::router::{build_simple_router, Router};
137    /// # use gotham::prelude::*;
138    /// # use gotham::state::State;
139    /// # use gotham::test::TestServer;
140    /// # use serde::Deserialize;
141    /// #
142    /// fn handler(state: State) -> (State, Response<Body>) {
143    ///     // Implementation elided.
144    /// #   assert_eq!(state.borrow::<MyPathExtractor>().id, 42);
145    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
146    /// }
147    ///
148    /// #[derive(Deserialize, StateData, StaticResponseExtender)]
149    /// struct MyPathExtractor {
150    /// #   #[allow(dead_code)]
151    ///     id: u32,
152    /// }
153    ///
154    /// #
155    /// # fn router() -> Router {
156    /// build_simple_router(|route| {
157    ///     route.associate("/resource/:id", |assoc| {
158    ///         let mut assoc = assoc.with_path_extractor::<MyPathExtractor>();
159    ///         assoc.get().to(handler);
160    ///     });
161    /// })
162    /// # }
163    /// #
164    /// # fn main() {
165    /// #   let test_server = TestServer::new(router()).unwrap();
166    /// #   let response = test_server.client()
167    /// #       .get("https://example.com/resource/42")
168    /// #       .perform()
169    /// #       .unwrap();
170    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
171    /// # }
172    /// ```
173    pub fn with_path_extractor<'b, NPE>(
174        &'b mut self,
175    ) -> AssociatedRouteBuilder<'b, M, C, P, NPE, QSE>
176    where
177        NPE: PathExtractor<Body> + Send + Sync + 'static,
178    {
179        AssociatedRouteBuilder {
180            node_builder: self.node_builder,
181            matcher: self.matcher.clone(),
182            pipeline_chain: self.pipeline_chain,
183            pipelines: self.pipelines.clone(),
184            phantom: PhantomData,
185        }
186    }
187
188    /// Binds a new `QueryStringExtractor` to the associated routes.
189    ///
190    /// # Examples
191    ///
192    /// ```rust
193    /// # use hyper::{Body, Response, StatusCode};
194    /// # use gotham::router::{build_simple_router, Router};
195    /// # use gotham::prelude::*;
196    /// # use gotham::state::State;
197    /// # use gotham::test::TestServer;
198    /// # use serde::Deserialize;
199    /// #
200    /// fn handler(state: State) -> (State, Response<Body>) {
201    ///     // Implementation elided.
202    /// #   assert_eq!(state.borrow::<MyQueryStringExtractor>().val.as_str(), "test_val");
203    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
204    /// }
205    ///
206    /// #[derive(StateData, Deserialize, StaticResponseExtender)]
207    /// struct MyQueryStringExtractor {
208    /// #   #[allow(dead_code)]
209    ///     val: String,
210    /// }
211    ///
212    /// #
213    /// # fn router() -> Router {
214    /// build_simple_router(|route| {
215    ///     route.associate("/resource", |assoc| {
216    ///         let mut assoc = assoc.with_query_string_extractor::<MyQueryStringExtractor>();
217    ///         assoc.get().to(handler);
218    ///     });
219    /// })
220    /// # }
221    /// #
222    /// # fn main() {
223    /// #   let test_server = TestServer::new(router()).unwrap();
224    /// #   let response = test_server.client()
225    /// #       .get("https://example.com/resource?val=test_val")
226    /// #       .perform()
227    /// #       .unwrap();
228    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
229    /// # }
230    /// ```
231    pub fn with_query_string_extractor<'b, NQSE>(
232        &'b mut self,
233    ) -> AssociatedRouteBuilder<'b, M, C, P, PE, NQSE>
234    where
235        NQSE: QueryStringExtractor<Body> + Send + Sync + 'static,
236    {
237        AssociatedRouteBuilder {
238            node_builder: self.node_builder,
239            matcher: self.matcher.clone(),
240            pipeline_chain: self.pipeline_chain,
241            pipelines: self.pipelines.clone(),
242            phantom: PhantomData,
243        }
244    }
245
246    /// Associates a route which matches requests with any of the specified methods, to the current
247    /// path.
248    ///
249    /// # Examples
250    ///
251    /// ```rust
252    /// # extern crate gotham;
253    /// # extern crate hyper;
254    /// # extern crate mime;
255    /// #
256    /// # use hyper::{Body, Response, Method, StatusCode};
257    /// # use gotham::router::Router;
258    /// # use gotham::router::builder::*;
259    /// # use gotham::state::State;
260    /// # use gotham::test::TestServer;
261    /// #
262    /// fn handler(state: State) -> (State, Response<Body>) {
263    ///     // Implementation elided.
264    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
265    /// }
266    ///
267    /// #
268    /// # fn router() -> Router {
269    /// build_simple_router(|route| {
270    ///     route.associate("/resource", |assoc| {
271    ///         assoc.request(vec![Method::GET, Method::HEAD, Method::POST]).to(handler);
272    ///     });
273    /// })
274    /// # }
275    /// #
276    /// # fn main() {
277    /// #   let test_server = TestServer::new(router()).unwrap();
278    /// #
279    /// #   let response = test_server.client()
280    /// #       .get("https://example.com/resource")
281    /// #       .perform()
282    /// #       .unwrap();
283    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
284    /// #
285    /// #   let response = test_server.client()
286    /// #       .head("https://example.com/resource")
287    /// #       .perform()
288    /// #       .unwrap();
289    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
290    /// #
291    /// #   let response = test_server.client()
292    /// #       .post("https://example.com/resource", b"".to_vec(), mime::TEXT_PLAIN)
293    /// #       .perform()
294    /// #       .unwrap();
295    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
296    /// # }
297    /// ```
298    pub fn request<'b>(
299        &'b mut self,
300        methods: Vec<Method>,
301    ) -> AssociatedSingleRouteBuilder<'b, AssociatedRouteMatcher<M>, C, P, PE, QSE> {
302        let AssociatedRouteBuilder {
303            ref mut node_builder,
304            ref matcher,
305            ref pipeline_chain,
306            ref pipelines,
307            phantom,
308        } = *self;
309
310        SingleRouteBuilder {
311            node_builder,
312            matcher: AndRouteMatcher::new(MethodOnlyRouteMatcher::new(methods), matcher.clone()),
313            pipeline_chain: *pipeline_chain,
314            pipelines: pipelines.clone(),
315            phantom,
316        }
317    }
318
319    /// Associates a route which matches `HEAD` requests to the current path.
320    ///
321    /// # Examples
322    ///
323    /// ```rust
324    /// # extern crate gotham;
325    /// # extern crate hyper;
326    /// #
327    /// # use hyper::{Body, Response, StatusCode};
328    /// # use gotham::router::Router;
329    /// # use gotham::router::builder::*;
330    /// # use gotham::state::State;
331    /// # use gotham::test::TestServer;
332    /// #
333    /// fn handler(state: State) -> (State, Response<Body>) {
334    ///     // Implementation elided.
335    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
336    /// }
337    ///
338    /// #
339    /// # fn router() -> Router {
340    /// build_simple_router(|route| {
341    ///     route.associate("/resource", |assoc| {
342    ///         assoc.head().to(handler);
343    ///     });
344    /// })
345    /// # }
346    /// #
347    /// # fn main() {
348    /// #   let test_server = TestServer::new(router()).unwrap();
349    /// #   let response = test_server.client()
350    /// #       .head("https://example.com/resource")
351    /// #       .perform()
352    /// #       .unwrap();
353    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
354    /// # }
355    /// ```
356    pub fn head<'b>(
357        &'b mut self,
358    ) -> AssociatedSingleRouteBuilder<'b, AssociatedRouteMatcher<M>, C, P, PE, QSE> {
359        self.request(vec![Method::HEAD])
360    }
361
362    /// Associates a route which matches `GET` or `HEAD` requests to the current path.
363    ///
364    /// # Examples
365    ///
366    /// ```rust
367    /// # extern crate gotham;
368    /// # extern crate hyper;
369    /// #
370    /// # use hyper::{Body, Response, StatusCode};
371    /// # use gotham::router::Router;
372    /// # use gotham::router::builder::*;
373    /// # use gotham::state::State;
374    /// # use gotham::test::TestServer;
375    /// #
376    /// fn handler(state: State) -> (State, Response<Body>) {
377    ///     // Implementation elided.
378    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
379    /// }
380    ///
381    /// #
382    /// # fn router() -> Router {
383    /// build_simple_router(|route| {
384    ///     route.associate("/resource", |assoc| {
385    ///         assoc.get_or_head().to(handler);
386    ///     });
387    /// })
388    /// # }
389    /// #
390    /// # fn main() {
391    /// #   let test_server = TestServer::new(router()).unwrap();
392    /// #
393    /// #   let response = test_server.client()
394    /// #       .get("https://example.com/resource")
395    /// #       .perform()
396    /// #       .unwrap();
397    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
398    /// #
399    /// #   let response = test_server.client()
400    /// #       .head("https://example.com/resource")
401    /// #       .perform()
402    /// #       .unwrap();
403    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
404    /// # }
405    /// ```
406    pub fn get_or_head<'b>(
407        &'b mut self,
408    ) -> AssociatedSingleRouteBuilder<'b, AssociatedRouteMatcher<M>, C, P, PE, QSE> {
409        self.request(vec![Method::GET, Method::HEAD])
410    }
411
412    /// Associates a route which matches `GET` requests to the current path.
413    ///
414    /// # Examples
415    ///
416    /// ```rust
417    /// # extern crate gotham;
418    /// # extern crate hyper;
419    /// #
420    /// # use hyper::{Body, Response, StatusCode};
421    /// # use gotham::router::Router;
422    /// # use gotham::router::builder::*;
423    /// # use gotham::state::State;
424    /// # use gotham::test::TestServer;
425    /// #
426    /// fn handler(state: State) -> (State, Response<Body>) {
427    ///     // Implementation elided.
428    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
429    /// }
430    ///
431    /// #
432    /// # fn router() -> Router {
433    /// build_simple_router(|route| {
434    ///     route.associate("/resource", |assoc| {
435    ///         assoc.get().to(handler);
436    ///     });
437    /// })
438    /// # }
439    /// #
440    /// # fn main() {
441    /// #   let test_server = TestServer::new(router()).unwrap();
442    /// #   let response = test_server.client()
443    /// #       .get("https://example.com/resource")
444    /// #       .perform()
445    /// #       .unwrap();
446    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
447    /// # }
448    /// ```
449    pub fn get<'b>(
450        &'b mut self,
451    ) -> AssociatedSingleRouteBuilder<'b, AssociatedRouteMatcher<M>, C, P, PE, QSE> {
452        self.request(vec![Method::GET])
453    }
454
455    /// Associates a route which matches `POST` requests to the current path.
456    ///
457    /// # Examples
458    ///
459    /// ```rust
460    /// # extern crate gotham;
461    /// # extern crate hyper;
462    /// # extern crate mime;
463    /// #
464    /// # use hyper::{Body, Response, StatusCode};
465    /// # use gotham::router::Router;
466    /// # use gotham::router::builder::*;
467    /// # use gotham::state::State;
468    /// # use gotham::test::TestServer;
469    /// #
470    /// fn handler(state: State) -> (State, Response<Body>) {
471    ///     // Implementation elided.
472    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
473    /// }
474    ///
475    /// #
476    /// # fn router() -> Router {
477    /// build_simple_router(|route| {
478    ///     route.associate("/resource", |assoc| {
479    ///         assoc.post().to(handler);
480    ///     });
481    /// })
482    /// # }
483    /// #
484    /// # fn main() {
485    /// #   let test_server = TestServer::new(router()).unwrap();
486    /// #   let response = test_server.client()
487    /// #       .post("https://example.com/resource", b"".to_vec(), mime::TEXT_PLAIN)
488    /// #       .perform()
489    /// #       .unwrap();
490    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
491    /// # }
492    /// ```
493    pub fn post<'b>(
494        &'b mut self,
495    ) -> AssociatedSingleRouteBuilder<'b, AssociatedRouteMatcher<M>, C, P, PE, QSE> {
496        self.request(vec![Method::POST])
497    }
498
499    /// Associates a route which matches `PUT` requests to the current path.
500    ///
501    /// # Examples
502    ///
503    /// ```rust
504    /// # extern crate gotham;
505    /// # extern crate hyper;
506    /// # extern crate mime;
507    /// #
508    /// # use hyper::{Body, Response, StatusCode};
509    /// # use gotham::router::Router;
510    /// # use gotham::router::builder::*;
511    /// # use gotham::state::State;
512    /// # use gotham::test::TestServer;
513    /// #
514    /// fn handler(state: State) -> (State, Response<Body>) {
515    ///     // Implementation elided.
516    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
517    /// }
518    ///
519    /// #
520    /// # fn router() -> Router {
521    /// build_simple_router(|route| {
522    ///     route.associate("/resource", |assoc| {
523    ///         assoc.put().to(handler);
524    ///     });
525    /// })
526    /// # }
527    /// #
528    /// # fn main() {
529    /// #   let test_server = TestServer::new(router()).unwrap();
530    /// #   let response = test_server.client()
531    /// #       .put("https://example.com/resource", b"".to_vec(), mime::TEXT_PLAIN)
532    /// #       .perform()
533    /// #       .unwrap();
534    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
535    /// # }
536    /// ```
537    pub fn put<'b>(
538        &'b mut self,
539    ) -> AssociatedSingleRouteBuilder<'b, AssociatedRouteMatcher<M>, C, P, PE, QSE> {
540        self.request(vec![Method::PUT])
541    }
542
543    /// Associates a route which matches `PATCH` requests to the current path.
544    ///
545    /// # Examples
546    ///
547    /// ```rust
548    /// # extern crate gotham;
549    /// # extern crate hyper;
550    /// # extern crate mime;
551    /// #
552    /// # use hyper::{Body, Response, StatusCode};
553    /// # use gotham::router::Router;
554    /// # use gotham::router::builder::*;
555    /// # use gotham::state::State;
556    /// # use gotham::test::TestServer;
557    /// #
558    /// fn handler(state: State) -> (State, Response<Body>) {
559    ///     // Implementation elided.
560    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
561    /// }
562    ///
563    /// #
564    /// # fn router() -> Router {
565    /// build_simple_router(|route| {
566    ///     route.associate("/resource", |assoc| {
567    ///         assoc.patch().to(handler);
568    ///     });
569    /// })
570    /// # }
571    /// #
572    /// # fn main() {
573    /// #   let test_server = TestServer::new(router()).unwrap();
574    /// #   let response = test_server.client()
575    /// #       .patch("https://example.com/resource", b"".to_vec(), mime::TEXT_PLAIN)
576    /// #       .perform()
577    /// #       .unwrap();
578    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
579    /// # }
580    /// ```
581    pub fn patch<'b>(
582        &'b mut self,
583    ) -> AssociatedSingleRouteBuilder<'b, AssociatedRouteMatcher<M>, C, P, PE, QSE> {
584        self.request(vec![Method::PATCH])
585    }
586
587    /// Associates a route which matches `DELETE` requests to the current path.
588    ///
589    /// # Examples
590    ///
591    /// ```rust
592    /// # extern crate gotham;
593    /// # extern crate hyper;
594    /// #
595    /// # use hyper::{Body, Response, StatusCode};
596    /// # use gotham::router::Router;
597    /// # use gotham::router::builder::*;
598    /// # use gotham::state::State;
599    /// # use gotham::test::TestServer;
600    /// #
601    /// fn handler(state: State) -> (State, Response<Body>) {
602    ///     // Implementation elided.
603    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
604    /// }
605    ///
606    /// #
607    /// # fn router() -> Router {
608    /// build_simple_router(|route| {
609    ///     route.associate("/resource", |assoc| {
610    ///         assoc.delete().to(handler);
611    ///     });
612    /// })
613    /// # }
614    /// #
615    /// # fn main() {
616    /// #   let test_server = TestServer::new(router()).unwrap();
617    /// #   let response = test_server.client()
618    /// #       .delete("https://example.com/resource")
619    /// #       .perform()
620    /// #       .unwrap();
621    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
622    /// # }
623    /// ```
624    pub fn delete<'b>(
625        &'b mut self,
626    ) -> AssociatedSingleRouteBuilder<'b, AssociatedRouteMatcher<M>, C, P, PE, QSE> {
627        self.request(vec![Method::DELETE])
628    }
629
630    /// Associates a route which matches `OPTIONS` requests to the current path.
631    ///
632    /// # Examples
633    ///
634    /// ```rust
635    /// # extern crate gotham;
636    /// # extern crate hyper;
637    /// #
638    /// # use hyper::{Body, Response, StatusCode};
639    /// # use gotham::router::Router;
640    /// # use gotham::router::builder::*;
641    /// # use gotham::state::State;
642    /// # use gotham::test::TestServer;
643    /// #
644    /// fn handler(state: State) -> (State, Response<Body>) {
645    ///     // Implementation elided.
646    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
647    /// }
648    ///
649    /// #
650    /// # fn router() -> Router {
651    /// build_simple_router(|route| {
652    ///     route.associate("/resource", |assoc| {
653    ///         assoc.options().to(handler);
654    ///     });
655    /// })
656    /// # }
657    /// #
658    /// # fn main() {
659    /// #   let test_server = TestServer::new(router()).unwrap();
660    /// #   let response = test_server.client()
661    /// #       .options("https://example.com/resource")
662    /// #       .perform()
663    /// #       .unwrap();
664    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
665    /// # }
666    /// ```
667    pub fn options<'b>(
668        &'b mut self,
669    ) -> AssociatedSingleRouteBuilder<'b, AssociatedRouteMatcher<M>, C, P, PE, QSE> {
670        self.request(vec![Method::OPTIONS])
671    }
672}