html2pdf_api/integrations/
rocket.rs

1//! Rocket framework integration.
2//!
3//! This module provides helpers for using `BrowserPool` with Rocket.
4//!
5//! # Setup
6//!
7//! Add to your `Cargo.toml`:
8//!
9//! ```toml
10//! [dependencies]
11//! html2pdf-api = { version = "0.1", features = ["rocket-integration"] }
12//! rocket = "0.5"
13//! ```
14//!
15//! # Basic Usage
16//!
17//! ```rust,ignore
18//! use rocket::{get, launch, routes, State};
19//! use rocket::http::Status;
20//! use html2pdf_api::prelude::*;
21//! use std::sync::Arc;
22//!
23//! #[get("/pdf")]
24//! async fn generate_pdf(
25//!     pool: &State<SharedBrowserPool>,
26//! ) -> Result<Vec<u8>, Status> {
27//!     let pool_guard = pool.lock().map_err(|_| Status::InternalServerError)?;
28//!     let browser = pool_guard.get().map_err(|_| Status::InternalServerError)?;
29//!
30//!     let tab = browser.new_tab().map_err(|_| Status::InternalServerError)?;
31//!     tab.navigate_to("https://example.com").map_err(|_| Status::InternalServerError)?;
32//!
33//!     // Generate PDF...
34//!     let pdf_data = tab.print_to_pdf(None).map_err(|_| Status::InternalServerError)?;
35//!
36//!     Ok(pdf_data)
37//! }
38//!
39//! #[launch]
40//! async fn rocket() -> _ {
41//!     // Create and warmup pool
42//!     let pool = BrowserPool::builder()
43//!         .factory(Box::new(ChromeBrowserFactory::with_defaults()))
44//!         .build()
45//!         .expect("Failed to create pool");
46//!
47//!     pool.warmup().await.expect("Failed to warmup");
48//!
49//!     // Convert to shared state
50//!     let shared_pool = pool.into_shared();
51//!
52//!     rocket::build()
53//!         .manage(shared_pool)
54//!         .mount("/", routes![generate_pdf])
55//! }
56//! ```
57//!
58//! # Using with `init_browser_pool`
59//!
60//! If you have the `env-config` feature enabled:
61//!
62//! ```rust,ignore
63//! use rocket::{launch, routes};
64//! use html2pdf_api::init_browser_pool;
65//!
66//! #[launch]
67//! async fn rocket() -> _ {
68//!     let pool = init_browser_pool().await
69//!         .expect("Failed to initialize browser pool");
70//!
71//!     rocket::build()
72//!         .manage(pool)
73//!         .mount("/", routes![generate_pdf])
74//! }
75//! ```
76//!
77//! # Using Fairings for Lifecycle Management
78//!
79//! For proper startup and shutdown handling, use a custom fairing:
80//!
81//! ```rust,ignore
82//! use rocket::{Rocket, Build, fairing::{self, Fairing, Info, Kind}};
83//! use html2pdf_api::prelude::*;
84//!
85//! pub struct BrowserPoolFairing;
86//!
87//! #[rocket::async_trait]
88//! impl Fairing for BrowserPoolFairing {
89//!     fn info(&self) -> Info {
90//!         Info {
91//!             name: "Browser Pool",
92//!             kind: Kind::Ignite | Kind::Shutdown,
93//!         }
94//!     }
95//!
96//!     async fn on_ignite(&self, rocket: Rocket<Build>) -> fairing::Result {
97//!         let pool = BrowserPool::builder()
98//!             .factory(Box::new(ChromeBrowserFactory::with_defaults()))
99//!             .build()
100//!             .expect("Failed to create pool");
101//!
102//!         pool.warmup().await.expect("Failed to warmup");
103//!
104//!         Ok(rocket.manage(pool.into_shared()))
105//!     }
106//!
107//!     async fn on_shutdown(&self, rocket: &Rocket<rocket::Orbit>) {
108//!         if let Some(pool) = rocket.state::<SharedBrowserPool>() {
109//!             if let Ok(mut pool) = pool.lock() {
110//!                 pool.shutdown_async().await;
111//!             }
112//!         }
113//!     }
114//! }
115//!
116//! #[launch]
117//! fn rocket() -> _ {
118//!     rocket::build()
119//!         .attach(BrowserPoolFairing)
120//!         .mount("/", routes![generate_pdf])
121//! }
122//! ```
123//!
124//! # Response Types
125//!
126//! For PDF responses, you can create a custom responder:
127//!
128//! ```rust,ignore
129//! use rocket::response::{self, Response, Responder};
130//! use rocket::http::{ContentType, Status};
131//! use rocket::Request;
132//! use std::io::Cursor;
133//!
134//! pub struct PdfResponse(pub Vec<u8>);
135//!
136//! impl<'r> Responder<'r, 'static> for PdfResponse {
137//!     fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
138//!         Response::build()
139//!             .header(ContentType::PDF)
140//!             .sized_body(self.0.len(), Cursor::new(self.0))
141//!             .ok()
142//!     }
143//! }
144//! ```
145
146use rocket::State;
147
148use crate::SharedBrowserPool;
149use crate::pool::BrowserPool;
150
151/// Type alias for Rocket `State` wrapper around the shared pool.
152///
153/// Use this type in your handler parameters:
154///
155/// ```rust,ignore
156/// #[get("/pdf")]
157/// async fn handler(pool: BrowserPoolState<'_>) -> Result<Vec<u8>, Status> {
158///     let pool = pool.lock().unwrap();
159///     let browser = pool.get()?;
160///     // ...
161/// }
162/// ```
163pub type BrowserPoolState<'r> = &'r State<SharedBrowserPool>;
164
165/// Extension trait for `BrowserPool` with Rocket helpers.
166///
167/// Provides convenient methods for integrating with Rocket.
168pub trait BrowserPoolRocketExt {
169    /// Convert the pool into a form suitable for Rocket's `manage()`.
170    ///
171    /// # Example
172    ///
173    /// ```rust,ignore
174    /// use html2pdf_api::integrations::rocket::BrowserPoolRocketExt;
175    ///
176    /// let pool = BrowserPool::builder()
177    ///     .factory(Box::new(ChromeBrowserFactory::with_defaults()))
178    ///     .build()?;
179    ///
180    /// let managed_pool = pool.into_rocket_state();
181    ///
182    /// rocket::build().manage(managed_pool)
183    /// ```
184    fn into_rocket_state(self) -> SharedBrowserPool;
185}
186
187impl BrowserPoolRocketExt for BrowserPool {
188    fn into_rocket_state(self) -> SharedBrowserPool {
189        self.into_shared()
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    #[test]
198    fn test_extension_trait_exists() {
199        // This test just verifies the trait is properly defined
200        fn _accepts_shared_pool(_: SharedBrowserPool) {}
201    }
202}