With the second form you can do println!("{}", error) and see the full context without needing to iterate through the sources manually (e.g. "error with configuration: unable to open config file: permission denied").
I personally don't like it because libraries like anyhow will print the full error chain anyway, meaning you'd get duplicates in your error messages:
Error: error with configuration: unable to open config file: permission denied
Caused By: unable to open config file: permission denied
Caused By: permission denied
In comparison to the non-redundant form:
Error: error with configuration
Caused By: unable to open config file
Caused By: permission denied
#[error("error with configuration")]
Configuration(#[source] BoxDynError),
As an aside, I'm not a fan of implementing from for public error types. Especially not for something as public and generic as io::Error. The source is something you have more control over and doesn't simply allow creating, say, a Disconnect error from any random io::Error.
So for example, using source you can have multiple errors based on io::Error that provide a bit more context. E.g.:
#[derive(Error, Debug)]
pub enum DataStoreError {
#[error("data store disconnected")]
Disconnect(#[source] io::Error),
#[error("could not read from data store")]
Read(#[source] io::Error)