haematite 0.6.2

Content-addressed, branchable, actor-native storage engine
Documentation
use std::cell::RefCell;
use std::fmt;
use std::sync::Arc;

use crate::store::NodeStore;

use super::{Hash, Node, TreeError};

/// Crate-private tree operation error that retains a backend refusal until a
/// native compatibility wrapper deliberately maps it to `TreeError`.
#[derive(Debug)]
pub enum TreeOperationError<E> {
    Tree(TreeError),
    NodeGet { hash: Hash, source: E },
    NodePut { source: E },
}

impl<E> TreeOperationError<E> {
    pub(crate) fn into_tree_error(self) -> TreeError {
        match self {
            Self::Tree(error) => error,
            Self::NodeGet { hash, .. } => TreeError::MissingNode { hash },
            Self::NodePut { .. } => TreeError::InvalidNode,
        }
    }
}

impl<E: fmt::Display> fmt::Display for TreeOperationError<E> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Tree(error) => error.fmt(formatter),
            Self::NodeGet { hash, source } => {
                write!(formatter, "node get for {hash} failed: {source}")
            }
            Self::NodePut { source } => write!(formatter, "node put failed: {source}"),
        }
    }
}

impl<E: std::error::Error + 'static> std::error::Error for TreeOperationError<E> {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Tree(error) => Some(error),
            Self::NodeGet { source, .. } | Self::NodePut { source } => Some(source),
        }
    }
}

#[derive(Debug)]
pub(super) struct ErrorCapturingStore<'a, S: NodeStore + ?Sized> {
    inner: &'a mut S,
    captured: RefCell<Option<TreeOperationError<S::Error>>>,
}

impl<'a, S: NodeStore + ?Sized> ErrorCapturingStore<'a, S> {
    pub(super) const fn new(inner: &'a mut S) -> Self {
        Self {
            inner,
            captured: RefCell::new(None),
        }
    }

    pub(super) fn finish<T>(
        self,
        result: Result<T, TreeError>,
    ) -> Result<T, TreeOperationError<S::Error>> {
        match result {
            Ok(value) => Ok(value),
            Err(error) => Err(self
                .captured
                .into_inner()
                .unwrap_or(TreeOperationError::Tree(error))),
        }
    }
}

#[derive(Debug)]
pub(super) struct CapturedBackendError;

impl fmt::Display for CapturedBackendError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("backend error retained by source-preserving tree core")
    }
}

impl std::error::Error for CapturedBackendError {}

impl<S: NodeStore + ?Sized> NodeStore for ErrorCapturingStore<'_, S> {
    type Error = CapturedBackendError;

    fn get(&self, hash: &Hash) -> Result<Option<Arc<Node>>, Self::Error> {
        self.inner.get(hash).map_err(|source| {
            self.captured.replace(Some(TreeOperationError::NodeGet {
                hash: *hash,
                source,
            }));
            CapturedBackendError
        })
    }

    fn put(&mut self, node: &Node) -> Result<Hash, Self::Error> {
        self.inner.put(node).map_err(|source| {
            self.captured
                .replace(Some(TreeOperationError::NodePut { source }));
            CapturedBackendError
        })
    }
}