use curl::easy::{Easy2, Handler, WriteError};
use log::debug;
use url::Url;
use crate::errors::*;
use crate::lib_provider::Provider;
pub struct HttpProvider;
struct Collector(Vec<u8>);
impl Handler for Collector {
fn write(&mut self, data: &[u8]) -> std::result::Result<usize, WriteError> {
self.0.extend_from_slice(data);
Ok(data.len())
}
}
impl Provider for HttpProvider {
fn resolve_url(&self, url: &Url, default_filename: &str, extensions: &[&str]) -> Result<Url> {
if Self::resource_exists(url).is_ok() {
return Ok(url.clone());
}
if let Ok(found) = Self::resource_by_extensions(url, extensions) {
return Ok(found);
}
let default_filename_url = Url::parse(&format!("{}/{}", url.to_string(), default_filename))
.chain_err(|| "Could not append default_filename to url")?;
if let Ok(found) = Self::resource_by_extensions(&default_filename_url, extensions) {
return Ok(found);
}
let mut segments = url.path_segments().ok_or("Could not get path segments")?;
let file_name = segments
.next_back()
.ok_or("Could not get last path segment")?;
let filename_url = Url::parse(&format!("{}/{}", url.to_string(), file_name))
.chain_err(|| "Could not append filename after directory in Url")?;
if let Ok(found) = Self::resource_by_extensions(&filename_url, extensions) {
return Ok(found);
}
bail!("Could not resolve the Url: '{}'", url)
}
fn get_contents(&self, url: &Url) -> Result<Vec<u8>> {
let mut easy = Easy2::new(Collector(Vec::new()));
easy.get(true).chain_err(|| "Could not set GET operation")?;
easy.url(url.as_str()).chain_err(|| "Could not set Url")?;
easy.perform().chain_err(|| "Could not perform operation")?;
match easy
.response_code()
.chain_err(|| "Could not get status code")?
{
200..=299 => {
let contents = easy.get_ref();
Ok(contents.0.clone())
}
error_code => bail!("Response code: {} from '{}'", error_code, url.as_str()),
}
}
}
impl HttpProvider {
fn resource_exists(url: &Url) -> Result<()> {
debug!("Looking for resource '{}'", url);
let mut easy = Easy2::new(Collector(Vec::new()));
easy.nobody(true)
.chain_err(|| "Could not set NO_BODY on operation")?;
easy.url(url.as_str())
.chain_err(|| "Could not set Url on operation")?;
easy.perform().chain_err(|| "Could not perform operation")?;
match easy
.response_code()
.chain_err(|| "Could not get status code")?
{
200..=299 => Ok(()),
error_code => bail!("Response code: {} from '{}'", error_code, url.as_str()),
}
}
fn resource_by_extensions(resource: &Url, extensions: &[&str]) -> Result<Url> {
for extension in extensions {
let resource_with_extension =
Url::parse(&format!("{}.{}", resource.as_str(), extension))
.chain_err(|| "Could not parse Url with extension added")?;
if Self::resource_exists(&resource_with_extension).is_ok() {
return Ok(resource_with_extension);
}
}
bail!(
"No resources found at path '{}' with any of these extensions '{:?}'",
resource,
extensions
)
}
}
#[cfg(test)]
mod test {
use url::Url;
use crate::lib_provider::Provider;
use super::HttpProvider;
#[test]
fn resolve() {
let provider = HttpProvider {};
let folder_url = Url::parse("https://raw.githubusercontent.com/andrewdavidmackenzie/flow/master/samples/hello-world/context.toml")
.expect("Could not form Url");
let full_url = provider
.resolve_url(&folder_url, "context", &[&"toml"])
.expect("Could not resolve url");
assert_eq!(folder_url.as_str(), full_url.as_str());
}
#[test]
fn resolve_default() {
let provider = HttpProvider {};
let folder_url = Url::parse("https://raw.githubusercontent.com/andrewdavidmackenzie/flow/master/samples/hello-world")
.expect("Could not form Url");
let expected_url = Url::parse("https://raw.githubusercontent.com/andrewdavidmackenzie/flow/master/samples/hello-world/context.toml")
.expect("Could not form Url");
let full_url = provider
.resolve_url(&folder_url, "context", &[&"toml"])
.expect("Could not resolve url");
assert_eq!(full_url.as_str(), expected_url.as_str());
}
#[test]
fn get_github_sample() {
let provider: &dyn Provider = &HttpProvider;
let url = Url::parse("https://raw.githubusercontent.com/andrewdavidmackenzie/flow/master/samples/hello-world/context.toml")
.expect("Could not form Url");
let found = provider.get_contents(&url);
assert!(found.is_ok());
}
#[test]
fn online_get_contents_file_not_found() {
let provider: &dyn Provider = &HttpProvider;
let url = Url::parse("http://foo.com/no-such-file").expect("Could not form Url");
let not_found = provider.get_contents(&url);
assert!(not_found.is_err());
}
}