1use reqwest::Client;
6use std::time::Duration;
7
8pub type Result<T> = std::result::Result<T, Error>;
10
11#[derive(Debug, thiserror::Error)]
13pub enum Error {
14 #[error("HTTP error: {0}")]
15 Http(String),
16 #[error("Other error: {0}")]
17 Other(String),
18}
19
20impl From<reqwest::Error> for Error {
21 fn from(e: reqwest::Error) -> Self {
22 Error::Http(e.to_string())
23 }
24}
25
26pub async fn fetch_http(url: &str) -> Result<Vec<u8>> {
36 let client = Client::builder()
37 .timeout(Duration::from_secs(300)) .build()
39 .map_err(|e| Error::Other(format!("Failed to create HTTP client: {e}")))?;
40
41 let response = client
42 .get(url)
43 .send()
44 .await
45 .map_err(|e| Error::Http(format!("Failed to fetch URL {url}: {e}")))?;
46
47 if !response.status().is_success() {
48 return Err(Error::Http(format!(
49 "HTTP request failed with status: {}",
50 response.status()
51 )));
52 }
53
54 let bytes = response
55 .bytes()
56 .await
57 .map_err(|e| Error::Http(format!("Failed to read response body: {e}")))?;
58
59 Ok(bytes.to_vec())
60}
61
62pub fn fetch_http_sync(url: &str) -> Result<Vec<u8>> {
64 tokio::runtime::Runtime::new()
65 .map_err(|e| Error::Other(format!("Failed to create runtime: {e}")))?
66 .block_on(fetch_http(url))
67}
68
69pub fn is_http_url(s: &str) -> bool {
71 s.starts_with("http://") || s.starts_with("https://")
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn test_is_http_url() {
80 assert!(is_http_url("http://example.com/data.csv"));
81 assert!(is_http_url("https://example.com/data.csv"));
82 assert!(!is_http_url("file:///data.csv"));
83 assert!(!is_http_url("/path/to/file.csv"));
84 assert!(!is_http_url("data.csv"));
85 }
86}