tide_fluent_routes/
lib.rs

1//! Tide Fluent Routes is a fluent api to define routes for the Tide HTTP framework.
2//! At the moment it supports setting up paths, you can integrate middleware at any place in the
3//! route-tree and you can integrate endpoints.
4//! Some things that are possible with Tide-native routes are not (yet) possible;
5//! - Tide prefix routes are not implemented
6//! - you can not nest Tide servers
7//!
8//! To use this you can import Tide Fluent Routes with `use tide_fluent_routes::prelude::* it
9//! introduces the `register` extension method on the `Tide::Server to register routes from a
10//! RouteBuilder.
11//! A RouteBuilder can be initialized using the `route()` method.
12//! You can register simple endpoints like this;
13//! ```rust
14//! # use tide::{Request, Result};
15//! # pub async fn endpoint(_: Request<()>) -> Result {
16//! #     todo!()
17//! # }
18//! use tide_fluent_routes::prelude::*;
19//!
20//! let mut server = tide::Server::new();
21//!
22//! server.register(
23//!    root()
24//!        .get(endpoint)
25//!        .post(endpoint));
26//! ```
27//! Fluent Routes follows conventions from Tide. All HTTP verbs are supported the same way. Paths
28//! can be extended using `at` but this method takes a router closure that allows building the route
29//! as a tree.
30//! A complete route tree can be defined like this;
31//! ```rust
32//! # use tide::{Request, Result};
33//! # use tide_fluent_routes::prelude::*;
34//! # async fn endpoint(_: Request<()>) -> Result {
35//! #     todo!()
36//! # }
37//! # let mut server = tide::Server::new();
38//!
39//! server.register(
40//!     root()
41//!         .get(endpoint)
42//!         .post(endpoint)
43//!         .at("api/v1", |route| route
44//!             .get(endpoint)
45//!             .post(endpoint)
46//!         )
47//!         .at("api/v2", |route| route
48//!             .get(endpoint)
49//!             .post(endpoint)
50//!         )
51//! );
52//! ```
53//! This eliminates the need to introduce variables for partial pieces of your route tree.
54//!
55//! Including routes defined in other functions also looks very natural, this makes it easy
56//! to compose large route trees from smaller trees defined elsewhere;
57//! ```rust
58//! # use tide::{Request, Result};
59//! # use tide_fluent_routes::prelude::*;
60//! # async fn endpoint(_: Request<()>) -> Result {
61//! #     todo!()
62//! # }
63//! # let mut server = tide::Server::new();
64//!
65//! fn v1_routes(routes: RouteSegment<()>) -> RouteSegment<()> {
66//!     routes
67//!         .at("articles", |route| route
68//!             .get(endpoint)
69//!             .post(endpoint)
70//!             .at(":id", |route| route
71//!                 .get(endpoint)
72//!                 .put(endpoint)
73//!                 .delete(endpoint)
74//!             )
75//!         )
76//! }
77//!
78//! fn v2_routes(routes: RouteSegment<()>) -> RouteSegment<()> {
79//!     routes
80//!         .at("articles", |route| route
81//!             .get(endpoint))
82//! }
83//!
84//! server.register(
85//!     root()
86//!         .get(endpoint)
87//!         .post(endpoint)
88//!         .at("api/v1", v1_routes)
89//!         .at("api/v2", v2_routes));
90//! ```
91//!
92//! With vanilla Tide routes it can be hard to see what middleware is active for what
93//! endpoints.
94//! Adding middleware to a tree is easy, and its very clear where the middleware is applied;
95//! ```rust
96//! # use std::{future::Future, pin::Pin};
97//! # use tide::{Next, Request, Result};
98//! # use tide_fluent_routes::prelude::*;
99//! # async fn endpoint(_: Request<()>) -> Result {
100//! #     todo!()
101//! # }
102//! # fn dummy_middleware<'a>(
103//! #     request: Request<()>,
104//! #     next: Next<'a, ()>,
105//! # ) -> Pin<Box<dyn Future<Output = Result> + Send + 'a>> {
106//! #     Box::pin(async { Ok(next.run(request).await) })
107//! # }
108//! # let mut server = tide::Server::new();
109//! server.register(
110//!     root()
111//!         .get(endpoint)
112//!         .post(endpoint)
113//!         .at("api/v1", |route| route
114//!             .with(dummy_middleware, |route| route
115//!                 .get(endpoint)
116//!             )
117//!             .post(endpoint)
118//!         )
119//!         .at("api/v2", |route| route
120//!             .get(endpoint)
121//!             .get(endpoint)
122//!         ),
123//! );
124//! ```
125//!
126//! Serving directories is possible using `serve_dir`, this works the same as with normal Tide routes,
127//! fluent routes adds the `serve_file` convenience method for serving single files.
128//! ```rust,no_run
129//! # use tide::{Request, Result};
130//! use tide_fluent_routes::prelude::*;
131//! use tide_fluent_routes::fs::ServeFs;
132//!
133//! let mut server = tide::Server::new();
134//!
135//! server.register(
136//!     root()
137//!         .serve_file("files/index.html").unwrap()
138//!         .at("img", |r| r
139//!             .serve_dir("files/images").unwrap()
140//!         )
141//! );
142//! ```
143
144// Turn on warnings for some lints
145#![warn(
146    missing_debug_implementations,
147    missing_docs,
148    trivial_casts,
149    trivial_numeric_casts,
150    unreachable_pub,
151    unused_import_braces,
152    unused_qualifications
153)]
154
155pub mod fs;
156mod path;
157pub mod reverse_router;
158pub mod routebuilder;
159pub mod router;
160mod util;
161
162use crate::path::Path;
163use crate::util::{ArcMiddleware, BoxedEndpoint};
164use reverse_router::ReverseRouter;
165use routebuilder::RouteBuilder;
166use std::collections::HashMap;
167use tide::http::Method;
168use tide::{Endpoint, Middleware};
169
170/// Start building a route. Returns a RouteBuilder for the root of your route
171pub fn root<State>() -> RouteSegment<State> {
172    RouteSegment {
173        path: Path::prefix("/"),
174        middleware: Vec::new(),
175        name: None,
176        branches: Vec::new(),
177        endpoints: HashMap::new(),
178    }
179}
180
181/// A Builder for Tide routes. RouteBuilders can be composed into a tree that represents the tree of
182/// path segments, middleware and endpoints that defines the routes in a Tide application. This tree
183/// can then be returned as a list of routes to each of the endpoints.
184#[derive(Debug)]
185pub struct RouteSegment<State> {
186    path: Path,
187    middleware: Vec<ArcMiddleware<State>>,
188
189    name: Option<String>,
190    branches: Vec<RouteSegment<State>>,
191    endpoints: HashMap<Option<Method>, BoxedEndpoint<State>>,
192}
193
194impl<State: Clone + Send + Sync + 'static> RouteSegment<State> {
195    fn names(&self) -> Vec<RouteDescriptor<State>> {
196        let path = self.path.clone();
197
198        let local_name = self
199            .name
200            .clone()
201            .map(|name| RouteDescriptor {
202                path: path.clone(),
203                middleware: Vec::new(), // We don't care about middleware for route names
204                route: Route::Name(name),
205            })
206            .into_iter();
207
208        let sub_routes = self.branches.iter().flat_map(RouteSegment::names);
209
210        local_name.chain(sub_routes).collect()
211    }
212
213    /// Construct a reverse router for the paths in the route builder
214    pub fn reverse_router(&self) -> ReverseRouter {
215        let mut routes = ReverseRouter::new();
216
217        for RouteDescriptor {
218            path,
219            middleware: _,
220            route,
221        } in self.names()
222        {
223            if let Route::Name(name) = route {
224                routes.insert(&name, &path.to_string());
225            }
226        }
227
228        routes
229    }
230
231    fn build(self) -> Vec<RouteDescriptor<State>> {
232        let path = self.path;
233        let middleware = self.middleware;
234
235        let local_endpoints =
236            self.endpoints
237                .into_iter()
238                .map(|(method, endpoint)| RouteDescriptor {
239                    path: path.clone(),
240                    middleware: middleware.clone(),
241                    route: Route::Handler(method, endpoint),
242                });
243
244        let sub_endpoints = self.branches.into_iter().flat_map(RouteSegment::build);
245
246        local_endpoints.chain(sub_endpoints).collect()
247    }
248}
249
250impl<State: Clone + Send + Sync + 'static> RouteBuilder<State> for RouteSegment<State> {
251    fn at<R: FnOnce(Self) -> Self>(mut self, path: &str, routes: R) -> Self {
252        self.branches.push(routes(RouteSegment {
253            path: self.path.clone().append(path),
254            middleware: self.middleware.clone(),
255            name: None,
256            branches: Vec::new(),
257            endpoints: HashMap::new(),
258        }));
259        self
260    }
261
262    fn with<M: Middleware<State>, R: FnOnce(Self) -> Self>(
263        mut self,
264        middleware: M,
265        routes: R,
266    ) -> Self {
267        let mut ware = self.middleware.clone();
268        ware.push(ArcMiddleware::new(middleware));
269
270        self.branches.push(routes(RouteSegment {
271            path: self.path.clone(),
272            middleware: ware,
273            name: None,
274            branches: Vec::new(),
275            endpoints: HashMap::new(),
276        }));
277        self
278    }
279
280    fn method(mut self, method: Method, endpoint: impl Endpoint<State>) -> Self {
281        self.endpoints
282            .insert(Some(method), BoxedEndpoint::new(endpoint));
283        self
284    }
285
286    fn all(mut self, endpoint: impl Endpoint<State>) -> Self {
287        self.endpoints.insert(None, BoxedEndpoint::new(endpoint));
288        self
289    }
290
291    fn name(mut self, name: &str) -> Self {
292        if let Some(name) = self.name {
293            panic!("route already has name: {}", name);
294        }
295        self.name = Some(name.to_string());
296        self
297    }
298}
299
300/// Describes a branch in the route tree, the path and middleware collected and the route as the leaf
301#[derive(Debug)]
302pub(crate) struct RouteDescriptor<State> {
303    path: Path,
304    middleware: Vec<ArcMiddleware<State>>,
305    route: Route<State>,
306}
307
308/// Descibes a leaf in the route tree, either a name or a handler
309#[derive(Debug)]
310pub(crate) enum Route<State> {
311    Name(String),
312    Handler(Option<Method>, BoxedEndpoint<State>),
313}
314
315/// Import types to use tide_fluent_routes
316pub mod prelude {
317    pub use super::reverse_router::ReverseRouter;
318    pub use super::routebuilder::{RouteBuilder, RouteBuilderExt};
319    pub use super::router::Router;
320    pub use super::{root, RouteSegment};
321    pub use tide::http::Method;
322}
323
324#[cfg(test)]
325mod test {
326    use super::prelude::*;
327    use super::ArcMiddleware;
328    use std::future::Future;
329    use std::pin::Pin;
330    use tide::{Next, Request, Result};
331
332    #[test]
333    fn should_build_single_endpoint() {
334        let routes: Vec<_> = root::<()>().get(|_| async { Ok("") }).build();
335
336        assert_eq!(routes.len(), 1);
337    }
338
339    #[test]
340    fn should_build_multiple_endpoints() {
341        let routes: Vec<_> = root::<()>()
342            .get(|_| async { Ok("") })
343            .post(|_| async { Ok("") })
344            .build();
345
346        assert_eq!(routes.len(), 2);
347    }
348
349    #[test]
350    fn should_build_sub_endpoints() {
351        let routes: Vec<_> = root::<()>()
352            .at("sub_path", |r| {
353                r.get(|_| async { Ok("") }).post(|_| async { Ok("") })
354            })
355            .build();
356
357        assert_eq!(routes.len(), 2);
358    }
359
360    #[test]
361    fn should_build_endpoint_path() {
362        let routes: Vec<_> = root::<()>()
363            .at("path", |r| r.at("subpath", |r| r.get(|_| async { Ok("") })))
364            .build();
365
366        assert_eq!(routes.len(), 1);
367        // TODO: Fix this, possibly with a named endpoint
368        // assert_eq!(routes.get(0).unwrap().route, Some(Method::Get));
369        assert_eq!(
370            routes.get(0).unwrap().path.to_string(),
371            "/path/subpath".to_string()
372        );
373    }
374
375    #[test]
376    fn should_start_path_with_slash() {
377        let routes: Vec<_> = root::<()>().get(|_| async { Ok("") }).build();
378        assert_eq!(routes.get(0).unwrap().path.to_string(), "/".to_string());
379    }
380
381    fn middleware<'a>(
382        request: Request<()>,
383        next: Next<'a, ()>,
384    ) -> Pin<Box<dyn Future<Output = Result> + Send + 'a>> {
385        Box::pin(async { Ok(next.run(request).await) })
386    }
387
388    #[test]
389    fn should_collect_middleware() {
390        let middleware1 = ArcMiddleware::new(middleware);
391        let middleware2 = ArcMiddleware::new(middleware);
392
393        let routes: Vec<_> = root::<()>()
394            .at("path", |r| {
395                r.with(middleware1.clone(), |r| {
396                    r.at("subpath", |r| {
397                        r.with(middleware2.clone(), |r| r.get(|_| async { Ok("") }))
398                    })
399                    .get(|_| async { Ok("") })
400                })
401            })
402            .build();
403
404        assert_eq!(routes.get(0).unwrap().middleware.len(), 1);
405        assert_eq!(routes.get(1).unwrap().middleware.len(), 2);
406    }
407}