abi_loader/fetcher/
http.rs1use crate::fetcher::{FetchContext, FetchError, FetchResult, ImportFetcher};
6use crate::file::ImportSource;
7use std::time::Duration;
8
9pub struct HttpFetcher {
11 client: reqwest::blocking::Client,
12}
13
14impl HttpFetcher {
15 pub fn new() -> Result<Self, FetchError> {
17 Self::with_timeout(30)
18 }
19
20 pub fn with_timeout(timeout_seconds: u64) -> Result<Self, FetchError> {
22 let client = reqwest::blocking::Client::builder()
23 .timeout(Duration::from_secs(timeout_seconds))
24 .user_agent("thru-abi-loader/1.0")
25 .build()
26 .map_err(|e| FetchError::Http {
27 status: 0,
28 message: format!("Failed to create HTTP client: {}", e),
29 })?;
30
31 Ok(Self { client })
32 }
33}
34
35impl ImportFetcher for HttpFetcher {
36 fn handles(&self, source: &ImportSource) -> bool {
37 matches!(source, ImportSource::Http { .. })
38 }
39
40 fn fetch(&self, source: &ImportSource, _ctx: &FetchContext) -> Result<FetchResult, FetchError> {
41 let ImportSource::Http { url } = source else {
42 return Err(FetchError::UnsupportedSource(
43 "HttpFetcher only handles Http imports".to_string(),
44 ));
45 };
46
47 let response = self
49 .client
50 .get(url)
51 .send()
52 .map_err(|e| FetchError::Http {
53 status: 0,
54 message: format!("Request failed: {}", e),
55 })?;
56
57 let status = response.status();
59 if !status.is_success() {
60 return Err(FetchError::Http {
61 status: status.as_u16(),
62 message: format!("HTTP {} for {}", status, url),
63 });
64 }
65
66 let content = response.text().map_err(|e| FetchError::Http {
68 status: 0,
69 message: format!("Failed to read response body: {}", e),
70 })?;
71
72 Ok(FetchResult {
73 content,
74 canonical_location: url.clone(),
75 is_remote: true,
76 resolved_path: None,
77 })
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn test_http_fetcher_handles() {
87 let fetcher = HttpFetcher::new().unwrap();
88
89 let http_import = ImportSource::Http {
90 url: "https://example.com/types.abi.yaml".to_string(),
91 };
92 let path_import = ImportSource::Path {
93 path: "local.abi.yaml".to_string(),
94 };
95 let git_import = ImportSource::Git {
96 url: "https://github.com/test/repo".to_string(),
97 git_ref: "main".to_string(),
98 path: "abi.yaml".to_string(),
99 };
100
101 assert!(fetcher.handles(&http_import));
102 assert!(!fetcher.handles(&path_import));
103 assert!(!fetcher.handles(&git_import));
104 }
105
106 #[test]
108 #[ignore] fn test_http_fetcher_real_request() {
110 let fetcher = HttpFetcher::new().unwrap();
111 let source = ImportSource::Http {
112 url: "https://httpbin.org/get".to_string(),
113 };
114 let ctx = FetchContext {
115 base_path: None,
116 parent_is_remote: false,
117 include_dirs: vec![],
118 };
119
120 let result = fetcher.fetch(&source, &ctx);
121 assert!(result.is_ok());
122
123 let fetch_result = result.unwrap();
124 assert!(fetch_result.is_remote);
125 assert!(fetch_result.content.contains("httpbin"));
126 }
127}