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.
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.