use std::sync::Arc;
use fraiseql_error::Result;
use crate::host::{
HttpResponse,
live::{HostContextConfig, http_validator},
};
pub async fn perform(
config: &HostContextConfig,
client: Option<&Arc<reqwest::Client>>,
method: &str,
url: &str,
headers: &[(String, String)],
body: Option<&[u8]>,
) -> Result<HttpResponse> {
let http_config = http_validator::HttpClientConfig {
allowed_domains: config.allowed_domains.clone(),
max_response_bytes: config.max_http_response_bytes,
connect_timeout_ms: config.http_connect_timeout_ms,
read_timeout_ms: config.http_read_timeout_ms,
};
http_validator::validate_outbound_url(url, &http_config).await?;
let client = if let Some(client) = client {
Arc::clone(client)
} else {
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.connect_timeout(std::time::Duration::from_millis(config.http_connect_timeout_ms))
.timeout(std::time::Duration::from_millis(config.http_read_timeout_ms))
.build()
.map_err(|e| fraiseql_error::FraiseQLError::Internal {
message: format!("failed to create HTTP client: {}", e),
source: None,
})?;
Arc::new(client)
};
let mut req = match method.to_uppercase().as_str() {
"GET" => client.get(url),
"POST" => client.post(url),
"PUT" => client.put(url),
"PATCH" => client.patch(url),
"DELETE" => client.delete(url),
"HEAD" => client.head(url),
_ => {
return Err(fraiseql_error::FraiseQLError::Validation {
message: format!("unsupported HTTP method: {}", method),
path: None,
});
},
};
for (key, value) in headers {
req = req.header(key.clone(), value.clone());
}
if let Some(body_bytes) = body {
req = req.body(body_bytes.to_vec());
}
let response = req.send().await.map_err(|e| fraiseql_error::FraiseQLError::Internal {
message: format!("HTTP request failed: {}", e),
source: None,
})?;
let status = response.status().as_u16();
let response_headers: Vec<(String, String)> = response
.headers()
.iter()
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body_bytes =
response.bytes().await.map_err(|e| fraiseql_error::FraiseQLError::Internal {
message: format!("failed to read response body: {}", e),
source: None,
})?;
if body_bytes.len() > config.max_http_response_bytes {
return Err(fraiseql_error::FraiseQLError::Validation {
message: format!(
"response body too large: {} > {}",
body_bytes.len(),
config.max_http_response_bytes
),
path: None,
});
}
Ok(crate::host::HttpResponse {
status,
headers: response_headers,
body: body_bytes.to_vec(),
})
}