Using and_then with different types of result errors without map_err
I have some functions that will return a different type of error on failure.
First, I have a builder that contains this method:
#[derive(Debug)]
pub enum BuilderError {
ElementMissing(&'static str),
}
pub fn spawn(self) -> Result<ServiceStatus, BuilderError>
Thus, it will return 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 a constructor to create an object ServiceStatus
and use it to create an XML string with a function create_xml
.
For this 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 use better 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 BuilderError
or XmlError
to 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) }
source to share
After some testing, 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 function and_then
for the type Result
that will do the conversion inside it, cleaning up the code
source to share