[go: up one dir, main page]

tide/
route.rs

1use std::fmt::Debug;
2use std::io;
3use std::path::Path;
4use std::sync::Arc;
5
6use crate::endpoint::MiddlewareEndpoint;
7use crate::fs::{ServeDir, ServeFile};
8use crate::log;
9use crate::{router::Router, Endpoint, Middleware};
10
11/// A handle to a route.
12///
13/// All HTTP requests are made against resources. After using [`Server::at`] (or
14/// [`Route::at`]) to establish a route, the `Route` type can be used to
15/// establish endpoints for various HTTP methods at that path. Also, using
16/// `nest`, it can be used to set up a subrouter.
17///
18/// [`Server::at`]: ./struct.Server.html#method.at
19#[allow(missing_debug_implementations)]
20pub struct Route<'a, State> {
21    router: &'a mut Router<State>,
22    path: String,
23    middleware: Vec<Arc<dyn Middleware<State>>>,
24    /// Indicates whether the path of current route is treated as a prefix. Set by
25    /// [`strip_prefix`].
26    ///
27    /// [`strip_prefix`]: #method.strip_prefix
28    prefix: bool,
29}
30
31impl<'a, State: Clone + Send + Sync + 'static> Route<'a, State> {
32    pub(crate) fn new(router: &'a mut Router<State>, path: String) -> Route<'a, State> {
33        Route {
34            router,
35            path,
36            middleware: Vec::new(),
37            prefix: false,
38        }
39    }
40
41    /// Extend the route with the given `path`.
42    pub fn at<'b>(&'b mut self, path: &str) -> Route<'b, State> {
43        let mut p = self.path.clone();
44
45        if !p.ends_with('/') && !path.starts_with('/') {
46            p.push('/');
47        }
48
49        if path != "/" {
50            p.push_str(path);
51        }
52
53        Route {
54            router: &mut self.router,
55            path: p,
56            middleware: self.middleware.clone(),
57            prefix: false,
58        }
59    }
60
61    /// Get the current path.
62    #[must_use]
63    pub fn path(&self) -> &str {
64        &self.path
65    }
66
67    /// Treat the current path as a prefix, and strip prefixes from requests.
68    ///
69    /// This method is marked unstable as its name might change in the near future.
70    ///
71    /// Endpoints will be given a path with the prefix removed.
72    #[cfg(any(feature = "unstable", feature = "docs"))]
73    #[cfg_attr(feature = "docs", doc(cfg(unstable)))]
74    pub fn strip_prefix(&mut self) -> &mut Self {
75        self.prefix = true;
76        self
77    }
78
79    /// Apply the given middleware to the current route.
80    pub fn with<M>(&mut self, middleware: M) -> &mut Self
81    where
82        M: Middleware<State>,
83    {
84        log::trace!(
85            "Adding middleware {} to route {:?}",
86            middleware.name(),
87            self.path
88        );
89        self.middleware.push(Arc::new(middleware));
90        self
91    }
92
93    /// Reset the middleware chain for the current route, if any.
94    pub fn reset_middleware(&mut self) -> &mut Self {
95        self.middleware.clear();
96        self
97    }
98
99    /// Nest a [`Server`] at the current path.
100    ///
101    /// [`Server`]: struct.Server.html
102    pub fn nest<InnerState>(&mut self, service: crate::Server<InnerState>) -> &mut Self
103    where
104        State: Clone + Send + Sync + 'static,
105        InnerState: Clone + Send + Sync + 'static,
106    {
107        let prefix = self.prefix;
108
109        self.prefix = true;
110        self.all(service);
111        self.prefix = prefix;
112
113        self
114    }
115
116    /// Serve a directory statically.
117    ///
118    /// Each file will be streamed from disk, and a mime type will be determined
119    /// based on magic bytes.
120    ///
121    /// # Examples
122    ///
123    /// Serve the contents of the local directory `./public/images/*` from
124    /// `localhost:8080/images/*`.
125    ///
126    /// ```no_run
127    /// #[async_std::main]
128    /// async fn main() -> Result<(), std::io::Error> {
129    ///     let mut app = tide::new();
130    ///     app.at("/images").serve_dir("public/images/")?;
131    ///     app.listen("127.0.0.1:8080").await?;
132    ///     Ok(())
133    /// }
134    /// ```
135    pub fn serve_dir(&mut self, dir: impl AsRef<Path>) -> io::Result<()> {
136        // Verify path exists, return error if it doesn't.
137        let dir = dir.as_ref().to_owned().canonicalize()?;
138        let prefix = self.path().to_string();
139        self.at("*").get(ServeDir::new(prefix, dir));
140        Ok(())
141    }
142
143    /// Serve a static file.
144    ///
145    /// The file will be streamed from disk, and a mime type will be determined
146    /// based on magic bytes. Similar to serve_dir
147    pub fn serve_file(&mut self, file: impl AsRef<Path>) -> io::Result<()> {
148        self.get(ServeFile::init(file)?);
149        Ok(())
150    }
151
152    /// Add an endpoint for the given HTTP method
153    pub fn method(&mut self, method: http_types::Method, ep: impl Endpoint<State>) -> &mut Self {
154        if self.prefix {
155            let ep = StripPrefixEndpoint::new(ep);
156
157            self.router.add(
158                &self.path,
159                method,
160                MiddlewareEndpoint::wrap_with_middleware(ep.clone(), &self.middleware),
161            );
162            let wildcard = self.at("*--tide-path-rest");
163            wildcard.router.add(
164                &wildcard.path,
165                method,
166                MiddlewareEndpoint::wrap_with_middleware(ep, &wildcard.middleware),
167            );
168        } else {
169            self.router.add(
170                &self.path,
171                method,
172                MiddlewareEndpoint::wrap_with_middleware(ep, &self.middleware),
173            );
174        }
175        self
176    }
177
178    /// Add an endpoint for all HTTP methods, as a fallback.
179    ///
180    /// Routes with specific HTTP methods will be tried first.
181    pub fn all(&mut self, ep: impl Endpoint<State>) -> &mut Self {
182        if self.prefix {
183            let ep = StripPrefixEndpoint::new(ep);
184
185            self.router.add_all(
186                &self.path,
187                MiddlewareEndpoint::wrap_with_middleware(ep.clone(), &self.middleware),
188            );
189            let wildcard = self.at("*--tide-path-rest");
190            wildcard.router.add_all(
191                &wildcard.path,
192                MiddlewareEndpoint::wrap_with_middleware(ep, &wildcard.middleware),
193            );
194        } else {
195            self.router.add_all(
196                &self.path,
197                MiddlewareEndpoint::wrap_with_middleware(ep, &self.middleware),
198            );
199        }
200        self
201    }
202
203    /// Add an endpoint for `GET` requests
204    pub fn get(&mut self, ep: impl Endpoint<State>) -> &mut Self {
205        self.method(http_types::Method::Get, ep);
206        self
207    }
208
209    /// Add an endpoint for `HEAD` requests
210    pub fn head(&mut self, ep: impl Endpoint<State>) -> &mut Self {
211        self.method(http_types::Method::Head, ep);
212        self
213    }
214
215    /// Add an endpoint for `PUT` requests
216    pub fn put(&mut self, ep: impl Endpoint<State>) -> &mut Self {
217        self.method(http_types::Method::Put, ep);
218        self
219    }
220
221    /// Add an endpoint for `POST` requests
222    pub fn post(&mut self, ep: impl Endpoint<State>) -> &mut Self {
223        self.method(http_types::Method::Post, ep);
224        self
225    }
226
227    /// Add an endpoint for `DELETE` requests
228    pub fn delete(&mut self, ep: impl Endpoint<State>) -> &mut Self {
229        self.method(http_types::Method::Delete, ep);
230        self
231    }
232
233    /// Add an endpoint for `OPTIONS` requests
234    pub fn options(&mut self, ep: impl Endpoint<State>) -> &mut Self {
235        self.method(http_types::Method::Options, ep);
236        self
237    }
238
239    /// Add an endpoint for `CONNECT` requests
240    pub fn connect(&mut self, ep: impl Endpoint<State>) -> &mut Self {
241        self.method(http_types::Method::Connect, ep);
242        self
243    }
244
245    /// Add an endpoint for `PATCH` requests
246    pub fn patch(&mut self, ep: impl Endpoint<State>) -> &mut Self {
247        self.method(http_types::Method::Patch, ep);
248        self
249    }
250
251    /// Add an endpoint for `TRACE` requests
252    pub fn trace(&mut self, ep: impl Endpoint<State>) -> &mut Self {
253        self.method(http_types::Method::Trace, ep);
254        self
255    }
256}
257
258#[derive(Debug)]
259struct StripPrefixEndpoint<E>(std::sync::Arc<E>);
260
261impl<E> StripPrefixEndpoint<E> {
262    fn new(ep: E) -> Self {
263        Self(std::sync::Arc::new(ep))
264    }
265}
266
267impl<E> Clone for StripPrefixEndpoint<E> {
268    fn clone(&self) -> Self {
269        Self(self.0.clone())
270    }
271}
272
273#[async_trait::async_trait]
274impl<State, E> Endpoint<State> for StripPrefixEndpoint<E>
275where
276    State: Clone + Send + Sync + 'static,
277    E: Endpoint<State>,
278{
279    async fn call(&self, req: crate::Request<State>) -> crate::Result {
280        let crate::Request {
281            state,
282            mut req,
283            route_params,
284        } = req;
285
286        let rest = crate::request::rest(&route_params).unwrap_or("");
287        req.url_mut().set_path(&rest);
288
289        self.0
290            .call(crate::Request {
291                state,
292                req,
293                route_params,
294            })
295            .await
296    }
297}