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}