use std::time::Duration;
use crate::error::CompileError;
#[derive(Debug)]
pub struct DownloadResult {
pub wasm_bytes: Vec<u8>,
pub plugin_toml: Option<String>,
}
fn build_client() -> Result<reqwest::blocking::Client, CompileError> {
reqwest::blocking::Client::builder()
.connect_timeout(Duration::from_secs(30))
.timeout(Duration::from_secs(120))
.user_agent(format!("barbacane-compiler/{}", env!("CARGO_PKG_VERSION")))
.build()
.map_err(|e| CompileError::PluginResolution(format!("failed to build HTTP client: {e}")))
}
fn derive_plugin_toml_urls(wasm_url: &str) -> Vec<String> {
let mut urls = Vec::new();
if let Some(stripped) = wasm_url.strip_suffix(".wasm") {
urls.push(format!("{stripped}.plugin.toml"));
}
if let Some(last_slash) = wasm_url.rfind('/') {
urls.push(format!("{}/plugin.toml", &wasm_url[..last_slash]));
}
urls
}
pub fn download_plugin(url: &str) -> Result<DownloadResult, CompileError> {
if !url.starts_with("https://") {
return Err(CompileError::PluginResolution(format!(
"plugin URL must use HTTPS: {url}"
)));
}
let client = build_client()?;
tracing::info!(url, "downloading remote plugin");
let response = client.get(url).send().map_err(|e| {
CompileError::PluginResolution(format!("failed to download plugin from {url}: {e}"))
})?;
if !response.status().is_success() {
return Err(CompileError::PluginResolution(format!(
"failed to download plugin from {url}: HTTP {}",
response.status()
)));
}
let wasm_bytes = response.bytes().map_err(|e| {
CompileError::PluginResolution(format!(
"failed to read plugin response body from {url}: {e}"
))
})?;
let plugin_toml = derive_plugin_toml_urls(url)
.into_iter()
.find_map(|toml_url| {
tracing::debug!(url = %toml_url, "attempting to fetch plugin.toml");
let resp = client.get(&toml_url).send().ok()?;
if resp.status().is_success() {
resp.text().ok()
} else {
None
}
});
Ok(DownloadResult {
wasm_bytes: wasm_bytes.to_vec(),
plugin_toml,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reject_http_url() {
let result = download_plugin("http://example.com/plugin.wasm");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("HTTPS"));
}
#[test]
fn derive_plugin_toml_urls_from_wasm() {
let urls =
derive_plugin_toml_urls("https://example.com/plugins/jwt-auth/1.0.0/jwt-auth.wasm");
assert_eq!(urls.len(), 2);
assert_eq!(
urls[0],
"https://example.com/plugins/jwt-auth/1.0.0/jwt-auth.plugin.toml"
);
assert_eq!(
urls[1],
"https://example.com/plugins/jwt-auth/1.0.0/plugin.toml"
);
}
#[test]
fn derive_plugin_toml_urls_github_release() {
let urls = derive_plugin_toml_urls(
"https://github.com/barbacane-dev/barbacane/releases/download/v0.5.0/jwt-auth.wasm",
);
assert_eq!(
urls[0],
"https://github.com/barbacane-dev/barbacane/releases/download/v0.5.0/jwt-auth.plugin.toml"
);
}
}