Consolidating Vec<Error> to single anyhow::Error

I have a function that returns Result<(), Vec<LibraryError>> called validate() (see here for some background).

I want to call this in my main() function that has a return type of anyhow::Result<()>.

The naive approach of ? doesn't work, because of the Vec<LibraryError> .

I tried map_err() along with format() from itertools but that is breaking things even more.

My current code is:

library_data.validate().map_err(|err_vec| 
    Err(anyhow(err_vec.into_iter().format("\n")))
   
);

I have read Idiomatic way to __collect__ errors and Error handling in a validation function but neither offered solutions to my problem.

Thanks in advance!

just consider wrap the vector of LibraryError as a new error type

I suggest that you stop thinking of validation messages as errors. Producing the expected validation messages for a given input is everything working correctly, not failing.

It takes a bit of getting used to in your head, but I find it works out much nicer in the end. You end up with something like Result<Vec<ValidationMessage>, InternalError>, which is also a useful distinction, since that way you have the distinction between "that widget doesn't exist" and "I couldn't connect to the DB to determine which widgets exist", for example.

For internal helper things to implement that, you even write them not caring about the Vec at all, because using a signature like fn stuff(record_message: impl FnMut(ValidationMessage)) -> Result<(), InternalError> lets everything just care about reporting the message, not how that's done. And thus you have flexibility later to do it differently (such as in unit tests, in logging the messages instead of returning them, in panicking on them, whatever is appropriate) without needing to change the checking code.

6 Likes

Another reason to consider such a callback strategy is that it is easy to pass down to further functions implementing parts of the validation, whereas returning a Vec means needing to write code to concatenate the vectors at every point, or making the inner functions work differently.

I would suggest, however, making the callback parameter be record_message: &mut dyn FnMut(ValidationMessage) instead.

  • By using &mut, when passing it to inner functions, it is implicitly reborrowed, rather than needing an explicit borrow if it is used more than once.
  • By using dyn, the validation code is not monomorphized separately for each possible callback, improving compilation time and code size if there is ever more than one (for example, in tests).

Personally, I use something involving dyn ErrorHandler with an ErrorHandler trait (generally not with that exact name) that has a blanket impl for the appropriate function trait. It gives me a place to put a doc comment to describe the information provided to the error handler, and it allows implementations to be nameable without TAIT (type-alias-impl-trait for anyone who hasn't seen that acronym).

In my case, the Validation messages are all blocking errors, but I can see how this approach would work better.

If everything validated successfully, I would just get an empty Vec and if there was an error in the validatio nprocess itself, I would actually return an Err(). I can make that work.

Thanks