use std::borrow::Cow;
use std::fs;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use anyhow::Context;
use anyhow::anyhow;
use anyhow::bail;
use thiserror::Error;
use url::Url;
#[derive(Error, Debug)]
pub enum Error {
#[error("invalid URL: {0}")]
ParseUrl(url::ParseError),
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, Debug)]
pub enum Contents {
Url(Url),
Literal(Vec<u8>),
Path(PathBuf),
}
impl Contents {
pub fn url_from_str(url: impl AsRef<str>) -> Result<Self> {
url.as_ref().parse().map(Self::Url).map_err(Error::ParseUrl)
}
pub fn one_hot(self) -> anyhow::Result<(Option<Url>, Option<Vec<u8>>)> {
match self {
Self::Url(url) => Ok((Some(url), None)),
Self::Literal(value) => Ok((None, Some(value))),
Self::Path(path) => Ok((
None,
Some(fs::read(&path).with_context(|| {
format!("failed to read file `{path}`", path = path.display())
})?),
)),
}
}
pub async fn fetch(&self, temp_dir: &Path) -> anyhow::Result<Cow<'_, Path>> {
let contents: Cow<'_, [u8]> = match self {
Self::Url(url) => {
match url.scheme() {
"file" => {
let path = url.to_file_path().map_err(|_| {
anyhow!(
"URL `{url}` has a file scheme but cannot be represented as a \
file path"
)
})?;
return Ok(path.into());
}
"http" | "https" => bail!("support for HTTP URLs is not yet implemented"),
"s3" => bail!("support for S3 URLs is not yet implemented"),
"az" => bail!("support for Azure Storage URLs is not yet implemented"),
"gs" => bail!("support for Google Cloud Storage URLs is not yet implemented"),
scheme => bail!("URL has unsupported scheme `{scheme}`"),
}
}
Self::Literal(bytes) => bytes.into(),
Self::Path(path) => return Ok(path.into()),
};
let mut file = tempfile::NamedTempFile::new_in(temp_dir).with_context(|| {
format!(
"failed to create temporary input file in `{temp_dir}`",
temp_dir = temp_dir.display()
)
})?;
file.write(&contents).with_context(|| {
format!(
"failed to write input file contents to `{path}`",
path = file.path().display()
)
})?;
let (_, path) = file.keep().context("failed to persist temporary file")?;
Ok(path.into())
}
}