1use serde::{Deserialize, Deserializer, Serialize};
2use serde_json::{Map, Value};
3
4#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7#[serde(rename_all = "camelCase")]
8pub struct Viewport {
9 pub width: u32,
10 pub height: u32,
11 #[serde(skip_serializing_if = "Option::is_none")]
12 pub device_scale_factor: Option<f64>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, Default)]
16#[serde(rename_all = "camelCase")]
17pub struct WaitForSelector {
18 pub selector: String,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub timeout: Option<u64>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, Default)]
24#[serde(rename_all = "camelCase")]
25pub struct GotoOptions {
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub wait_until: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub timeout: Option<u64>,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub referer: Option<String>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, Default)]
35pub struct JsonOptions {
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub prompt: Option<String>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub schema: Option<Value>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, Default)]
43#[serde(rename_all = "camelCase")]
44pub struct PdfMargin {
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub top: Option<String>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub bottom: Option<String>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub left: Option<String>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub right: Option<String>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, Default)]
56#[serde(rename_all = "camelCase")]
57pub struct PdfOptions {
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub format: Option<String>,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub landscape: Option<bool>,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub print_background: Option<bool>,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub scale: Option<f64>,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub display_header_footer: Option<bool>,
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub header_template: Option<String>,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 pub footer_template: Option<String>,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub margin: Option<PdfMargin>,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize, Default)]
79#[serde(rename_all = "camelCase")]
80pub struct CrawlPayload {
81 pub url: String,
82 #[serde(skip_serializing_if = "Option::is_none")]
83 pub limit: Option<u32>,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub formats: Option<Vec<String>>,
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub reject_resource_types: Option<Vec<String>>,
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub depth: Option<u32>,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub source: Option<String>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub render: Option<bool>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 pub include_external_links: Option<bool>,
96 #[serde(skip_serializing_if = "Option::is_none")]
97 pub include_subdomains: Option<bool>,
98 #[serde(skip_serializing_if = "Option::is_none")]
99 pub include_patterns: Option<Vec<String>>,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub exclude_patterns: Option<Vec<String>>,
102 #[serde(skip_serializing_if = "Option::is_none")]
103 pub max_age: Option<u64>,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub modified_since: Option<i64>,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub wait_for_selector: Option<WaitForSelector>,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub json_options: Option<JsonOptions>,
110 #[serde(flatten, skip_serializing_if = "Option::is_none")]
111 pub extra: Option<Map<String, Value>>,
112}
113
114#[derive(Debug, Clone, Serialize, Default)]
117#[serde(rename_all = "camelCase")]
118pub struct ScreenshotOptions {
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub full_page: Option<bool>,
121 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
122 pub format: Option<String>,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub quality: Option<u32>,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub omit_background: Option<bool>,
127}
128
129#[derive(Debug, Clone, Serialize, Default)]
130#[serde(rename_all = "camelCase")]
131pub struct ScreenshotPayload {
132 pub url: String,
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub viewport: Option<Viewport>,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub screenshot_options: Option<ScreenshotOptions>,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub selector: Option<String>,
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub wait_for_selector: Option<WaitForSelector>,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub goto_options: Option<GotoOptions>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub user_agent: Option<String>,
145}
146
147#[derive(Debug, Clone, Serialize, Default)]
150#[serde(rename_all = "camelCase")]
151pub struct PdfPayload {
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub url: Option<String>,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub html: Option<String>,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub viewport: Option<Viewport>,
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub pdf_options: Option<PdfOptions>,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 pub wait_for_selector: Option<WaitForSelector>,
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub goto_options: Option<GotoOptions>,
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub user_agent: Option<String>,
166}
167
168#[derive(Debug, Clone, Serialize, Default)]
171#[serde(rename_all = "camelCase")]
172pub struct ContentPayload {
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub url: Option<String>,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub html: Option<String>,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub viewport: Option<Viewport>,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub goto_options: Option<GotoOptions>,
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub wait_for_selector: Option<WaitForSelector>,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub user_agent: Option<String>,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub reject_resource_types: Option<Vec<String>>,
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub reject_request_pattern: Option<Vec<String>>,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub allow_resource_types: Option<Vec<String>>,
191 #[serde(skip_serializing_if = "Option::is_none")]
192 pub allow_request_pattern: Option<Vec<String>>,
193 #[serde(skip_serializing_if = "Option::is_none")]
194 #[serde(rename = "setExtraHTTPHeaders")]
195 pub set_extra_http_headers: Option<Map<String, Value>>,
196 #[serde(skip_serializing_if = "Option::is_none")]
197 pub cookies: Option<Vec<Value>>,
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub set_javascript_enabled: Option<bool>,
200 #[serde(skip_serializing_if = "Option::is_none")]
201 pub emulate_media_type: Option<String>,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub best_attempt: Option<bool>,
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub action_timeout: Option<u64>,
206 #[serde(skip_serializing_if = "Option::is_none")]
207 pub wait_for_timeout: Option<u64>,
208 #[serde(skip_serializing_if = "Option::is_none")]
209 pub add_script_tag: Option<Vec<Value>>,
210 #[serde(skip_serializing_if = "Option::is_none")]
211 pub add_style_tag: Option<Vec<Value>>,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize, Default)]
215#[serde(rename_all = "camelCase")]
216pub struct ContentMeta {
217 #[serde(skip_serializing_if = "Option::is_none")]
218 pub status: Option<u16>,
219 #[serde(skip_serializing_if = "Option::is_none")]
220 pub title: Option<String>,
221}
222
223#[derive(Debug, Clone, Deserialize)]
224#[serde(rename_all = "camelCase")]
225pub struct ContentResult {
226 pub success: bool,
227 pub result: Option<String>,
228 pub errors: Option<Vec<Value>>,
229 pub meta: Option<ContentMeta>,
230}
231
232#[derive(Debug, Clone, Serialize, Default)]
235#[serde(rename_all = "camelCase")]
236pub struct MarkdownPayload {
237 #[serde(skip_serializing_if = "Option::is_none")]
238 pub url: Option<String>,
239 #[serde(skip_serializing_if = "Option::is_none")]
240 pub html: Option<String>,
241 #[serde(skip_serializing_if = "Option::is_none")]
242 pub viewport: Option<Viewport>,
243 #[serde(skip_serializing_if = "Option::is_none")]
244 pub goto_options: Option<GotoOptions>,
245 #[serde(skip_serializing_if = "Option::is_none")]
246 pub wait_for_selector: Option<WaitForSelector>,
247 #[serde(skip_serializing_if = "Option::is_none")]
248 pub user_agent: Option<String>,
249 #[serde(skip_serializing_if = "Option::is_none")]
250 pub reject_resource_types: Option<Vec<String>>,
251 #[serde(skip_serializing_if = "Option::is_none")]
252 pub reject_request_pattern: Option<Vec<String>>,
253 #[serde(skip_serializing_if = "Option::is_none")]
254 pub allow_resource_types: Option<Vec<String>>,
255 #[serde(skip_serializing_if = "Option::is_none")]
256 pub allow_request_pattern: Option<Vec<String>>,
257 #[serde(skip_serializing_if = "Option::is_none")]
258 #[serde(rename = "setExtraHTTPHeaders")]
259 pub set_extra_http_headers: Option<Map<String, Value>>,
260 #[serde(skip_serializing_if = "Option::is_none")]
261 pub cookies: Option<Vec<Value>>,
262 #[serde(skip_serializing_if = "Option::is_none")]
263 pub set_javascript_enabled: Option<bool>,
264 #[serde(skip_serializing_if = "Option::is_none")]
265 pub emulate_media_type: Option<String>,
266 #[serde(skip_serializing_if = "Option::is_none")]
267 pub best_attempt: Option<bool>,
268 #[serde(skip_serializing_if = "Option::is_none")]
269 pub action_timeout: Option<u64>,
270 #[serde(skip_serializing_if = "Option::is_none")]
271 pub wait_for_timeout: Option<u64>,
272 #[serde(skip_serializing_if = "Option::is_none")]
273 pub add_script_tag: Option<Vec<Value>>,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 pub add_style_tag: Option<Vec<Value>>,
276}
277
278#[derive(Debug, Clone, Deserialize)]
279#[serde(rename_all = "camelCase")]
280pub struct MarkdownResult {
281 pub success: bool,
282 pub result: Option<String>,
283 pub errors: Option<Vec<Value>>,
284 pub meta: Option<ContentMeta>,
285}
286
287#[derive(Debug, Clone, Serialize, Default)]
290#[serde(rename_all = "camelCase")]
291pub struct SnapshotPayload {
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub url: Option<String>,
294 #[serde(skip_serializing_if = "Option::is_none")]
295 pub html: Option<String>,
296 #[serde(skip_serializing_if = "Option::is_none")]
297 pub viewport: Option<Viewport>,
298 #[serde(skip_serializing_if = "Option::is_none")]
299 pub screenshot_options: Option<ScreenshotOptions>,
300 #[serde(skip_serializing_if = "Option::is_none")]
301 pub goto_options: Option<GotoOptions>,
302 #[serde(skip_serializing_if = "Option::is_none")]
303 pub wait_for_selector: Option<WaitForSelector>,
304 #[serde(skip_serializing_if = "Option::is_none")]
305 pub user_agent: Option<String>,
306 #[serde(skip_serializing_if = "Option::is_none")]
307 pub reject_resource_types: Option<Vec<String>>,
308 #[serde(skip_serializing_if = "Option::is_none")]
309 pub reject_request_pattern: Option<Vec<String>>,
310 #[serde(skip_serializing_if = "Option::is_none")]
311 pub allow_resource_types: Option<Vec<String>>,
312 #[serde(skip_serializing_if = "Option::is_none")]
313 pub allow_request_pattern: Option<Vec<String>>,
314 #[serde(skip_serializing_if = "Option::is_none")]
315 #[serde(rename = "setExtraHTTPHeaders")]
316 pub set_extra_http_headers: Option<Map<String, Value>>,
317 #[serde(skip_serializing_if = "Option::is_none")]
318 pub cookies: Option<Vec<Value>>,
319 #[serde(skip_serializing_if = "Option::is_none")]
320 pub set_javascript_enabled: Option<bool>,
321 #[serde(skip_serializing_if = "Option::is_none")]
322 pub emulate_media_type: Option<String>,
323 #[serde(skip_serializing_if = "Option::is_none")]
324 pub best_attempt: Option<bool>,
325 #[serde(skip_serializing_if = "Option::is_none")]
326 pub action_timeout: Option<u64>,
327 #[serde(skip_serializing_if = "Option::is_none")]
328 pub wait_for_timeout: Option<u64>,
329 #[serde(skip_serializing_if = "Option::is_none")]
330 pub add_script_tag: Option<Vec<Value>>,
331 #[serde(skip_serializing_if = "Option::is_none")]
332 pub add_style_tag: Option<Vec<Value>>,
333}
334
335#[derive(Debug, Clone, Deserialize)]
336#[serde(rename_all = "camelCase")]
337pub struct SnapshotResultData {
338 pub screenshot: String,
339 pub content: String,
340}
341
342#[derive(Debug, Clone, Deserialize)]
343#[serde(rename_all = "camelCase")]
344pub struct SnapshotResult {
345 pub success: bool,
346 pub result: Option<SnapshotResultData>,
347 pub errors: Option<Vec<Value>>,
348}
349
350#[derive(Debug, Clone, Serialize, Deserialize, Default)]
353pub struct ResponseFormat {
354 #[serde(rename = "type")]
355 pub format_type: String,
356 pub schema: Value,
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
360pub struct CustomAiModel {
361 pub model: String,
362 pub authorization: String,
363}
364
365#[derive(Debug, Clone, Serialize, Default)]
366#[serde(rename_all = "camelCase")]
367pub struct JsonPayload {
368 #[serde(skip_serializing_if = "Option::is_none")]
369 pub url: Option<String>,
370 #[serde(skip_serializing_if = "Option::is_none")]
371 pub html: Option<String>,
372 #[serde(skip_serializing_if = "Option::is_none")]
373 pub prompt: Option<String>,
374 #[serde(rename = "response_format", skip_serializing_if = "Option::is_none")]
375 pub response_format: Option<ResponseFormat>,
376 #[serde(rename = "custom_ai", skip_serializing_if = "Option::is_none")]
377 pub custom_ai: Option<Vec<CustomAiModel>>,
378 #[serde(skip_serializing_if = "Option::is_none")]
379 pub viewport: Option<Viewport>,
380 #[serde(skip_serializing_if = "Option::is_none")]
381 pub goto_options: Option<GotoOptions>,
382 #[serde(skip_serializing_if = "Option::is_none")]
383 pub wait_for_selector: Option<WaitForSelector>,
384 #[serde(skip_serializing_if = "Option::is_none")]
385 pub user_agent: Option<String>,
386 #[serde(skip_serializing_if = "Option::is_none")]
387 pub reject_resource_types: Option<Vec<String>>,
388 #[serde(skip_serializing_if = "Option::is_none")]
389 pub reject_request_pattern: Option<Vec<String>>,
390 #[serde(skip_serializing_if = "Option::is_none")]
391 pub allow_resource_types: Option<Vec<String>>,
392 #[serde(skip_serializing_if = "Option::is_none")]
393 pub allow_request_pattern: Option<Vec<String>>,
394 #[serde(skip_serializing_if = "Option::is_none")]
395 #[serde(rename = "setExtraHTTPHeaders")]
396 pub set_extra_http_headers: Option<Map<String, Value>>,
397 #[serde(skip_serializing_if = "Option::is_none")]
398 pub cookies: Option<Vec<Value>>,
399 #[serde(skip_serializing_if = "Option::is_none")]
400 pub set_javascript_enabled: Option<bool>,
401 #[serde(skip_serializing_if = "Option::is_none")]
402 pub emulate_media_type: Option<String>,
403 #[serde(skip_serializing_if = "Option::is_none")]
404 pub best_attempt: Option<bool>,
405 #[serde(skip_serializing_if = "Option::is_none")]
406 pub action_timeout: Option<u64>,
407 #[serde(skip_serializing_if = "Option::is_none")]
408 pub wait_for_timeout: Option<u64>,
409 #[serde(skip_serializing_if = "Option::is_none")]
410 pub add_script_tag: Option<Vec<Value>>,
411 #[serde(skip_serializing_if = "Option::is_none")]
412 pub add_style_tag: Option<Vec<Value>>,
413}
414
415#[derive(Debug, Clone, Deserialize)]
416#[serde(rename_all = "camelCase")]
417pub struct JsonExtractResult {
418 pub success: bool,
419 pub result: Option<Value>,
420 pub errors: Option<Vec<Value>>,
421}
422
423#[derive(Debug, Clone, Serialize, Default)]
426#[serde(rename_all = "camelCase")]
427pub struct LinksPayload {
428 #[serde(skip_serializing_if = "Option::is_none")]
429 pub url: Option<String>,
430 #[serde(skip_serializing_if = "Option::is_none")]
431 pub html: Option<String>,
432 #[serde(skip_serializing_if = "Option::is_none")]
433 pub visible_links_only: Option<bool>,
434 #[serde(skip_serializing_if = "Option::is_none")]
435 pub exclude_external_links: Option<bool>,
436 #[serde(skip_serializing_if = "Option::is_none")]
437 pub viewport: Option<Viewport>,
438 #[serde(skip_serializing_if = "Option::is_none")]
439 pub goto_options: Option<GotoOptions>,
440 #[serde(skip_serializing_if = "Option::is_none")]
441 pub wait_for_selector: Option<WaitForSelector>,
442 #[serde(skip_serializing_if = "Option::is_none")]
443 pub user_agent: Option<String>,
444 #[serde(skip_serializing_if = "Option::is_none")]
445 pub reject_resource_types: Option<Vec<String>>,
446 #[serde(skip_serializing_if = "Option::is_none")]
447 pub reject_request_pattern: Option<Vec<String>>,
448 #[serde(skip_serializing_if = "Option::is_none")]
449 pub allow_resource_types: Option<Vec<String>>,
450 #[serde(skip_serializing_if = "Option::is_none")]
451 pub allow_request_pattern: Option<Vec<String>>,
452 #[serde(skip_serializing_if = "Option::is_none")]
453 #[serde(rename = "setExtraHTTPHeaders")]
454 pub set_extra_http_headers: Option<Map<String, Value>>,
455 #[serde(skip_serializing_if = "Option::is_none")]
456 pub cookies: Option<Vec<Value>>,
457 #[serde(skip_serializing_if = "Option::is_none")]
458 pub set_javascript_enabled: Option<bool>,
459 #[serde(skip_serializing_if = "Option::is_none")]
460 pub emulate_media_type: Option<String>,
461 #[serde(skip_serializing_if = "Option::is_none")]
462 pub best_attempt: Option<bool>,
463 #[serde(skip_serializing_if = "Option::is_none")]
464 pub action_timeout: Option<u64>,
465 #[serde(skip_serializing_if = "Option::is_none")]
466 pub wait_for_timeout: Option<u64>,
467 #[serde(skip_serializing_if = "Option::is_none")]
468 pub add_script_tag: Option<Vec<Value>>,
469 #[serde(skip_serializing_if = "Option::is_none")]
470 pub add_style_tag: Option<Vec<Value>>,
471}
472
473#[derive(Debug, Clone, Deserialize)]
474#[serde(rename_all = "camelCase")]
475pub struct LinksResult {
476 pub success: bool,
477 pub result: Option<Vec<String>>,
478 pub errors: Option<Vec<Value>>,
479}
480
481#[derive(Debug, Clone, Serialize, Deserialize, Default)]
484#[serde(rename_all = "camelCase")]
485pub struct ScrapeElement {
486 pub selector: String,
487}
488
489#[derive(Debug, Clone, Serialize, Default)]
490#[serde(rename_all = "camelCase")]
491pub struct ScrapePayload {
492 #[serde(skip_serializing_if = "Option::is_none")]
493 pub url: Option<String>,
494 pub elements: Vec<ScrapeElement>,
495 #[serde(skip_serializing_if = "Option::is_none")]
496 pub viewport: Option<Viewport>,
497 #[serde(skip_serializing_if = "Option::is_none")]
498 pub goto_options: Option<GotoOptions>,
499 #[serde(skip_serializing_if = "Option::is_none")]
500 pub wait_for_selector: Option<WaitForSelector>,
501 #[serde(skip_serializing_if = "Option::is_none")]
502 pub user_agent: Option<String>,
503 #[serde(skip_serializing_if = "Option::is_none")]
504 pub reject_resource_types: Option<Vec<String>>,
505 #[serde(skip_serializing_if = "Option::is_none")]
506 pub reject_request_pattern: Option<Vec<String>>,
507 #[serde(skip_serializing_if = "Option::is_none")]
508 pub allow_resource_types: Option<Vec<String>>,
509 #[serde(skip_serializing_if = "Option::is_none")]
510 pub allow_request_pattern: Option<Vec<String>>,
511 #[serde(skip_serializing_if = "Option::is_none")]
512 #[serde(rename = "setExtraHTTPHeaders")]
513 pub set_extra_http_headers: Option<Map<String, Value>>,
514 #[serde(skip_serializing_if = "Option::is_none")]
515 pub cookies: Option<Vec<Value>>,
516 #[serde(skip_serializing_if = "Option::is_none")]
517 pub set_javascript_enabled: Option<bool>,
518 #[serde(skip_serializing_if = "Option::is_none")]
519 pub emulate_media_type: Option<String>,
520 #[serde(skip_serializing_if = "Option::is_none")]
521 pub best_attempt: Option<bool>,
522 #[serde(skip_serializing_if = "Option::is_none")]
523 pub action_timeout: Option<u64>,
524 #[serde(skip_serializing_if = "Option::is_none")]
525 pub wait_for_timeout: Option<u64>,
526 #[serde(skip_serializing_if = "Option::is_none")]
527 pub add_script_tag: Option<Vec<Value>>,
528 #[serde(skip_serializing_if = "Option::is_none")]
529 pub add_style_tag: Option<Vec<Value>>,
530}
531
532#[derive(Debug, Clone, Deserialize)]
533#[serde(rename_all = "camelCase")]
534pub struct ScrapeAttribute {
535 pub name: String,
536 pub value: String,
537}
538
539#[derive(Debug, Clone, Deserialize)]
540#[serde(rename_all = "camelCase")]
541pub struct ScrapeElementResult {
542 pub text: Option<String>,
543 pub html: Option<String>,
544 #[serde(default)]
545 pub attributes: Vec<ScrapeAttribute>,
546 pub height: Option<f64>,
547 pub width: Option<f64>,
548 pub top: Option<f64>,
549 pub left: Option<f64>,
550}
551
552#[derive(Debug, Clone, Deserialize)]
553#[serde(rename_all = "camelCase")]
554pub struct ScrapeSelectorResult {
555 pub selector: String,
556 pub results: Vec<ScrapeElementResult>,
557}
558
559#[derive(Debug, Clone, Deserialize)]
560#[serde(rename_all = "camelCase")]
561pub struct ScrapeResult {
562 pub success: bool,
563 pub result: Option<Vec<ScrapeSelectorResult>>,
564 pub errors: Option<Vec<Value>>,
565}
566
567fn deserialize_cursor<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
570where
571 D: Deserializer<'de>,
572{
573 let value: Option<Value> = Option::deserialize(deserializer)?;
574 Ok(value.and_then(|v| match v {
575 Value::String(s) if s.is_empty() => None,
576 Value::String(s) => Some(s),
577 Value::Number(n) => Some(n.to_string()),
578 Value::Null => None,
579 other => Some(other.to_string()),
580 }))
581}
582
583#[derive(Debug, Clone, Serialize, Deserialize)]
586pub struct CfApiResponse<T> {
587 pub success: bool,
588 pub result: Option<T>,
589 pub errors: Option<Vec<Value>>,
590 pub messages: Option<Vec<Value>>,
591}
592
593#[derive(Debug, Clone, Serialize, Deserialize)]
594#[serde(rename_all = "camelCase")]
595pub struct CrawlResult {
596 #[serde(default)]
597 pub status: String,
598 #[serde(default)]
599 pub records: Vec<CrawlRecord>,
600 #[serde(skip_serializing_if = "Option::is_none")]
601 pub total: Option<u64>,
602 #[serde(default, deserialize_with = "deserialize_cursor", skip_serializing_if = "Option::is_none")]
603 pub cursor: Option<String>,
604 #[serde(skip_serializing_if = "Option::is_none")]
605 pub browser_seconds_used: Option<f64>,
606 #[serde(flatten)]
607 pub extra: Map<String, Value>,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize)]
611#[serde(rename_all = "camelCase")]
612pub struct CrawlRecord {
613 #[serde(default)]
614 pub url: String,
615 #[serde(skip_serializing_if = "Option::is_none")]
616 pub status: Option<String>,
617 #[serde(skip_serializing_if = "Option::is_none")]
618 pub html: Option<String>,
619 #[serde(skip_serializing_if = "Option::is_none")]
620 pub markdown: Option<String>,
621 #[serde(skip_serializing_if = "Option::is_none")]
622 pub json: Option<Value>,
623 #[serde(flatten)]
624 pub extra: Map<String, Value>,
625}
626
627#[derive(Debug)]
628pub struct ScreenshotResult {
629 pub bytes: Vec<u8>,
630 pub content_type: String,
631}
632
633#[derive(Debug)]
634pub struct PdfResult {
635 pub bytes: Vec<u8>,
636 pub content_type: String,
637}
638
639#[cfg(test)]
640mod tests {
641 use super::*;
642
643 #[test]
644 fn crawl_payload_serializes_with_defaults() {
645 let payload = CrawlPayload {
646 url: "https://example.com".into(),
647 limit: Some(100),
648 ..Default::default()
649 };
650 let json = serde_json::to_value(&payload).unwrap();
651 assert_eq!(json["url"], "https://example.com");
652 assert_eq!(json["limit"], 100);
653 assert!(json.get("formats").is_none());
654 assert!(json.get("depth").is_none());
655 }
656
657 #[test]
658 fn screenshot_payload_nests_options_under_screenshot_options() {
659 let payload = ScreenshotPayload {
660 url: "https://example.com".into(),
661 screenshot_options: Some(ScreenshotOptions {
662 format: Some("jpeg".into()),
663 full_page: Some(true),
664 quality: Some(80),
665 omit_background: None,
666 }),
667 ..Default::default()
668 };
669 let json = serde_json::to_value(&payload).unwrap();
670 assert_eq!(json["screenshotOptions"]["type"], "jpeg");
671 assert_eq!(json["screenshotOptions"]["fullPage"], true);
672 assert_eq!(json["screenshotOptions"]["quality"], 80);
673 assert!(json.get("fullPage").is_none());
674 assert!(json.get("type").is_none());
675 assert!(json.get("quality").is_none());
676 }
677
678 #[test]
679 fn pdf_payload_skips_none_fields() {
680 let payload = PdfPayload {
681 url: Some("https://example.com".into()),
682 ..Default::default()
683 };
684 let json = serde_json::to_value(&payload).unwrap();
685 assert_eq!(json["url"], "https://example.com");
686 assert!(json.get("html").is_none());
687 assert!(json.get("pdfOptions").is_none());
688 }
689
690 #[test]
691 fn crawl_payload_with_extra_fields() {
692 let mut extra = Map::new();
693 extra.insert("customField".into(), Value::Bool(true));
694 let payload = CrawlPayload {
695 url: "https://example.com".into(),
696 extra: Some(extra),
697 ..Default::default()
698 };
699 let json = serde_json::to_value(&payload).unwrap();
700 assert_eq!(json["customField"], true);
701 }
702}