-
Notifications
You must be signed in to change notification settings - Fork 61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can I use context
for any error type that implements a trait without providing an exhaustive list of all possible source error types?
#123
Comments
You should be able to using |
/cc #104 |
If that section of the guide helps you, I'd appreciate any feedback on making it easier to discover. All the documentation I could write will be useless if no one finds it. |
Yes, I have read that section, but it mentions concrete type as source to transformation, while I need to be able to pass any type that satisfies concrete trait, such as std::error::Error and get it boxed automatically. #[derive(Snafu)]
pub enum CoalesceIoError
{
FileOpenErrror { source: std::io::Error},
OtherError { source: Box<dyn std::error::Error> },
}
fn send_file_over_network(file: tokio::fs::File) -> impl Future<Item=(), Error=network::Error> {
// ...
}
fn usage(path: PathBuf) {
tokio::fs::File::open(&path).context(FileOpenError{})
.and_then(|file| {
send_file_over_network(file)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
.context(OtherError{})
})
// other stuff
} What I want to be able to do is something like send_file_over_network(file)
// no need to map explicitly
.context(OtherError{}) |
This is a version of your code that actually compiles. When requesting help from people, I would encourage you to provide these examples yourself; there are many more people asking for help than there are to provide it (just me for SNAFU!). Creating and providing these makes the process of helping much faster, which I'm sure you'd appreciate: use tokio::prelude::*; // 0.1.22
use snafu::{Snafu, futures01::FutureExt}; // 0.4.2
use std::path::PathBuf;
mod network {
use snafu::Snafu;
#[derive(Debug, Snafu)]
pub enum Error {}
}
#[derive(Debug, Snafu)]
pub enum CoalesceIoError {
FileOpenError { source: std::io::Error },
OtherError { source: Box<dyn std::error::Error> },
}
fn send_file_over_network(_file: tokio::fs::File) -> impl Future<Item = (), Error = network::Error> {
future::ok(())
}
fn usage(path: PathBuf) {
tokio::fs::File::open(path)
.context(FileOpenError {})
.and_then(|file| {
send_file_over_network(file)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
.context(OtherError {})
});
// other stuff
}
fn main() {} Starting from this, #[derive(Debug, Snafu)]
pub enum CoalesceIoError {
FileOpenError { source: std::io::Error },
OtherError {
#[snafu(source(from(network::Error, Box::new)))]
source: Box<dyn std::error::Error>,
},
}
fn usage(path: PathBuf) {
tokio::fs::File::open(path)
.context(FileOpenError)
.and_then(|file| {
send_file_over_network(file)
.context(OtherError)
});
} If this isn't what you are asking, can you try constructing a more representative example? Other things to note:
|
Sorry for that, I thought that my wishes are clear, but it seems that they aren't. #[snafu(source(from(network::Error, Box::new)))] I just want to say that any type implementing specified trait is appropriate. Imagine that I want to separate errors in such a way that I don't want to distinguish sources, so many different source errors are possible causes of my single domain error. The only thing I want from them is to be representable as trait objects ( #[derive(Snafu)]
pub enum CoalesceMailboxError
{
MailboxErrorOccurred { source: MailboxError },
DomainError { source: Box<dyn std::error::Error> },
} But with that type I cannot simply use plain |
context
for any error type that implements a trait without providing an exhaustive list of all possible source error types?
I think this is a key point. Many similar libraries map one underlying error (e.g. Your request sounds like the counterpart: N->1. With the current tools that SNAFU provides, I don't see a direct path. The main problem is that Effectively, you'd like to be able to write this: impl<E> IntoError<Error> for Selector
where
E: Into<Box<dyn std::error::Error>>,
{
type Source = E;
fn into_error(self, source: Self::Source) -> Error {
unimplemented!()
}
} ...but...
Instead, you might be able to just get by with your own extension trait (untested): trait LooseResultExt {
fn loose_context<C, E2>(self, context: C) -> Result<T, E2>
where
C: IntoError<E2, Source = E>,
E2: Error + ErrorCompat;
}
impl<T, E> LooseResultExt for Result<T, E> {
fn loose_context<C, E2>(self, context: C) -> Result<T, E2>
where
E: std::error::Error,
C: IntoError<E2, Source = Box<dyn std::error::Error>>,
E2: Error + ErrorCompat,
{
self.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
.context(context)
}
} |
N->1, yes, exactly what I mean. Thanks, I'll try extention trait. I think in SNAFU it may be possible to do that by form |
I don't understand what this implementation would look like; would it be possible for you to provide some code demonstrating it working? |
I came here because I have the same problem. Several places in my application, I have to deserialize JSON. One example is in a request with |
@Ploppz there have been mentions in passing of supporting multiple enum Error {
JsonDeserialization {
#[snafu(from(SerdeError, Box::new))]
#[snafu(from(ReqwestError, Box::new))]
source: Box<dyn Error>,
}
} No one has taken the time to open an issue requesting such a feature yet.
SNAFU would encourage you to separate the variants based on what you are doing. I don't know the domain of your application, but instead of having a enum Error {
UserRecordMissing { source: ReqwestError },
ConfigurationFileMissing { source: SerdeError },
} You could also combine the concepts, if both of your operations could use either method to get JSON: enum DeserializeJsonError {
Reqwest { source: ReqwestError },
Serde { source: SerdeError },
}
enum Error {
UserRecordMissing { source: DeserializeJsonError },
ConfigurationFileMissing { source: DeserializeJsonError },
} |
This seems to work. I don't think it's possible to resolve this without changing the trait or adding another trait. trait IntoError<S, E>
where
S: Error + ErrorCompat,
E: Error + ErrorCompat,
{
fn into_error(self, source: S) -> E;
}
impl<S> IntoError<S, MyError> for MySelector
where
S: Error + ErrorCompat,
{
fn into_error(self, _source: S) -> MyError {
unimplemented!()
}
} |
Adding another use case: when the type of the use snafu::{ResultExt, Snafu};
use std::sync::Arc;
use std::fmt::Debug;
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("Partition error creating snapshot: {}", source))]
PartitionError {
// what to put in here? Ideally without introducing generics into this Error type
// #[snafu(source(from(???, Box::new)))]
source: Box<dyn std::error::Error + Send + Sync>,
},
}
#[derive(Debug)]
pub struct Snapshot<T>
where
T: Send + Sync + 'static + PartitionChunk,
{
partition: Arc<T>,
}
impl<T> Snapshot<T>
where
T: Send + Sync + 'static + PartitionChunk,
{
pub fn run(&self) -> Result<(), Error> {
self.partition
.id()
// would like to be able to remove this:
.map_err(|e| Box::new(e) as _)
.context(PartitionError)?;
Ok(())
}
}
pub trait PartitionChunk: Debug + Send + Sync {
type Error: std::error::Error + Send + Sync + 'static;
fn id(&self) -> Result<u32, Self::Error>;
} |
I need to have
Box<dyn std::error::Error>
as source in some of my error variants. Can I achieve that without manual boxing of error before passing it to error selector?The text was updated successfully, but these errors were encountered: