ralph-workflow 0.7.18

PROMPT-driven multi-agent orchestrator for git repos
Documentation
use std::{io, time::Duration};

use thiserror::Error;

#[derive(Debug, Error)]
pub enum CatalogFetchError {
    #[error("request failed: {0}")]
    Request(#[from] ureq::Error),
    #[error("failed to read catalog body: {0}")]
    ReadBody(#[from] io::Error),
    #[error("{message}")]
    HttpStatus {
        status: u16,
        body: Option<String>,
        message: String,
    },
}

impl CatalogFetchError {
    fn http_status(status: u16, body: String) -> Self {
        let body_for_message = body.clone();
        let message = if body.is_empty() {
            format!("status {}", status)
        } else {
            format!("status {}: {}", status, body_for_message)
        };

        let body = if body.is_empty() { None } else { Some(body) };

        CatalogFetchError::HttpStatus {
            status,
            body,
            message,
        }
    }
}

pub fn fetch_api_catalog_json(url: &str) -> Result<String, CatalogFetchError> {
    let agent = ureq::Agent::new_with_config(
        ureq::config::Config::builder()
            .timeout_global(Some(Duration::from_secs(10)))
            .http_status_as_error(false)
            .build(),
    );

    let mut response = agent.get(url).call().map_err(CatalogFetchError::Request)?;

    let status = response.status();
    let body = response
        .body_mut()
        .read_to_string()
        .map_err(CatalogFetchError::ReadBody)?;

    if status.is_client_error() || status.is_server_error() {
        return Err(CatalogFetchError::http_status(status.as_u16(), body));
    }

    Ok(body)
}

#[cfg(test)]
mod tests {
    use super::*;
    use mockito::Server;

    #[test]
    fn fetch_api_catalog_json_returns_mocked_body() {
        let mut server = Server::new();
        let _mock = server
            .mock("GET", "/catalog")
            .with_status(200)
            .with_body("{\"ok\":true}")
            .create();

        let url = format!("{}/catalog", server.url());
        let result = fetch_api_catalog_json(&url).unwrap();

        assert_eq!(result, "{\"ok\":true}");
    }

    #[test]
    fn fetch_api_catalog_json_propagates_errors() {
        let mut server = Server::new();
        let _mock = server
            .mock("GET", "/catalog")
            .with_status(500)
            .with_body("internal")
            .create();

        let url = format!("{}/catalog", server.url());
        let error = fetch_api_catalog_json(&url).unwrap_err();

        match error {
            CatalogFetchError::HttpStatus {
                status,
                body,
                message,
            } => {
                assert_eq!(status, 500);
                assert_eq!(body.as_deref(), Some("internal"));
                assert!(message.contains("500"));
                assert!(message.contains("internal"));
            }
            other => panic!("unexpected error variant: {other:?}"),
        }
    }
}