Skip to main content

ralph_workflow/io/
http_fetch.rs

1use std::time::Duration;
2
3fn build_agent() -> ureq::Agent {
4    ureq::Agent::new_with_config(
5        ureq::config::Config::builder()
6            .timeout_global(Some(Duration::from_secs(10)))
7            .http_status_as_error(false)
8            .build(),
9    )
10}
11
12fn check_http_status(status: ureq::http::StatusCode, body: &str) -> Result<(), String> {
13    if status.is_client_error() || status.is_server_error() {
14        if body.is_empty() {
15            return Err(format!("status {}", status.as_u16()));
16        }
17        return Err(format!("status {}: {}", status.as_u16(), body));
18    }
19    Ok(())
20}
21
22pub fn fetch_url(url: &str) -> Result<String, String> {
23    let agent = build_agent();
24    let mut response = agent
25        .get(url)
26        .call()
27        .map_err(|e: ureq::Error| e.to_string())?;
28
29    let status = response.status();
30    let body = response
31        .body_mut()
32        .read_to_string()
33        .map_err(|e| e.to_string())?;
34
35    check_http_status(status, &body)?;
36    Ok(body)
37}
38
39pub trait HttpFetcher: Send + Sync {
40    fn fetch(&self, url: &str) -> Result<String, String>;
41}
42
43#[derive(Debug, Clone, Default)]
44pub struct RealHttpFetcher;
45
46impl HttpFetcher for RealHttpFetcher {
47    fn fetch(&self, url: &str) -> Result<String, String> {
48        fetch_url(url)
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use mockito::Server;
56
57    #[test]
58    fn fetch_url_returns_mocked_body() {
59        let mut server = Server::new();
60        let _mock = server
61            .mock("GET", "/test")
62            .with_status(200)
63            .with_body("test content")
64            .create();
65
66        let url = format!("{}/test", server.url());
67        let result = fetch_url(&url).unwrap();
68
69        assert_eq!(result, "test content");
70    }
71
72    #[test]
73    fn fetch_url_propagates_error_status() {
74        let mut server = Server::new();
75        let _mock = server
76            .mock("GET", "/error")
77            .with_status(500)
78            .with_body("server error")
79            .create();
80
81        let url = format!("{}/error", server.url());
82        let error = fetch_url(&url).unwrap_err();
83
84        assert!(error.contains("500"));
85        assert!(error.contains("server error"));
86    }
87}