Skip to main content

thruster_app/
app.rs

1use std::io;
2
3use std::future::Future;
4use thruster_core::context::Context;
5use thruster_core::request::{Request, RequestWithParams};
6use thruster_core::route_parser::{MatchedRoute, RouteParser};
7use thruster_core::middleware::{MiddlewareChain};
8use thruster_context::basic_context::{generate_context, BasicContext};
9
10enum Method {
11  DELETE,
12  GET,
13  OPTIONS,
14  POST,
15  PUT,
16  UPDATE
17}
18
19// Warning, this method is slow and shouldn't be used for route matching, only for route adding
20fn _add_method_to_route(method: &Method, path: &str) -> String {
21  let prefix = match method {
22    Method::DELETE => "__DELETE__",
23    Method::GET => "__GET__",
24    Method::OPTIONS => "__OPTIONS__",
25    Method::POST => "__POST__",
26    Method::PUT => "__PUT__",
27    Method::UPDATE => "__UPDATE__"
28  };
29
30  match &path[0..1] {
31    "/" => format!("{}{}", prefix, path),
32    _ => format!("{}/{}", prefix, path)
33  }
34}
35
36#[inline]
37fn _add_method_to_route_from_str(method: &str, path: &str) -> String {
38  templatify!("__" ; method ; "__" ; path ; "")
39}
40
41///
42/// App, the main component of Thruster. The App is the entry point for your application
43/// and handles all incomming requests. Apps are also composeable, that is, via the `subapp`
44/// method, you can use all of the methods and middlewares contained within an app as a subset
45/// of your app's routes.
46///
47/// There are three main parts to creating a thruster app:
48/// 1. Use `App.create` to create a new app with a custom context generator
49/// 2. Add routes and middleware via `.get`, `.post`, etc.
50/// 3. Start the app with `App.start`
51///
52/// # Examples
53/// Subapp
54///
55/// ```rust, ignore
56/// let mut app1 = App::<Request, BasicContext>::new();
57///
58/// fn test_fn_1(context: BasicContext, next: impl Fn(BasicContext) -> MiddlewareReturnValue<BasicContext>  + Send) -> MiddlewareReturnValue<BasicContext> {
59///   Box::new(future::ok(BasicContext {
60///     body: context.params.get("id").unwrap().to_owned(),
61///     params: context.params,
62///     query_params: context.query_params
63///   }))
64/// };
65///
66/// app1.get("/:id", middleware![BasicContext => test_fn_1]);
67///
68/// let mut app2 = App::<Request, BasicContext>::new();
69/// app2.use_sub_app("/test", &app1);
70/// ```
71///
72/// In the above example, the route `/test/some-id` will return `some-id` in the body of the response.
73///
74/// The provided start methods are great places to start, but you can also simply use Thruster as a router
75/// and create your own version of an HTTP server by directly calling `App.resolve` with a Request object.
76/// It will then return a future with the Response object that corresponds to the request. This can be
77/// useful if trying to integrate with a different type of load balancing system within the threads of the
78/// application.
79///
80pub struct App<R: RequestWithParams, T: 'static + Context + Send> {
81  pub _route_parser: RouteParser<T>,
82  ///
83  /// Generate context is common to all `App`s. It's the function that's called upon receiving a request
84  /// that translates an acutal `Request` struct to your custom Context type. It should be noted that
85  /// the context_generator should be as fast as possible as this is called with every request, including
86  /// 404s.
87  pub context_generator: fn(R) -> T
88}
89
90impl<R: RequestWithParams, T: Context + Send> App<R, T> {
91  /// Creates a new instance of app with the library supplied `BasicContext`. Useful for trivial
92  /// examples, likely not a good solution for real code bases. The advantage is that the
93  /// context_generator is already supplied for the developer.
94  pub fn new_basic() -> App<Request, BasicContext> {
95    App::create(generate_context)
96  }
97
98  /// Create a new app with the given context generator. The app does not begin listening until start
99  /// is called.
100  pub fn create(generate_context: fn(R) -> T) -> App<R, T> {
101    App {
102      _route_parser: RouteParser::new(),
103      context_generator: generate_context
104    }
105  }
106
107  /// Add method-agnostic middleware for a route. This is useful for applying headers, logging, and
108  /// anything else that might not be sensitive to the HTTP method for the endpoint.
109  pub fn use_middleware(&mut self, path: &'static str, middleware: MiddlewareChain<T>) -> &mut App<R, T> {
110    self._route_parser.add_method_agnostic_middleware(path, middleware);
111
112    self
113  }
114
115  /// Add an app as a predetermined set of routes and middleware. Will prefix whatever string is passed
116  /// in to all of the routes. This is a main feature of Thruster, as it allows projects to be extermely
117  /// modular and composeable in nature.
118  pub fn use_sub_app(&mut self, prefix: &'static str, app: App<R, T>) -> &mut App<R, T> {
119    self._route_parser.route_tree
120      .add_route_tree(prefix, app._route_parser.route_tree);
121
122    self
123  }
124
125  /// Return the route parser for a given app
126  pub fn get_route_parser(&self) -> &RouteParser<T> {
127    &self._route_parser
128  }
129
130  /// Add a route that responds to `GET`s to a given path
131  pub fn get(&mut self, path: &'static str, middlewares: MiddlewareChain<T>) -> &mut App<R, T> {
132    self._route_parser.add_route(
133      &_add_method_to_route(&Method::GET, path), middlewares);
134
135    self
136  }
137
138  /// Add a route that responds to `OPTION`s to a given path
139  pub fn options(&mut self, path: &'static str, middlewares: MiddlewareChain<T>) -> &mut App<R, T> {
140    self._route_parser.add_route(
141      &_add_method_to_route(&Method::OPTIONS, path), middlewares);
142
143    self
144  }
145
146  /// Add a route that responds to `POST`s to a given path
147  pub fn post(&mut self, path: &'static str, middlewares: MiddlewareChain<T>) -> &mut App<R, T> {
148    self._route_parser.add_route(
149      &_add_method_to_route(&Method::POST, path), middlewares);
150
151    self
152  }
153
154  /// Add a route that responds to `PUT`s to a given path
155  pub fn put(&mut self, path: &'static str, middlewares: MiddlewareChain<T>) -> &mut App<R, T> {
156    self._route_parser.add_route(
157      &_add_method_to_route(&Method::PUT, path), middlewares);
158
159    self
160  }
161
162  /// Add a route that responds to `DELETE`s to a given path
163  pub fn delete(&mut self, path: &'static str, middlewares: MiddlewareChain<T>) -> &mut App<R, T> {
164    self._route_parser.add_route(
165      &_add_method_to_route(&Method::DELETE, path), middlewares);
166
167    self
168  }
169
170  /// Add a route that responds to `UPDATE`s to a given path
171  pub fn update(&mut self, path: &'static str, middlewares: MiddlewareChain<T>) -> &mut App<R, T> {
172    self._route_parser.add_route(
173      &_add_method_to_route(&Method::UPDATE, path), middlewares);
174
175    self
176  }
177
178  /// Sets the middleware if no route is successfully matched.
179  pub fn set404(&mut self, middlewares: MiddlewareChain<T>) -> &mut App<R, T> {
180    self._route_parser.add_route(
181      &_add_method_to_route(&Method::GET, "/*"), middlewares.clone());
182    self._route_parser.add_route(
183      &_add_method_to_route(&Method::POST, "/*"), middlewares.clone());
184    self._route_parser.add_route(
185      &_add_method_to_route(&Method::PUT, "/*"), middlewares.clone());
186    self._route_parser.add_route(
187      &_add_method_to_route(&Method::UPDATE, "/*"), middlewares.clone());
188    self._route_parser.add_route(
189      &_add_method_to_route(&Method::DELETE, "/*"), middlewares);
190
191    self
192  }
193
194  pub fn resolve_from_method_and_path(&self, method: &str, path: &str) -> MatchedRoute<T> {
195    let path_with_method = &_add_method_to_route_from_str(method, path);
196
197    self._route_parser.match_route(path_with_method)
198  }
199
200  /// Resolves a request, returning a future that is processable into a Response
201  #[cfg(feature = "hyper_server")]
202  pub fn resolve(&self, request: R, matched_route: MatchedRoute<T>) -> impl Future<Output=Result<T::Response, io::Error>> + Send {
203    self._resolve(request, matched_route)
204  }
205
206  #[cfg(not(feature = "hyper_server"))]
207  pub fn resolve(&self, request: R, matched_route: MatchedRoute<T>) -> impl Future<Output=Result<T::Response, io::Error>> + Send {
208    self._resolve(request, matched_route)
209  }
210
211  fn _resolve(&self, request: R, matched_route: MatchedRoute<T>) -> impl Future<Output=Result<T::Response, io::Error>> + Send {
212    use thruster_async_await::resolve;
213
214    resolve(self.context_generator, request, matched_route)
215  }
216}