url_preview/
preview_generator.rs

1use crate::fetcher::FetchResult;
2#[cfg(feature = "cache")]
3use crate::Cache;
4use crate::{Fetcher, MetadataExtractor, Preview, PreviewError, PreviewGenerator};
5use async_trait::async_trait;
6use url::Url;
7
8#[derive(Clone, Copy, Default)]
9pub enum CacheStrategy {
10    #[default]
11    UseCache,
12    NoCache,
13    ForceUpdate,
14}
15
16#[derive(Clone)]
17pub struct UrlPreviewGenerator {
18    #[cfg(feature = "cache")]
19    pub cache: Cache,
20    pub cache_strategy: CacheStrategy,
21    pub fetcher: Fetcher,
22    extractor: MetadataExtractor,
23}
24
25impl UrlPreviewGenerator {
26    pub fn new(
27        #[allow(unused_variables)] cache_capacity: usize,
28        cache_strategy: CacheStrategy,
29    ) -> Self {
30        Self {
31            #[cfg(feature = "cache")]
32            cache: Cache::new(cache_capacity),
33            cache_strategy,
34            fetcher: Fetcher::new(),
35            extractor: MetadataExtractor::new(),
36        }
37    }
38
39    pub fn new_with_fetcher(
40        #[allow(unused_variables)] cache_capacity: usize,
41        cache_strategy: CacheStrategy,
42        fetcher: Fetcher,
43    ) -> Self {
44        Self {
45            #[cfg(feature = "cache")]
46            cache: Cache::new(cache_capacity),
47            cache_strategy,
48            fetcher,
49            extractor: MetadataExtractor::new(),
50        }
51    }
52}
53
54// For Twitter url and Normal url
55#[async_trait]
56impl PreviewGenerator for UrlPreviewGenerator {
57    async fn generate_preview(&self, url: &str) -> Result<Preview, PreviewError> {
58        #[cfg(feature = "cache")]
59        if let CacheStrategy::UseCache = self.cache_strategy {
60            if let Some(cached) = self.cache.get(url).await {
61                return Ok(cached);
62            };
63        };
64
65        let _ = Url::parse(url)?;
66        let content = self.fetcher.fetch(url).await?;
67
68        let mut preview = match content {
69            FetchResult::OEmbed(oembed) => self
70                .extractor
71                .extract_from_oembed(&oembed.html)
72                .ok_or_else(|| {
73                    PreviewError::ExtractError("Failed to extract from oEmbed".into())
74                })?,
75            FetchResult::Html(html) => self.extractor.extract(&html, url)?,
76        };
77        preview.url = url.to_string();
78        #[cfg(feature = "cache")]
79        if let CacheStrategy::UseCache = self.cache_strategy {
80            self.cache.set(url.to_string(), preview.clone()).await;
81        };
82        Ok(preview)
83    }
84}