use async_trait::async_trait; use domain::{FetchError, TabFetcherPort, TabSource}; pub struct UgTabFetcher { client: reqwest::Client, } impl UgTabFetcher { pub fn new() -> Self { Self { client: reqwest::Client::new() } } } impl Default for UgTabFetcher { fn default() -> Self { Self::new() } } #[async_trait] impl TabFetcherPort for UgTabFetcher { async fn fetch(&self, source: TabSource) -> Result { match source { TabSource::File(path) => { Ok(tokio::fs::read_to_string(&path).await?) } TabSource::Url(url) => { let resp = self.client.get(&url).send().await .map_err(|e| FetchError::Network(e.to_string()))?; let content_type = resp.headers() .get(reqwest::header::CONTENT_TYPE) .and_then(|v| v.to_str().ok()) .unwrap_or(""); if !content_type.contains("text/html") { return Err(FetchError::InvalidContentType); } resp.text().await.map_err(|e| FetchError::Network(e.to_string())) } } } } #[cfg(test)] mod tests { use super::*; use domain::TabSource; use std::path::PathBuf; #[tokio::test] async fn fetch_local_file() { let fetcher = UgTabFetcher::new(); let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .parent().unwrap().parent().unwrap().parent().unwrap() .join("samples/A DROP IN THE OCEAN.html"); let html = fetcher.fetch(TabSource::File(path)).await.unwrap(); assert!(html.contains("[Chorus]")); assert!(html.contains("data-name=\"Em\"")); } }