Skip to main content

eth_avatars/
client.rs

1use std::sync::Arc;
2
3use crate::{AnyFetcher, FetchError, Fetcher, Resource, resource::Dyncoder};
4
5pub struct Client {
6    fetchers: Vec<Arc<dyn AnyFetcher>>,
7    max_hops: usize,
8}
9
10const DEFAULT_MAX_HOPS: usize = 5;
11
12impl Default for Client {
13    fn default() -> Self {
14        Self {
15            fetchers: Vec::new(),
16            max_hops: DEFAULT_MAX_HOPS,
17        }
18    }
19}
20
21impl Client {
22    pub fn with_fetcher(mut self, fetcher: impl Fetcher + 'static) -> Self {
23        self.fetchers.push(Arc::new(fetcher));
24        self
25    }
26
27    pub fn with_max_hops(mut self, max_hops: usize) -> Self {
28        self.max_hops = max_hops;
29        self
30    }
31
32    pub async fn fetch(&self, resource: Resource) -> Result<Vec<u8>, FetchError> {
33        let mut current = resource;
34        let mut decoders: Vec<Dyncoder> = Vec::new();
35        let mut hops = 0;
36
37        loop {
38            current = match current {
39                Resource::Decode { source, decoder } => {
40                    decoders.push(decoder);
41                    *source
42                }
43                Resource::Raw(bytes) => match decoders.pop() {
44                    None => return Ok(bytes),
45                    Some(decoder) => decoder.decode(bytes)?,
46                },
47                pending if hops < self.max_hops => {
48                    hops += 1;
49                    self.step(&pending).await?
50                }
51                _ => {
52                    return Err(FetchError::TooManyHops {
53                        hops: self.max_hops,
54                    });
55                }
56            };
57        }
58    }
59
60    async fn step(&self, resource: &Resource) -> Result<Resource, FetchError> {
61        let mut failure = None;
62
63        for fetcher in &self.fetchers {
64            match fetcher.fetch_any(resource).await {
65                None => continue,
66                Some(Ok(fetched)) => return Ok(fetched),
67                Some(Err(error)) => {
68                    tracing::warn!(%error, "fetcher failed");
69                    failure = Some(error);
70                }
71            }
72        }
73
74        Err(failure.unwrap_or(FetchError::Unsupported))
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use crate::{Client, modules::ipfs::IpfsGateway, resource::Resource};
81
82    #[cfg(feature = "reqwest")]
83    #[tokio::test]
84    async fn client_eip155_to_bytes() {
85        use crate::{
86            modules::{ethereum::resolver::EthereumResolver, http::HttpFetcher},
87            utils::test::get_test_provider,
88        };
89
90        let mainnet_provider = get_test_provider().await;
91
92        let client = Client::default()
93            .with_fetcher(HttpFetcher::default())
94            .with_fetcher(IpfsGateway::new("https://ipfs.io/"))
95            .with_fetcher(EthereumResolver::new(1, mainnet_provider));
96
97        let input: Resource = "eip155:1/erc1155:0x495f947276749ce646f68ac8c248420045cb7b5e/109791375735522898048150917964456965919994596086232976516654423066184641413121"
98            .parse()
99            .unwrap();
100
101        let result = client.fetch(input).await.unwrap();
102
103        assert_eq!(result.len(), 559490);
104    }
105
106    #[tokio::test]
107    async fn client_ipfs_to_bytes() {
108        use crate::modules::http::HttpFetcher;
109
110        let client = Client::default()
111            .with_fetcher(HttpFetcher::default())
112            .with_fetcher(IpfsGateway::new("https://ipfs.io/"));
113
114        let input: Resource = "ipfs://bafkreifnrjhkl7ccr2ifwn2n7ap6dh2way25a6w5x2szegvj5pt4b5nvfu"
115            .parse()
116            .unwrap();
117
118        let result = client.fetch(input).await.unwrap();
119
120        assert_eq!(result.len(), 26914);
121    }
122}