Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using and_then with different Result error types without map_err

I have some functions that will return a different error type when failing.

First I have a builder, which contains this method:

#[derive(Debug)]
pub enum BuilderError {
    ElementMissing(&'static str),
}

pub fn spawn(self) -> Result<ServiceStatus, BuilderError>

So it will return a BuildError on failure.

Now, I have another function that will return another error:

#[derive(Debug)]
pub enum XmlError {
    XmlCreationFailed(writer::Error),
    ConversionToUtf8(FromUtf8Error),
}

pub fn create_xml(service_status: super::ServiceStatus) -> Result<String, XmlError>

The idea is that I use the builder to create a ServiceStatus object and use it to create a XML string with create_xml function.

To do that, I have this code:

#[derive(Debug)]
pub enum WebserviceError {
    XmlError(XmlError),
    BuilderError(BuilderError),
}

impl std::error::Error for WebserviceError {
    ...
}

impl From<XmlError> for WebserviceError {
    fn from(error: XmlError) -> WebserviceError {
        WebserviceError::XmlError(error)
    }
}

impl From<BuilderError> for WebserviceError {
    fn from(error: BuilderError) -> WebserviceError {
        WebserviceError::BuilderError(error)
    }
}

fn test() -> Result<String, status::WebserviceError> {
    ...
    let service_status = builder.spawn()?;
    let xml = status::create_xml(service_status)?;
    Ok(xml)
}

Now, I think I can do better using and_then instead of using ? operator:

fn test() -> Result<String, status::WebserviceError> {
    ...
    builder
        .spawn()
        .map_err(status::WebserviceError::BuilderError)
        .and_then(|hue| status::create_xml(hue).map_err(status::WebserviceError::XmlError))
}

This solution works too, but now I need to explicitly call map_err to convert from a BuilderError or XmlError to a WebserviceError...

So, my question is, can I do better? I think a solution like this would be ideal:

fn test() -> Result<String, status::WebserviceError> {
    ...
    builder
        .spawn()
        .and_then(status::create_xml)
}
like image 933
Sassa Avatar asked May 04 '17 00:05

Sassa


2 Answers

After some trial, here is the solution:

trait CustomAndThen<T, E> {
    fn and_then2<U, E2, F: FnOnce(T) -> Result<U, E2>>(self, op: F) -> Result<U, E>
        where E: std::convert::From<E2>;
}

impl<T, E> CustomAndThen<T, E> for Result<T, E> {
    fn and_then2<U, E2, F: FnOnce(T) -> Result<U, E2>>(self, op: F) -> Result<U, E>
        where E: std::convert::From<E2>
    {
        match self {
            Ok(t) => op(t).map_err(From::from),
            Err(e) => Err(e),
        }
    }
}

...

Ok(builder)
    .and_then2(status::ServiceStatusBuilder::spawn)
    .and_then2(status::create_xml)

This will create a custom and_then function for Result type that will make the conversion inside it, clearing the code

like image 98
Sassa Avatar answered Nov 14 '22 19:11

Sassa


If you're not really interested in exact error, but raise some final error in the end you can use something like:

builder.spawn().ok()
    .and_then(|v| status.create_xml(v).ok())
    .ok_or_else(|| SomeError('failed to create xml'))
like image 23
Sergey Khoroshavin Avatar answered Nov 14 '22 21:11

Sergey Khoroshavin