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}