mod hash;
pub use hash::{Hash, HashParseError};
use std::io;
use std::path::Path;
#[derive(Debug, Clone, Copy)]
pub struct Options {
pub verify_on_read: bool,
}
impl Default for Options {
fn default() -> Self {
Self { verify_on_read: true }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stat {
pub size: u64,
pub sha256: [u8; 32],
}
#[derive(Debug)]
pub enum PutError {
HashCollision,
Apotheca(apotheca::PutError),
}
#[derive(Debug)]
pub enum GetError {
NotFound,
IntegrityError,
Apotheca(apotheca::GetError),
}
#[derive(Debug)]
pub enum StatError {
NotFound,
Apotheca(apotheca::StatError),
}
impl std::fmt::Display for PutError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PutError::HashCollision => f.write_str(
"blake3 collision: distinct bytes hashed to an existing entry's name",
),
PutError::Apotheca(e) => write!(f, "{e}"),
}
}
}
impl std::fmt::Display for GetError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GetError::NotFound => f.write_str("not found"),
GetError::IntegrityError => {
f.write_str("integrity error: stored bytes do not match expected hash")
}
GetError::Apotheca(e) => write!(f, "{e}"),
}
}
}
impl std::fmt::Display for StatError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StatError::NotFound => f.write_str("not found"),
StatError::Apotheca(e) => write!(f, "{e}"),
}
}
}
impl std::error::Error for PutError {}
impl std::error::Error for GetError {}
impl std::error::Error for StatError {}
pub struct Pool {
inner: apotheca::Pool,
verify_on_read: bool,
}
impl Pool {
pub fn open<P: AsRef<Path>>(root: P) -> io::Result<Self> {
Self::open_with(root, Options::default())
}
pub fn open_with<P: AsRef<Path>>(root: P, opts: Options) -> io::Result<Self> {
let inner = apotheca::Pool::open(root)?;
Ok(Self { inner, verify_on_read: opts.verify_on_read })
}
pub fn from_apotheca(inner: apotheca::Pool, opts: Options) -> Self {
Self { inner, verify_on_read: opts.verify_on_read }
}
pub fn root(&self) -> &Path {
self.inner.root()
}
pub fn verify_on_read(&self) -> bool {
self.verify_on_read
}
pub fn put(&self, bytes: &[u8]) -> Result<Hash, PutError> {
let hash = Hash::of(bytes);
let name = hash_name(&hash);
let name = apotheca::Name::new(&name).expect("64-char hex is a valid apotheca name");
match self.inner.put(&name, bytes).map_err(PutError::Apotheca)? {
apotheca::PutOutcome::Ok => Ok(hash),
apotheca::PutOutcome::Collision => Err(PutError::HashCollision),
}
}
pub fn get(&self, hash: &Hash) -> Result<Vec<u8>, GetError> {
let name = hash_name(hash);
let name = apotheca::Name::new(&name).expect("64-char hex is a valid apotheca name");
let bytes = match self.inner.get(&name) {
Ok(b) => b,
Err(apotheca::GetError::NotFound) => return Err(GetError::NotFound),
Err(apotheca::GetError::IntegrityError) => return Err(GetError::IntegrityError),
Err(e) => return Err(GetError::Apotheca(e)),
};
if self.verify_on_read && Hash::of(&bytes) != *hash {
return Err(GetError::IntegrityError);
}
Ok(bytes)
}
pub fn stat(&self, hash: &Hash) -> Result<Stat, StatError> {
let name = hash_name(hash);
let name = apotheca::Name::new(&name).expect("64-char hex is a valid apotheca name");
match self.inner.stat(&name) {
Ok(meta) => Ok(Stat { size: meta.size, sha256: meta.sha256 }),
Err(apotheca::StatError::NotFound) => Err(StatError::NotFound),
Err(e) => Err(StatError::Apotheca(e)),
}
}
}
fn hash_name(hash: &Hash) -> [u8; 64] {
let mut buf = [0u8; 64];
hex::encode_to_slice(hash.as_bytes(), &mut buf).expect("32 bytes always fit in 64 hex");
buf
}