Skip to content

Error<Err> type pattern prevent building wrapping abstraction #4

Closed
@touilleMan

Description

@touilleMan

Having to specify the custom error type in the Database brings a drawback: it is not possible to have multiple methods using different custom error.

This is typically an issue when wrapping the database to abstract it1, consider the following:

struct WrapperDatabase( Database<anyhow::Error>);  // The error is settled here...
struct WrapperTransaction(Transaction<anyhow::Error>);

impl WrapperTransaction {
  // Implement business-specific methods
}

impl WrapperDatabase {
    async fn for_update<R, E>(
        &mut self,
        cb: impl AsyncFnOnce(WrapperTransaction) -> Result<R, E>, // ...but we want to be generic over the error type !
    ) -> anyhow::Result<Result<R, E>> {
        self.0.transaction(&["store"])
            .rw()
            .run(async move |transaction| {
                let wrapper = WrapperTransaction(transaction);
                cb(updater).await // Error ! expected Result<Result<R, E>, Error<Error>>, found Result<R, E>
            })
            .await
            .map_err(|e| anyhow::anyhow!("{e:?}"))
    }
 }

A solution to overcome this issue is to rely on type erasing:

struct WrapperDatabase( Database<Box<dyn std::any::Any>>);
struct WrapperTransaction(Transaction<Box<dyn std::any::Any>>);

impl WrapperDatabase {
    async fn for_update<R, E>(
        &mut self,
        cb: impl AsyncFnOnce(WrapperTransaction) -> Result<R, E>,
    ) -> anyhow::Result<Result<R, E>> {
        let outcome = self.0.transaction(&["store"])
            .rw()
            .run(async move |transaction| {
                let wrapper = WrapperTransaction(transaction);
                cb(wrapper)
                .await
                .map_err(|e| {
                    let boxed = Box::new(e) as Box<dyn std::any::Any>;
                    boxed.into()
                })
            })
            .await;
        match outcome {
            Ok(ok) => Ok(Ok(ok)),
            Err(err) => match err {
                indexed_db::Error::User(err) => Ok(Err(err.downcast().unwrap())),
                err => Err(anyhow::anyhow!("{err:?}")),
            }
        }
    }
 }

However this is rather cumbersome, and force to Box the error...

Do you see any better approach for this ?

Footnotes

  1. I'm doing this to have a codebase using SQLite on native and IndexedDb on web

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions