use std::{fmt, io};
use std::path::{Path, PathBuf};
use sha2::{Sha256, Digest};
use tokio::fs;
use tokio::io::AsyncReadExt;
#[derive(Debug, thiserror::Error)]
#[error("{kind} ({path}): {error}")]
pub struct Error {
path: PathBuf,
kind: Operation,
#[source]
error: io::Error,
}
impl Error {
pub const fn new(path: PathBuf, kind: Operation, error: io::Error) -> Self {
Self { path, kind, error }
}
}
#[derive(Debug)]
pub enum Operation {
Open,
Read,
}
impl fmt::Display for Operation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Open => write!(f, "failed to open file"),
Self::Read => write!(f, "failed to read file"),
}
}
}
pub(crate) async fn file_content(file_path: &Path) -> Result<Vec<u8>, Error> {
#[cfg(feature = "tracing")]
tracing::debug!("reading contents of {}", file_path.display());
let mut file = fs::File::open(file_path)
.await
.map_err(|error| Error::new(file_path.to_path_buf(), Operation::Open, error))?;
let mut content = Vec::new();
file.read_to_end(&mut content)
.await
.map_err(|error| Error::new(file_path.to_path_buf(), Operation::Read, error))?;
Ok(content)
}
pub(crate) fn checksum(content: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(&content);
format!("{:X}", hasher.finalize())
}
pub(crate) fn urlencode_path(path: &str) -> String {
path.split_terminator('/')
.map(|comp| urlencoding::encode(comp).to_string())
.collect::<Vec<String>>()
.join("/")
}