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}