rocket/fairing/
ad_hoc.rs

1use futures::future::{Future, BoxFuture, FutureExt};
2use parking_lot::Mutex;
3
4use crate::{Rocket, Request, Response, Data, Build, Orbit};
5use crate::fairing::{Fairing, Kind, Info, Result};
6
7/// A ad-hoc fairing that can be created from a function or closure.
8///
9/// This enum can be used to create a fairing from a simple function or closure
10/// without creating a new structure or implementing `Fairing` directly.
11///
12/// # Usage
13///
14/// Use [`AdHoc::on_ignite`], [`AdHoc::on_liftoff`], [`AdHoc::on_request()`], or
15/// [`AdHoc::on_response()`] to create an `AdHoc` structure from a function or
16/// closure. Then, simply attach the structure to the `Rocket` instance.
17///
18/// # Example
19///
20/// The following snippet creates a `Rocket` instance with two ad-hoc fairings.
21/// The first, a liftoff fairing named "Liftoff Printer", simply prints a message
22/// indicating that Rocket has launched. The second named "Put Rewriter", a
23/// request fairing, rewrites the method of all requests to be `PUT`.
24///
25/// ```rust
26/// use rocket::fairing::AdHoc;
27/// use rocket::http::Method;
28///
29/// rocket::build()
30///     .attach(AdHoc::on_liftoff("Liftoff Printer", |_| Box::pin(async move {
31///         println!("...annnddd we have liftoff!");
32///     })))
33///     .attach(AdHoc::on_request("Put Rewriter", |req, _| Box::pin(async move {
34///         req.set_method(Method::Put);
35///     })));
36/// ```
37pub struct AdHoc {
38    name: &'static str,
39    kind: AdHocKind,
40}
41
42struct Once<F: ?Sized>(Mutex<Option<Box<F>>>);
43
44impl<F: ?Sized> Once<F> {
45    fn new(f: Box<F>) -> Self { Once(Mutex::new(Some(f))) }
46
47    #[track_caller]
48    fn take(&self) -> Box<F> {
49        self.0.lock().take().expect("Once::take() called once")
50    }
51}
52
53enum AdHocKind {
54    /// An ad-hoc **ignite** fairing. Called during ignition.
55    Ignite(Once<dyn FnOnce(Rocket<Build>) -> BoxFuture<'static, Result> + Send + 'static>),
56
57    /// An ad-hoc **liftoff** fairing. Called just after Rocket launches.
58    Liftoff(Once<dyn for<'a> FnOnce(&'a Rocket<Orbit>) -> BoxFuture<'a, ()> + Send + 'static>),
59
60    /// An ad-hoc **request** fairing. Called when a request is received.
61    Request(Box<dyn for<'a> Fn(&'a mut Request<'_>, &'a Data<'_>)
62        -> BoxFuture<'a, ()> + Send + Sync + 'static>),
63
64    /// An ad-hoc **response** fairing. Called when a response is ready to be
65    /// sent to a client.
66    Response(Box<dyn for<'r, 'b> Fn(&'r Request<'_>, &'b mut Response<'r>)
67        -> BoxFuture<'b, ()> + Send + Sync + 'static>),
68
69    /// An ad-hoc **shutdown** fairing. Called on shutdown.
70    Shutdown(Once<dyn for<'a> FnOnce(&'a Rocket<Orbit>) -> BoxFuture<'a, ()> + Send + 'static>),
71}
72
73impl AdHoc {
74    /// Constructs an `AdHoc` ignite fairing named `name`. The function `f` will
75    /// be called by Rocket during the [`Rocket::ignite()`] phase.
76    ///
77    /// This version of an `AdHoc` ignite fairing cannot abort ignite. For a
78    /// fallible version that can, see [`AdHoc::try_on_ignite()`].
79    ///
80    /// # Example
81    ///
82    /// ```rust
83    /// use rocket::fairing::AdHoc;
84    ///
85    /// // The no-op ignite fairing.
86    /// let fairing = AdHoc::on_ignite("Boom!", |rocket| async move {
87    ///     rocket
88    /// });
89    /// ```
90    pub fn on_ignite<F, Fut>(name: &'static str, f: F) -> AdHoc
91        where F: FnOnce(Rocket<Build>) -> Fut + Send + 'static,
92              Fut: Future<Output = Rocket<Build>> + Send + 'static,
93    {
94        AdHoc::try_on_ignite(name, |rocket| f(rocket).map(Ok))
95    }
96
97    /// Constructs an `AdHoc` ignite fairing named `name`. The function `f` will
98    /// be called by Rocket during the [`Rocket::ignite()`] phase. Returning an
99    /// `Err` aborts ignition and thus launch.
100    ///
101    /// For an infallible version, see [`AdHoc::on_ignite()`].
102    ///
103    /// # Example
104    ///
105    /// ```rust
106    /// use rocket::fairing::AdHoc;
107    ///
108    /// // The no-op try ignite fairing.
109    /// let fairing = AdHoc::try_on_ignite("No-Op", |rocket| async { Ok(rocket) });
110    /// ```
111    pub fn try_on_ignite<F, Fut>(name: &'static str, f: F) -> AdHoc
112        where F: FnOnce(Rocket<Build>) -> Fut + Send + 'static,
113              Fut: Future<Output = Result> + Send + 'static,
114    {
115        AdHoc { name, kind: AdHocKind::Ignite(Once::new(Box::new(|r| f(r).boxed()))) }
116    }
117
118    /// Constructs an `AdHoc` liftoff fairing named `name`. The function `f`
119    /// will be called by Rocket just after [`Rocket::launch()`].
120    ///
121    /// # Example
122    ///
123    /// ```rust
124    /// use rocket::fairing::AdHoc;
125    ///
126    /// // A fairing that prints a message just before launching.
127    /// let fairing = AdHoc::on_liftoff("Boom!", |_| Box::pin(async move {
128    ///     println!("Rocket has lifted off!");
129    /// }));
130    /// ```
131    pub fn on_liftoff<F: Send + Sync + 'static>(name: &'static str, f: F) -> AdHoc
132        where F: for<'a> FnOnce(&'a Rocket<Orbit>) -> BoxFuture<'a, ()>
133    {
134        AdHoc { name, kind: AdHocKind::Liftoff(Once::new(Box::new(f))) }
135    }
136
137    /// Constructs an `AdHoc` request fairing named `name`. The function `f`
138    /// will be called and the returned `Future` will be `await`ed by Rocket
139    /// when a new request is received.
140    ///
141    /// # Example
142    ///
143    /// ```rust
144    /// use rocket::fairing::AdHoc;
145    ///
146    /// // The no-op request fairing.
147    /// let fairing = AdHoc::on_request("Dummy", |req, data| {
148    ///     Box::pin(async move {
149    ///         // do something with the request and data...
150    /// #       let (_, _) = (req, data);
151    ///     })
152    /// });
153    /// ```
154    pub fn on_request<F: Send + Sync + 'static>(name: &'static str, f: F) -> AdHoc
155        where F: for<'a> Fn(&'a mut Request<'_>, &'a Data<'_>) -> BoxFuture<'a, ()>
156    {
157        AdHoc { name, kind: AdHocKind::Request(Box::new(f)) }
158    }
159
160    // FIXME(rustc): We'd like to allow passing `async fn` to these methods...
161    // https://github.com/rust-lang/rust/issues/64552#issuecomment-666084589
162
163    /// Constructs an `AdHoc` response fairing named `name`. The function `f`
164    /// will be called and the returned `Future` will be `await`ed by Rocket
165    /// when a response is ready to be sent.
166    ///
167    /// # Example
168    ///
169    /// ```rust
170    /// use rocket::fairing::AdHoc;
171    ///
172    /// // The no-op response fairing.
173    /// let fairing = AdHoc::on_response("Dummy", |req, resp| {
174    ///     Box::pin(async move {
175    ///         // do something with the request and pending response...
176    /// #       let (_, _) = (req, resp);
177    ///     })
178    /// });
179    /// ```
180    pub fn on_response<F: Send + Sync + 'static>(name: &'static str, f: F) -> AdHoc
181        where F: for<'b, 'r> Fn(&'r Request<'_>, &'b mut Response<'r>) -> BoxFuture<'b, ()>
182    {
183        AdHoc { name, kind: AdHocKind::Response(Box::new(f)) }
184    }
185
186    /// Constructs an `AdHoc` shutdown fairing named `name`. The function `f`
187    /// will be called by Rocket when [shutdown is triggered].
188    ///
189    /// [shutdown is triggered]: crate::config::Shutdown#triggers
190    ///
191    /// # Example
192    ///
193    /// ```rust
194    /// use rocket::fairing::AdHoc;
195    ///
196    /// // A fairing that prints a message just before launching.
197    /// let fairing = AdHoc::on_shutdown("Bye!", |_| Box::pin(async move {
198    ///     println!("Rocket is on its way back!");
199    /// }));
200    /// ```
201    pub fn on_shutdown<F: Send + Sync + 'static>(name: &'static str, f: F) -> AdHoc
202        where F: for<'a> FnOnce(&'a Rocket<Orbit>) -> BoxFuture<'a, ()>
203    {
204        AdHoc { name, kind: AdHocKind::Shutdown(Once::new(Box::new(f))) }
205    }
206
207    /// Constructs an `AdHoc` launch fairing that extracts a configuration of
208    /// type `T` from the configured provider and stores it in managed state. If
209    /// extractions fails, pretty-prints the error message and aborts launch.
210    ///
211    /// # Example
212    ///
213    /// ```rust
214    /// # use rocket::launch;
215    /// use serde::Deserialize;
216    /// use rocket::fairing::AdHoc;
217    ///
218    /// #[derive(Deserialize)]
219    /// struct Config {
220    ///     field: String,
221    ///     other: usize,
222    ///     /* and so on.. */
223    /// }
224    ///
225    /// #[launch]
226    /// fn rocket() -> _ {
227    ///     rocket::build().attach(AdHoc::config::<Config>())
228    /// }
229    /// ```
230    pub fn config<'de, T>() -> AdHoc
231        where T: serde::Deserialize<'de> + Send + Sync + 'static
232    {
233        AdHoc::try_on_ignite(std::any::type_name::<T>(), |rocket| async {
234            let app_config = match rocket.figment().extract::<T>() {
235                Ok(config) => config,
236                Err(e) => {
237                    crate::config::pretty_print_error(e);
238                    return Err(rocket);
239                }
240            };
241
242            Ok(rocket.manage(app_config))
243        })
244    }
245}
246
247#[crate::async_trait]
248impl Fairing for AdHoc {
249    fn info(&self) -> Info {
250        let kind = match self.kind {
251            AdHocKind::Ignite(_) => Kind::Ignite,
252            AdHocKind::Liftoff(_) => Kind::Liftoff,
253            AdHocKind::Request(_) => Kind::Request,
254            AdHocKind::Response(_) => Kind::Response,
255            AdHocKind::Shutdown(_) => Kind::Shutdown,
256        };
257
258        Info { name: self.name, kind }
259    }
260
261    async fn on_ignite(&self, rocket: Rocket<Build>) -> Result {
262        match self.kind {
263            AdHocKind::Ignite(ref f) => (f.take())(rocket).await,
264            _ => Ok(rocket)
265        }
266    }
267
268    async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
269        if let AdHocKind::Liftoff(ref f) = self.kind {
270            (f.take())(rocket).await
271        }
272    }
273
274    async fn on_request(&self, req: &mut Request<'_>, data: &mut Data<'_>) {
275        if let AdHocKind::Request(ref f) = self.kind {
276            f(req, data).await
277        }
278    }
279
280    async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {
281        if let AdHocKind::Response(ref f) = self.kind {
282            f(req, res).await
283        }
284    }
285
286    async fn on_shutdown(&self, rocket: &Rocket<Orbit>) {
287        if let AdHocKind::Shutdown(ref f) = self.kind {
288            (f.take())(rocket).await
289        }
290    }
291}