url_preview/
preview_generator.rs

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
use crate::fetcher::FetchResult;
use crate::{Cache, Fetcher, MetadataExtractor, Preview, PreviewError, PreviewGenerator};
use async_trait::async_trait;
use url::Url;

#[derive(Clone, Default)]
pub enum CacheStrategy {
    #[default]
    UseCache,
    NoCache,
    ForceUpdate,
}

#[derive(Clone)]
pub struct UrlPreviewGenerator {
    pub cache: Cache,
    pub cache_strategy: CacheStrategy,
    pub fetcher: Fetcher,
    extractor: MetadataExtractor,
}

impl UrlPreviewGenerator {
    pub fn new(cache_capacity: usize, cache_strategy: CacheStrategy) -> Self {
        Self {
            cache: Cache::new(cache_capacity),
            cache_strategy,
            fetcher: Fetcher::new(),
            extractor: MetadataExtractor::new(),
        }
    }

    pub fn new_with_fetcher(
        cache_capacity: usize,
        cache_strategy: CacheStrategy,
        fetcher: Fetcher,
    ) -> Self {
        Self {
            cache: Cache::new(cache_capacity),
            cache_strategy,
            fetcher,
            extractor: MetadataExtractor::new(),
        }
    }
}

// For Twitter url and Normal url
#[async_trait]
impl PreviewGenerator for UrlPreviewGenerator {
    async fn generate_preview(&self, url: &str) -> Result<Preview, PreviewError> {
        if let CacheStrategy::UseCache = self.cache_strategy {
            if let Some(cached) = self.cache.get(url).await {
                return Ok(cached);
            };
        };

        let _ = Url::parse(url)?;
        let content = self.fetcher.fetch(url).await?;

        let mut preview = match content {
            FetchResult::OEmbed(oembed) => self
                .extractor
                .extract_from_oembed(&oembed.html)
                .ok_or_else(|| {
                    PreviewError::ExtractError("Failed to extract from oEmbed".into())
                })?,
            FetchResult::Html(html) => self.extractor.extract(&html, url)?,
        };
        preview.url = url.to_string();
        if let CacheStrategy::UseCache = self.cache_strategy {
            self.cache.set(url.to_string(), preview.clone()).await;
        };
        Ok(preview)
    }
}