1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
//! PDF generation service module.
//!
//! This module provides the **framework-agnostic core** of the PDF generation
//! service. It contains shared types, error definitions, and the core PDF
//! generation logic that is reused across all web framework integrations.
//!
//! # Module Overview
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────────────────┐
//! │ html2pdf-api crate │
//! │ │
//! │ ┌───────────────────────────────────────────────────────────────────┐ │
//! │ │ service module (this module) │ │
//! │ │ │ │
//! │ │ ┌─────────────────────────┐ ┌─────────────────────────────────┐ │ │
//! │ │ │ types.rs │ │ pdf.rs │ │ │
//! │ │ │ ┌───────────────────┐ │ │ ┌───────────────────────────┐ │ │ │
//! │ │ │ │ PdfFromUrlRequest │ │ │ │ generate_pdf_from_url() │ │ │ │
//! │ │ │ │ PdfFromHtmlRequest│ │ │ │ generate_pdf_from_html() │ │ │ │
//! │ │ │ │ PdfResponse │ │ │ │ get_pool_stats() │ │ │ │
//! │ │ │ │ PdfServiceError │ │ │ │ is_pool_ready() │ │ │ │
//! │ │ │ │ ErrorResponse │ │ │ └───────────────────────────┘ │ │ │
//! │ │ │ │ PoolStatsResponse │ │ │ │ │ │
//! │ │ │ │ HealthResponse │ │ │ │ │ │
//! │ │ │ └───────────────────┘ │ │ │ │ │
//! │ │ └─────────────────────────┘ └─────────────────────────────────┘ │ │
//! │ └───────────────────────────────────────────────────────────────────┘ │
//! │ │ │
//! │ │ used by │
//! │ ▼ │
//! │ ┌───────────────────────────────────────────────────────────────────┐ │
//! │ │ integrations module │ │
//! │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
//! │ │ │ actix.rs │ │ rocket.rs │ │ axum.rs │ │ │
//! │ │ │ (handlers) │ │ (handlers) │ │ (handlers) │ │ │
//! │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
//! │ └───────────────────────────────────────────────────────────────────┘ │
//! └─────────────────────────────────────────────────────────────────────────┘
//! ```
//!
//! # Design Philosophy
//!
//! This module follows the **"thin handler, thick service"** pattern:
//!
//! | Layer | Responsibility | This Module? |
//! |-------|----------------|--------------|
//! | **Service** | Core business logic, validation, PDF generation | ✅ Yes |
//! | **Handler** | HTTP request/response mapping, framework glue | ❌ No (integrations) |
//!
//! Benefits of this design:
//! - **Single source of truth** for PDF generation logic
//! - **Easy testing** without HTTP overhead
//! - **Framework flexibility** - add new frameworks without duplicating logic
//! - **Type safety** - shared types ensure consistency across integrations
//!
//! # Public API Summary
//!
//! ## Request Types
//!
//! | Type | Purpose | Used By |
//! |------|---------|---------|
//! | `PdfFromUrlRequest` | Parameters for URL → PDF conversion | `GET /pdf` |
//! | `PdfFromHtmlRequest` | Parameters for HTML → PDF conversion | `POST /pdf/html` |
//!
//! ## Response Types
//!
//! | Type | Purpose | Used By |
//! |------|---------|---------|
//! | `PdfResponse` | Successful PDF generation result | PDF endpoints |
//! | `PoolStatsResponse` | Browser pool statistics | `GET /pool/stats` |
//! | `HealthResponse` | Health check response | `GET /health` |
//! | `ErrorResponse` | JSON error response | All endpoints (on error) |
//!
//! ## Error Types
//!
//! | Type | Purpose |
//! |------|---------|
//! | `PdfServiceError` | All possible service errors with HTTP status mapping |
//!
//! ## Core Functions
//!
//! | Function | Purpose | Blocking? |
//! |----------|---------|-----------|
//! | `generate_pdf_from_url` | Convert URL to PDF | ⚠️ Yes |
//! | `generate_pdf_from_html` | Convert HTML to PDF | ⚠️ Yes |
//! | `get_pool_stats` | Get pool statistics | ✅ Fast |
//! | `is_pool_ready` | Check pool readiness | ✅ Fast |
//!
//! ## Constants
//!
//! | Constant | Value | Purpose |
//! |----------|-------|---------|
//! | `DEFAULT_TIMEOUT_SECS` | 60 | Overall operation timeout |
//! | `DEFAULT_WAIT_SECS` | 5 | JavaScript wait time |
//!
//! # Usage Patterns
//!
//! ## Pattern 1: Use Pre-built Framework Integration (Recommended)
//!
//! The easiest way to use this library is via the pre-built integrations:
//!
//! ```rust,ignore
//! use actix_web::{App, HttpServer, web};
//! use html2pdf_api::prelude::*;
//!
//! #[actix_web::main]
//! async fn main() -> std::io::Result<()> {
//! let pool = init_browser_pool().await?;
//!
//! HttpServer::new(move || {
//! App::new()
//! .app_data(web::Data::new(pool.clone()))
//! .configure(html2pdf_api::integrations::actix::configure_routes)
//! })
//! .bind("127.0.0.1:8080")?
//! .run()
//! .await
//! }
//! ```
//!
//! ## Pattern 2: Custom Handlers with Service Functions
//!
//! For custom behavior, use the service functions directly:
//!
//! ```rust,ignore
//! use actix_web::{web, HttpResponse};
//! use html2pdf_api::service::{
//! generate_pdf_from_url, PdfFromUrlRequest, PdfServiceError
//! };
//! use std::sync::{Arc, Mutex};
//!
//! async fn custom_pdf_handler(
//! pool: web::Data<Arc<BrowserPool>>,
//! query: web::Query<PdfFromUrlRequest>,
//! ) -> HttpResponse {
//! // Add custom logic: authentication, rate limiting, logging, etc.
//! log::info!("Custom handler called for: {}", query.url);
//!
//! let pool = pool.into_inner();
//! let request = query.into_inner();
//!
//! // Call service in blocking context
//! let result = web::block(move || {
//! generate_pdf_from_url(&pool, &request)
//! }).await;
//!
//! match result {
//! Ok(Ok(pdf)) => {
//! // Add custom headers, transform response, etc.
//! HttpResponse::Ok()
//! .content_type("application/pdf")
//! .insert_header(("X-Custom-Header", "value"))
//! .body(pdf.data)
//! }
//! Ok(Err(e)) => {
//! // Custom error handling
//! HttpResponse::build(http::StatusCode::from_u16(e.status_code()).unwrap())
//! .json(serde_json::json!({
//! "error": e.to_string(),
//! "code": e.error_code(),
//! "request_id": "custom-id-123"
//! }))
//! }
//! Err(e) => {
//! HttpResponse::InternalServerError().body(e.to_string())
//! }
//! }
//! }
//! ```
//!
//! ## Pattern 3: Direct Service Usage (Non-HTTP)
//!
//! For CLI tools, batch processing, or testing:
//!
//! ```rust,ignore
//! use html2pdf_api::service::{
//! generate_pdf_from_url, generate_pdf_from_html,
//! PdfFromUrlRequest, PdfFromHtmlRequest,
//! };
//! use std::sync::Mutex;
//!
//! fn batch_convert(pool: &BrowserPool, urls: Vec<String>) -> Vec<Result<Vec<u8>, PdfServiceError>> {
//! urls.into_iter()
//! .map(|url| {
//! let request = PdfFromUrlRequest {
//! url,
//! landscape: Some(true),
//! ..Default::default()
//! };
//! generate_pdf_from_url(pool, &request).map(|r| r.data)
//! })
//! .collect()
//! }
//!
//! fn generate_report(pool: &BrowserPool, html: String) -> Result<(), Box<dyn std::error::Error>> {
//! let request = PdfFromHtmlRequest {
//! html,
//! filename: Some("report.pdf".to_string()),
//! ..Default::default()
//! };
//!
//! let response = generate_pdf_from_html(pool, &request)?;
//! std::fs::write("report.pdf", &response.data)?;
//! println!("Generated report: {} bytes", response.size());
//! Ok(())
//! }
//! ```
//!
//! # Blocking Behavior
//!
//! ⚠️ **Important:** The PDF generation functions (`generate_pdf_from_url` and
//! `generate_pdf_from_html`) are **blocking** and should never be called directly
//! from an async context.
//!
//! ## Correct Usage
//!
//! ```rust,ignore
//! // ✅ Actix-web: Use web::block
//! let result = web::block(move || {
//! generate_pdf_from_url(&pool, &request)
//! }).await;
//!
//! // ✅ Tokio: Use spawn_blocking
//! let result = tokio::task::spawn_blocking(move || {
//! generate_pdf_from_url(&pool, &request)
//! }).await;
//!
//! // ✅ Synchronous context: Call directly
//! let result = generate_pdf_from_url(&pool, &request);
//! ```
//!
//! ## Incorrect Usage
//!
//! ```rust,ignore
//! // ❌ WRONG: Blocking the async runtime
//! async fn bad_handler(pool: web::Data<SharedPool>) -> HttpResponse {
//! // This blocks the entire async runtime thread!
//! let result = generate_pdf_from_url(&pool, &request);
//! // ...
//! }
//! ```
//!
//! # Error Handling
//!
//! All service functions return `Result<T, PdfServiceError>`. The error type
//! provides HTTP status codes and error codes for easy API response building:
//!
//! ```rust,ignore
//! use html2pdf_api::service::{PdfServiceError, ErrorResponse};
//!
//! fn handle_error(error: PdfServiceError) -> (u16, ErrorResponse) {
//! let status = error.status_code(); // e.g., 400, 503, 504
//! let response = ErrorResponse::from(&error);
//! (status, response)
//! }
//!
//! // Check if error is worth retrying
//! if error.is_retryable() {
//! // Wait and retry
//! std::thread::sleep(Duration::from_secs(1));
//! }
//! ```
//!
//! # Testing
//!
//! The service functions can be tested without HTTP:
//!
//! ```rust,ignore
//! use html2pdf_api::service::{generate_pdf_from_url, PdfFromUrlRequest, PdfServiceError};
//! use html2pdf_api::factory::mock::MockBrowserFactory;
//!
//! #[test]
//! fn test_invalid_url_returns_error() {
//! let pool = create_test_pool();
//!
//! let request = PdfFromUrlRequest {
//! url: "not-a-valid-url".to_string(),
//! ..Default::default()
//! };
//!
//! let result = generate_pdf_from_url(&pool, &request);
//!
//! assert!(matches!(result, Err(PdfServiceError::InvalidUrl(_))));
//! }
//!
//! #[test]
//! fn test_empty_html_returns_error() {
//! let pool = create_test_pool();
//!
//! let request = PdfFromHtmlRequest {
//! html: " ".to_string(), // whitespace only
//! ..Default::default()
//! };
//!
//! let result = generate_pdf_from_html(&pool, &request);
//!
//! assert!(matches!(result, Err(PdfServiceError::EmptyHtml)));
//! }
//! ```
//!
//! # Feature Flags
//!
//! This module is always available. However, the types include serde support
//! which is enabled by any integration feature:
//!
//! | Feature | Effect on this module |
//! |---------|----------------------|
//! | `actix-integration` | Enables `serde` for request/response types |
//! | `rocket-integration` | Enables `serde` for request/response types |
//! | `axum-integration` | Enables `serde` for request/response types |
//!
//! # See Also
//!
//! - [`crate::pool`] - Browser pool management
//! - [`crate::integrations`] - Framework-specific handlers
//! - [`crate::prelude`] - Convenient re-exports
// ============================================================================
// Re-exports: Types
// ============================================================================
pub use ErrorResponse;
pub use HealthResponse;
pub use PdfFromHtmlRequest;
pub use PdfFromUrlRequest;
pub use PdfResponse;
pub use PdfServiceError;
pub use PoolStatsResponse;
// ============================================================================
// Re-exports: Functions
// ============================================================================
pub use generate_pdf_from_html;
pub use generate_pdf_from_url;
pub use get_pool_stats;
pub use is_pool_ready;
// ============================================================================
// Re-exports: Constants
// ============================================================================
pub use DEFAULT_TIMEOUT_SECS;
pub use DEFAULT_WAIT_SECS;
// ============================================================================
// Module-level tests
// ============================================================================