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.client.get(url).send().map_err(|e| FetchError::Http {
49 status: 0,
50 message: format!("Request failed: {}", e),
51 })?;
52
53 let status = response.status();
55 if !status.is_success() {
56 return Err(FetchError::Http {
57 status: status.as_u16(),
58 message: format!("HTTP {} for {}", status, url),
59 });
60 }
61
62 let content = response.text().map_err(|e| FetchError::Http {
64 status: 0,
65 message: format!("Failed to read response body: {}", e),
66 })?;
67
68 Ok(FetchResult {
69 content,
70 canonical_location: url.clone(),
71 is_remote: true,
72 resolved_path: None,
73 })
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn test_http_fetcher_handles() {
83 let fetcher = HttpFetcher::new().unwrap();
84
85 let http_import = ImportSource::Http {
86 url: "https://example.com/types.abi.yaml".to_string(),
87 };
88 let path_import = ImportSource::Path {
89 path: "local.abi.yaml".to_string(),
90 };
91 let git_import = ImportSource::Git {
92 url: "https://github.com/test/repo".to_string(),
93 git_ref: "main".to_string(),
94 path: "abi.yaml".to_string(),
95 };
96
97 assert!(fetcher.handles(&http_import));
98 assert!(!fetcher.handles(&path_import));
99 assert!(!fetcher.handles(&git_import));
100 }
101
102 #[test]
104 #[ignore] fn test_http_fetcher_real_request() {
106 let fetcher = HttpFetcher::new().unwrap();
107 let source = ImportSource::Http {
108 url: "https://httpbin.org/get".to_string(),
109 };
110 let ctx = FetchContext {
111 base_path: None,
112 parent_is_remote: false,
113 include_dirs: vec![],
114 };
115
116 let result = fetcher.fetch(&source, &ctx);
117 assert!(result.is_ok());
118
119 let fetch_result = result.unwrap();
120 assert!(fetch_result.is_remote);
121 assert!(fetch_result.content.contains("httpbin"));
122 }
123}