heed icon indicating copy to clipboard operation
heed copied to clipboard

Make the error returned by the BytesDecode/BytesEncode trait customizable

Open irevoire opened this issue 2 years ago • 2 comments
trafficstars

This PR is an attempt at getting rid of the BoxedError type we currently use everywhere. That allows us to use a « real » type when an error arises in the BytesEncode or BytesDecode traits.

The new traits

/// A trait that represents an encoding structure.
pub trait BytesEncode<'a> {
    type EItem: ?Sized + 'a;
    type Err;

    fn bytes_encode(item: &'a Self::EItem) -> Result<Cow<'a, [u8]>, Self::Err>;
}

/// A trait that represents a decoding structure.
pub trait BytesDecode<'a> {
    type DItem: 'a;
    type Err;

    fn bytes_decode(bytes: &'a [u8]) -> Result<Self::DItem, Self::Err>;
}

I called the type Err to do something similar to the FromStr trait from the stdlib.

The new Error enum

/// An error that encapsulates all possible errors in this crate.
#[derive(Debug)]
pub enum Error<E, D> {
    Io(io::Error),
    Mdb(MdbError),
    Encoding(E),
    Decoding(D),
    InvalidDatabaseTyping,
    DatabaseClosing,
    BadOpenOptions {
        /// The options that were used to originaly open this env.
        options: EnvOpenOptions,
        /// The env opened with the original options.
        env: Env,
    },
}

I had to make the Error enum generic over the decoding and encoding error.

For most functions, that do not add any complexity because we use our Result type;

/// Either a success or an [`Error`].
pub type Result<T, E = Infallible, D = Infallible> = result::Result<T, Error<E, D>>;

That set E and D to Infallible by default.

My issue

For some methods like the following one, we can return an error while encoding the key OR the value + while decoding the key OR the value:

    pub fn get_lower_than<'a, 'txn, KC, DC>(
        &self,
        txn: &'txn RoTxn,
        key: &'a KC::EItem,
    ) -> Result<Option<(KC::DItem, DC::DItem)>, KC::Err, DC::Err>
    where
        KC: BytesEncode<'a> + BytesDecode<'txn>,
        DC: BytesDecode<'txn>,
    {
        assert_eq_env_db_txn!(self, txn);

        let mut cursor = RoCursor::new(txn, self.dbi)?;
        let key_bytes: Cow<[u8]> = KC::bytes_encode(&key).map_err(Error::Encoding)?;
        cursor.move_on_key_greater_than_or_equal_to(&key_bytes)?;

        match cursor.move_on_prev() {
            Ok(Some((key, data))) => match (KC::bytes_decode(key), DC::bytes_decode(data)) {
                (Ok(key), Ok(data)) => Ok(Some((key, data))),
                (Err(e), _) | (_, Err(e)) => Err(Error::Decoding(e)),
            },
            Ok(None) => Ok(None),
            Err(e) => Err(e),
        }
    }

One solution could be adding two other variants to the Error enum.

irevoire avatar Feb 22 '23 15:02 irevoire

I tried to introduce more variants to the Error enum to represent the key encoding/decoding and data encoding/decoding possible errors.

Unfortunately, it's not that easy to convert the generic Error types between them. Some functions return a Result<Infallible, Infallible, Infallible, Infallible> and it is not easy to convert that into a Result<KE, KD, DE, DD>.

Kerollmops avatar Mar 05 '23 18:03 Kerollmops

For some methods like the following one, we can return an error while encoding the key OR the value + while decoding the key OR the value:

Instead of adding two variants to the enum, a new enum can be made that can hold two (different) encoding errors (or use a crate like either). The method in your example can then return Result<Option<(KC::DItem, DC::DItem)>, KC::Err, Either<KC::Err, DC::Err>>.

I came up with this here.

antonilol avatar Aug 20 '24 16:08 antonilol