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}