pagesift_engine/engine/
hooked.rs1use pagesift_api::types::{Document, ScrapeOptions};
5use pagesift_api::{ApiError, ErrorCode};
6use pagesift_hooks::{Request, Verdict};
7
8use super::{Scraper, Source};
9
10impl Scraper {
11 pub(super) async fn scrape_inner(
12 &self,
13 source: Source<'_>,
14 opts: &ScrapeOptions,
15 ) -> Result<Document, ApiError> {
16 if self.hooks.is_empty() {
17 return self.scrape_plain(source, opts).await;
18 }
19 let (url, mode) = match source {
20 Source::Url(url, mode) => (url.to_string(), mode),
21 Source::Upload(_) => {
22 let doc = Box::pin(self.scrape_plain(source, opts)).await?;
23 let name = doc.metadata.source_url.clone().unwrap_or_default();
24 return Ok(self.hooks.run_document(&name, doc));
25 }
26 };
27 let req = self
28 .hooks
29 .run_request(Request {
30 url,
31 options: opts.clone(),
32 })
33 .map_err(|why| {
34 ApiError::new(
35 ErrorCode::UnsupportedOption,
36 format!("skipped by a request hook: {why}"),
37 )
38 })?;
39 let mut doc =
40 Box::pin(self.scrape_plain(Source::Url(&req.url, mode), &req.options)).await?;
41 match self.hooks.run_response(&req.url, &doc) {
42 Verdict::Keep => {}
43 Verdict::Retry => {
45 doc =
46 Box::pin(self.scrape_plain(Source::Url(&req.url, mode), &req.options)).await?;
47 }
48 Verdict::Drop(why) => {
49 return Err(ApiError::new(
50 ErrorCode::UpstreamError,
51 format!("dropped by a response hook: {why}"),
52 ));
53 }
54 }
55 Ok(self.hooks.run_document(&req.url, doc))
56 }
57}
58
59pub struct ScraperBuilder {
61 config: super::ScraperConfig,
62}
63
64impl Scraper {
65 pub fn builder(config: super::ScraperConfig) -> ScraperBuilder {
66 ScraperBuilder { config }
67 }
68
69 pub fn hooks(&self) -> &pagesift_hooks::Hooks {
71 &self.hooks
72 }
73}
74
75impl ScraperBuilder {
76 pub fn hook(mut self, hooks: &pagesift_hooks::Hooks) -> Self {
78 self.config.hooks.extend(hooks);
79 self
80 }
81
82 pub fn build(self) -> Result<Scraper, super::ScraperError> {
83 Scraper::new(self.config)
84 }
85}