use anyhow::{Context, Result};
use std::io::Read;
pub fn base_url() -> String {
std::env::var("SWAPDEX_UPSTREAM")
.ok()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "https://api.anthropic.com".to_string())
}
pub struct Upstream {
pub status: u16,
pub headers: Vec<(String, String)>,
pub reader: Box<dyn Read + Send>,
}
pub fn agent() -> ureq::Agent {
ureq::Agent::config_builder()
.http_status_as_error(false)
.build()
.into()
}
fn collect_headers<T>(resp: &ureq::http::Response<T>) -> Vec<(String, String)> {
resp.headers()
.iter()
.filter_map(|(n, v)| {
v.to_str()
.ok()
.map(|s| (n.as_str().to_string(), s.to_string()))
})
.collect()
}
pub fn forward(
agent: &ureq::Agent,
method: &str,
url: &str,
headers: &[(String, String)],
body: &[u8],
) -> Result<Upstream> {
let bodyless = matches!(
method.to_ascii_uppercase().as_str(),
"GET" | "HEAD" | "DELETE" | "OPTIONS"
);
if bodyless {
let mut rb = match method.to_ascii_uppercase().as_str() {
"HEAD" => agent.head(url),
"DELETE" => agent.delete(url),
"OPTIONS" => agent.options(url),
_ => agent.get(url),
};
for (k, v) in headers {
rb = rb.header(k.as_str(), v.as_str());
}
let resp = rb.call().context("upstream request failed")?;
let status = resp.status().as_u16();
let headers = collect_headers(&resp);
return Ok(Upstream {
status,
headers,
reader: Box::new(resp.into_body().into_reader()),
});
}
let mut rb = match method.to_ascii_uppercase().as_str() {
"PUT" => agent.put(url),
"PATCH" => agent.patch(url),
_ => agent.post(url),
};
for (k, v) in headers {
rb = rb.header(k.as_str(), v.as_str());
}
let resp = rb.send(body).context("upstream request failed")?;
let status = resp.status().as_u16();
let headers = collect_headers(&resp);
Ok(Upstream {
status,
headers,
reader: Box::new(resp.into_body().into_reader()),
})
}