mod hash;
pub use hash::{Hash, HashParseError};
pub use apotheca::{Digest256, GetPinaxError, Name, NameError, SetPinaxError, SetPinaxOutcome};
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 DepositError {
HashCollision,
Apotheca(apotheca::DepositError),
}
#[derive(Debug)]
pub enum GetError {
NotFound,
IntegrityError,
Apotheca(apotheca::GetError),
}
#[derive(Debug)]
pub enum StatError {
NotFound,
Apotheca(apotheca::StatError),
}
impl std::fmt::Display for DepositError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DepositError::HashCollision => f.write_str(
"blake3 collision: distinct bytes hashed to an existing depositum's name",
),
DepositError::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 DepositError {}
impl std::error::Error for GetError {}
impl std::error::Error for StatError {}
pub struct Cella {
inner: apotheca::Cella,
verify_on_read: bool,
}
impl Cella {
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::Cella::open(root)?;
Ok(Self {
inner,
verify_on_read: opts.verify_on_read,
})
}
pub fn from_apotheca(inner: apotheca::Cella, 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 deposit(&self, bytes: &[u8]) -> Result<Hash, DepositError> {
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
.deposit(&name, bytes)
.map_err(DepositError::Apotheca)?
{
apotheca::DepositOutcome::Ok => Ok(hash),
apotheca::DepositOutcome::Collision => Err(DepositError::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)),
}
}
pub fn get_pinax(&self, name: &Name<'_>) -> Result<Vec<u8>, GetPinaxError> {
self.inner.get_pinax(name)
}
pub fn set_pinax(
&self,
name: &Name<'_>,
bytes: &[u8],
expected: Option<Digest256>,
) -> Result<SetPinaxOutcome, SetPinaxError> {
self.inner.set_pinax(name, bytes, expected)
}
}
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
}