summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLinus Färnstrand <linus@mullvad.net>2019-04-04 19:10:23 +0200
committerLinus Färnstrand <linus@mullvad.net>2019-04-04 19:10:23 +0200
commit646ac0eff4253edf31934c36caa1dd1a2c778b27 (patch)
treed0e5e1f7c6b54a6d76d249f6d33a1df6c766d58a
parent0ff9b0d3321647367fdde3d501641f6e87975919 (diff)
parent77277422e52cfad4341900bbab2d6c611608292a (diff)
downloadmullvadvpn-646ac0eff4253edf31934c36caa1dd1a2c778b27.tar.xz
mullvadvpn-646ac0eff4253edf31934c36caa1dd1a2c778b27.zip
Merge branch 'eliminate-error-chain-types'
-rw-r--r--Cargo.lock3
-rw-r--r--mullvad-daemon/src/lib.rs26
-rw-r--r--mullvad-daemon/src/management_interface.rs6
-rw-r--r--mullvad-types/Cargo.toml8
-rw-r--r--mullvad-types/src/custom_tunnel.rs30
-rw-r--r--mullvad-types/src/lib.rs3
-rw-r--r--mullvad-types/src/settings.rs74
-rw-r--r--talpid-core/src/dns/linux/systemd_resolved.rs3
-rw-r--r--talpid-core/src/lib.rs31
-rw-r--r--talpid-core/src/tunnel_state_machine/blocked_state.rs4
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs2
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs2
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnected_state.rs3
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnecting_state.rs7
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs3
-rw-r--r--talpid-types/Cargo.toml1
-rw-r--r--talpid-types/src/lib.rs31
17 files changed, 121 insertions, 116 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ae84577039..a447c280d1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1214,7 +1214,7 @@ name = "mullvad-types"
version = "0.1.0"
dependencies = [
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "err-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"ipnetwork 0.14.0 (git+https://github.com/mullvad/ipnetwork?branch=fix-deserialization)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2065,7 +2065,6 @@ name = "talpid-types"
version = "0.1.0"
dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"ipnetwork 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 82baca2db0..90db136bcb 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -50,6 +50,7 @@ use talpid_core::{
use talpid_types::{
net::{openvpn, wireguard, TransportProtocol, TunnelParameters},
tunnel::{BlockReason, TunnelStateTransition},
+ ErrorExt,
};
@@ -387,8 +388,8 @@ impl Daemon {
.map_err(|_| Error::from("Tunnel parameters receiver stopped listening"))
})
});
- if let Err(error) = result {
- error!("{}", error.display_chain());
+ if let Err(e) = result {
+ error!("{}", ErrorExt::display_chain(&e));
}
}
@@ -550,7 +551,10 @@ impl Daemon {
let https_handle = self.https_handle.clone();
geoip::send_location_request(https_handle).map_err(|e| {
- warn!("Unable to fetch GeoIP location: {}", e.display_chain());
+ warn!(
+ "Unable to fetch GeoIP location: {}",
+ ChainedError::display_chain(&e)
+ );
})
}
@@ -615,7 +619,7 @@ impl Daemon {
}
}
}
- Err(e) => error!("{}", e.display_chain()),
+ Err(e) => error!("{}", ErrorExt::display_chain(&e)),
}
}
@@ -672,7 +676,7 @@ impl Daemon {
self.reconnect_tunnel();
}
}
- Err(e) => error!("{}", e.display_chain()),
+ Err(e) => error!("{}", ErrorExt::display_chain(&e)),
}
}
@@ -687,7 +691,7 @@ impl Daemon {
self.send_tunnel_command(TunnelCommand::AllowLan(allow_lan));
}
}
- Err(e) => error!("{}", e.display_chain()),
+ Err(e) => error!("{}", ErrorExt::display_chain(&e)),
}
}
@@ -710,7 +714,7 @@ impl Daemon {
));
}
}
- Err(e) => error!("{}", e.display_chain()),
+ Err(e) => error!("{}", ErrorExt::display_chain(&e)),
}
}
@@ -724,7 +728,7 @@ impl Daemon {
.notify_settings(self.settings.clone());
}
}
- Err(e) => error!("{}", e.display_chain()),
+ Err(e) => error!("{}", ErrorExt::display_chain(&e)),
}
}
@@ -740,7 +744,7 @@ impl Daemon {
self.reconnect_tunnel();
}
}
- Err(e) => error!("{}", e.display_chain()),
+ Err(e) => error!("{}", ErrorExt::display_chain(&e)),
}
}
@@ -807,7 +811,7 @@ impl Daemon {
self.reconnect_tunnel();
}
}
- Err(e) => error!("{}", e.display_chain()),
+ Err(e) => error!("{}", ErrorExt::display_chain(&e)),
}
}
@@ -823,7 +827,7 @@ impl Daemon {
self.reconnect_tunnel();
}
}
- Err(e) => error!("{}", e.display_chain()),
+ Err(e) => error!("{}", ErrorExt::display_chain(&e)),
}
}
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index 1966e0a7bf..24bf2df391 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -544,9 +544,9 @@ impl<T: From<ManagementCommand> + 'static + Send> ManagementInterfaceApi
.send_command_to_daemon(ManagementCommand::SetOpenVpnProxy(tx, proxy))
.and_then(|_| rx.map_err(|_| Error::internal_error()))
.and_then(|settings_result| {
- settings_result.map_err(|err| match err.kind() {
- settings::ErrorKind::InvalidProxyData(msg) => {
- Error::invalid_params(msg.to_owned())
+ settings_result.map_err(|error| match error {
+ settings::Error::InvalidProxyData(reason) => {
+ Error::invalid_params(reason.to_owned())
}
_ => Error::internal_error(),
})
diff --git a/mullvad-types/Cargo.toml b/mullvad-types/Cargo.toml
index 27c59acc0f..d3933508ad 100644
--- a/mullvad-types/Cargo.toml
+++ b/mullvad-types/Cargo.toml
@@ -8,13 +8,13 @@ edition = "2018"
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
-serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0"
-error-chain = "0.12"
+err-derive = "0.1.5"
ipnetwork = { git = "https://github.com/mullvad/ipnetwork", branch = "fix-deserialization" }
+lazy_static = "1.1.0"
log = "0.4"
regex = "1"
-lazy_static = "1.1.0"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
talpid-types = { path = "../talpid-types" }
mullvad-paths = { path = "../mullvad-paths" }
diff --git a/mullvad-types/src/custom_tunnel.rs b/mullvad-types/src/custom_tunnel.rs
index a9171a5478..ac46b609a3 100644
--- a/mullvad-types/src/custom_tunnel.rs
+++ b/mullvad-types/src/custom_tunnel.rs
@@ -1,20 +1,19 @@
use crate::settings::TunnelOptions;
use serde::{Deserialize, Serialize};
use std::{
- fmt,
+ fmt, io,
net::{IpAddr, SocketAddr, ToSocketAddrs},
};
use talpid_types::net::{openvpn, wireguard, TunnelParameters};
-error_chain! {
- errors {
- InvalidHost(host: String) {
- display("Invalid host: {}", host)
- }
- Unsupported {
- description("Tunnel type not supported")
- }
- }
+
+#[derive(err_derive::Error, Debug)]
+pub enum Error {
+ #[error(display = "Invalid host/domain: {}", _0)]
+ InvalidHost(String, #[error(cause)] io::Error),
+
+ #[error(display = "Host has no IPv4 address: {}", _0)]
+ HostHasNoIpv4(String),
}
@@ -29,7 +28,10 @@ impl CustomTunnelEndpoint {
Self { host, config }
}
- pub fn to_tunnel_parameters(&self, tunnel_options: TunnelOptions) -> Result<TunnelParameters> {
+ pub fn to_tunnel_parameters(
+ &self,
+ tunnel_options: TunnelOptions,
+ ) -> Result<TunnelParameters, Error> {
let ip = resolve_to_ip(&self.host)?;
let mut config = self.config.clone();
config.set_ip(ip);
@@ -76,10 +78,10 @@ impl fmt::Display for CustomTunnelEndpoint {
/// Returns the first IPv4 address if one exists, otherwise the first IPv6 address.
/// Rust only provides means to resolve a socket addr, not just a host, for some reason. So
/// because of this we do the resolving with port zero and then pick out the IPs.
-fn resolve_to_ip(host: &str) -> Result<IpAddr> {
+fn resolve_to_ip(host: &str) -> Result<IpAddr, Error> {
let (mut ipv4, mut ipv6): (Vec<IpAddr>, Vec<IpAddr>) = (host, 0)
.to_socket_addrs()
- .chain_err(|| ErrorKind::InvalidHost(host.to_owned()))?
+ .map_err(|e| Error::InvalidHost(host.to_owned(), e))?
.map(|addr| addr.ip())
.partition(|addr| addr.is_ipv4());
@@ -88,7 +90,7 @@ fn resolve_to_ip(host: &str) -> Result<IpAddr> {
log::info!("No IPv4 for host {}", host);
ipv6.pop()
})
- .ok_or_else(|| ErrorKind::InvalidHost(host.to_owned()).into())
+ .ok_or_else(|| Error::HostHasNoIpv4(host.to_owned()))
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
diff --git a/mullvad-types/src/lib.rs b/mullvad-types/src/lib.rs
index c49f9e904d..6947c4ee9c 100644
--- a/mullvad-types/src/lib.rs
+++ b/mullvad-types/src/lib.rs
@@ -8,9 +8,6 @@
#![deny(rust_2018_idioms)]
-#[macro_use]
-extern crate error_chain;
-
pub mod account;
pub mod auth_failed;
pub mod endpoint;
diff --git a/mullvad-types/src/settings.rs b/mullvad-types/src/settings.rs
index 5609960883..92b656bfa5 100644
--- a/mullvad-types/src/settings.rs
+++ b/mullvad-types/src/settings.rs
@@ -7,27 +7,28 @@ use serde_json;
use std::{fs::File, io, path::PathBuf};
use talpid_types::net::{openvpn, wireguard, GenericTunnelOptions};
-error_chain! {
- errors {
- DirectoryError {
- description("Unable to create settings directory for program")
- }
- ReadError(path: PathBuf) {
- description("Unable to read settings file")
- display("Unable to read settings from {}", path.display())
- }
- WriteError(path: PathBuf) {
- description("Unable to write settings file")
- display("Unable to write settings to {}", path.display())
- }
- ParseError {
- description("Malformed settings")
- }
- InvalidProxyData(reason: String) {
- description("Invalid proxy configuration was rejected")
- display("Invalid proxy configuration was rejected: {}", reason)
- }
- }
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[derive(err_derive::Error, Debug)]
+pub enum Error {
+ #[error(display = "Unable to create settings directory")]
+ DirectoryError(#[error(cause)] mullvad_paths::Error),
+
+ #[error(display = "Unable to read settings from {}", _0)]
+ ReadError(String, #[error(cause)] io::Error),
+
+ #[error(display = "Malformed settings")]
+ ParseError(#[error(cause)] serde_json::Error),
+
+ #[error(display = "Unable to serialize settings to JSON")]
+ SerializeError(#[error(cause)] serde_json::Error),
+
+ #[error(display = "Unable to write settings to {}", _0)]
+ WriteError(String, #[error(cause)] io::Error),
+
+ #[error(display = "Invalid OpenVPN proxy configuration: {}", _0)]
+ InvalidProxyData(String),
}
static SETTINGS_FILE: &str = "settings.json";
@@ -70,20 +71,17 @@ impl Default for Settings {
impl Settings {
/// Loads user settings from file. If no file is present it returns the defaults.
pub fn load() -> Result<Settings> {
- let settings_path = Self::get_settings_path()?;
- match File::open(&settings_path) {
+ let path = Self::get_settings_path()?;
+ match File::open(&path) {
Ok(file) => {
- info!("Loading settings from {}", settings_path.display());
+ info!("Loading settings from {}", path.display());
Self::read_settings(&mut io::BufReader::new(file))
}
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
- info!(
- "No settings file at {}, using defaults",
- settings_path.display()
- );
+ info!("No settings file at {}, using defaults", path.display());
Ok(Settings::default())
}
- Err(e) => Err(e).chain_err(|| ErrorKind::ReadError(settings_path)),
+ Err(e) => Err(Error::ReadError(path.display().to_string(), e)),
}
}
@@ -92,20 +90,21 @@ impl Settings {
let path = Self::get_settings_path()?;
debug!("Writing settings to {}", path.display());
- let mut file = File::create(&path).chain_err(|| ErrorKind::WriteError(path.clone()))?;
+ let mut file =
+ File::create(&path).map_err(|e| Error::WriteError(path.display().to_string(), e))?;
- serde_json::to_writer_pretty(&mut file, self)
- .chain_err(|| ErrorKind::WriteError(path.clone()))?;
- file.sync_all().chain_err(|| ErrorKind::WriteError(path))
+ serde_json::to_writer_pretty(&mut file, self).map_err(Error::SerializeError)?;
+ file.sync_all()
+ .map_err(|e| Error::WriteError(path.display().to_string(), e))
}
fn get_settings_path() -> Result<PathBuf> {
- let dir = ::mullvad_paths::settings_dir().chain_err(|| ErrorKind::DirectoryError)?;
+ let dir = ::mullvad_paths::settings_dir().map_err(Error::DirectoryError)?;
Ok(dir.join(SETTINGS_FILE))
}
fn read_settings<T: io::Read>(file: &mut T) -> Result<Settings> {
- serde_json::from_reader(file).chain_err(|| ErrorKind::ParseError)
+ serde_json::from_reader(file).map_err(Error::ParseError)
}
pub fn get_account_token(&self) -> Option<String> {
@@ -203,9 +202,8 @@ impl Settings {
pub fn set_openvpn_proxy(&mut self, proxy: Option<openvpn::ProxySettings>) -> Result<bool> {
if let Some(ref settings) = proxy {
- if let Err(validation_error) = openvpn::ProxySettingsValidation::validate(settings) {
- bail!(ErrorKind::InvalidProxyData(validation_error));
- }
+ openvpn::ProxySettingsValidation::validate(settings)
+ .map_err(Error::InvalidProxyData)?;
}
if self.tunnel_options.openvpn.proxy != proxy {
diff --git a/talpid-core/src/dns/linux/systemd_resolved.rs b/talpid-core/src/dns/linux/systemd_resolved.rs
index 6ed023d9cd..a633cea91e 100644
--- a/talpid-core/src/dns/linux/systemd_resolved.rs
+++ b/talpid-core/src/dns/linux/systemd_resolved.rs
@@ -1,5 +1,5 @@
use super::RESOLV_CONF_PATH;
-use crate::{linux::iface_index, ErrorExt as _};
+use crate::linux::iface_index;
use dbus::{
arg::RefArg, stdintf::*, BusType, Interface, Member, Message, MessageItem, MessageItemArray,
Signature,
@@ -11,6 +11,7 @@ use std::{
net::{IpAddr, Ipv4Addr},
path::Path,
};
+use talpid_types::ErrorExt as _;
pub type Result<T> = std::result::Result<T, Error>;
diff --git a/talpid-core/src/lib.rs b/talpid-core/src/lib.rs
index 1b75276561..1499557439 100644
--- a/talpid-core/src/lib.rs
+++ b/talpid-core/src/lib.rs
@@ -60,34 +60,3 @@ mod mktemp;
/// Misc utilities for the Linux platform.
#[cfg(target_os = "linux")]
mod linux;
-
-
-trait ErrorExt {
- /// Creates a string representation of the entire error chain.
- fn display_chain(&self) -> String;
-
- /// Like [`display_chain`] but with an extra message at the start of the chain
- fn display_chain_with_msg(&self, msg: &str) -> String;
-}
-
-impl<E: std::error::Error> ErrorExt for E {
- fn display_chain(&self) -> String {
- let mut s = format!("Error: {}", self);
- let mut source = self.source();
- while let Some(error) = source {
- s.push_str(&format!("\nCaused by: {}", error));
- source = error.source();
- }
- s
- }
-
- fn display_chain_with_msg(&self, msg: &str) -> String {
- let mut s = format!("Error: {}\nCaused by: {}", msg, self);
- let mut source = self.source();
- while let Some(error) = source {
- s.push_str(&format!("\nCaused by: {}", error));
- source = error.source();
- }
- s
- }
-}
diff --git a/talpid-core/src/tunnel_state_machine/blocked_state.rs b/talpid-core/src/tunnel_state_machine/blocked_state.rs
index a080e3ab67..2387d4e922 100644
--- a/talpid-core/src/tunnel_state_machine/blocked_state.rs
+++ b/talpid-core/src/tunnel_state_machine/blocked_state.rs
@@ -2,9 +2,9 @@ use super::{
ConnectingState, DisconnectedState, EventConsequence, SharedTunnelStateValues, TunnelCommand,
TunnelState, TunnelStateTransition, TunnelStateWrapper,
};
-use crate::{firewall::FirewallPolicy, ErrorExt};
+use crate::firewall::FirewallPolicy;
use futures::{sync::mpsc, Stream};
-use talpid_types::tunnel::BlockReason;
+use talpid_types::{tunnel::BlockReason, ErrorExt};
/// No tunnel is running and all network connections are blocked.
pub struct BlockedState {
diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs
index fc0c070c59..f0dad2b581 100644
--- a/talpid-core/src/tunnel_state_machine/connected_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connected_state.rs
@@ -5,7 +5,6 @@ use super::{
use crate::{
firewall::FirewallPolicy,
tunnel::{CloseHandle, TunnelEvent, TunnelMetadata},
- ErrorExt,
};
use futures::{
sync::{mpsc, oneshot},
@@ -14,6 +13,7 @@ use futures::{
use talpid_types::{
net::{Endpoint, TunnelParameters},
tunnel::BlockReason,
+ ErrorExt,
};
pub struct ConnectedStateBootstrap {
diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs
index faa8f77c78..a843fa1fdd 100644
--- a/talpid-core/src/tunnel_state_machine/connecting_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs
@@ -6,7 +6,6 @@ use super::{
use crate::{
firewall::FirewallPolicy,
tunnel::{self, CloseHandle, TunnelEvent, TunnelMetadata, TunnelMonitor},
- ErrorExt,
};
use futures::{
sync::{mpsc, oneshot},
@@ -23,6 +22,7 @@ use std::{
use talpid_types::{
net::{openvpn, TunnelParameters},
tunnel::BlockReason,
+ ErrorExt,
};
diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs
index 437ef865e7..619efb2c10 100644
--- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs
+++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs
@@ -2,8 +2,9 @@ use super::{
BlockedState, ConnectingState, EventConsequence, SharedTunnelStateValues, TunnelCommand,
TunnelState, TunnelStateTransition, TunnelStateWrapper,
};
-use crate::{firewall::FirewallPolicy, ErrorExt};
+use crate::firewall::FirewallPolicy;
use futures::{sync::mpsc, Stream};
+use talpid_types::ErrorExt;
/// No tunnel is running.
pub struct DisconnectedState;
diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs
index 42b2b230ba..3dd1951806 100644
--- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs
+++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs
@@ -2,13 +2,16 @@ use super::{
BlockedState, ConnectingState, DisconnectedState, EventConsequence, SharedTunnelStateValues,
TunnelCommand, TunnelState, TunnelStateTransition, TunnelStateWrapper,
};
-use crate::{tunnel::CloseHandle, ErrorExt};
+use crate::tunnel::CloseHandle;
use futures::{
sync::{mpsc, oneshot},
Async, Future, Stream,
};
use std::thread;
-use talpid_types::tunnel::{ActionAfterDisconnect, BlockReason};
+use talpid_types::{
+ tunnel::{ActionAfterDisconnect, BlockReason},
+ ErrorExt,
+};
/// This state is active from when we manually trigger a tunnel kill until the tunnel wait
/// operation (TunnelExit) returned.
diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs
index 74874ab39a..370aad57bc 100644
--- a/talpid-core/src/tunnel_state_machine/mod.rs
+++ b/talpid-core/src/tunnel_state_machine/mod.rs
@@ -14,7 +14,7 @@ use self::{
disconnected_state::DisconnectedState,
disconnecting_state::{AfterDisconnect, DisconnectingState},
};
-use crate::{dns::DnsMonitor, firewall::Firewall, mpsc::IntoSender, offline, ErrorExt};
+use crate::{dns::DnsMonitor, firewall::Firewall, mpsc::IntoSender, offline};
use futures::{sync::mpsc, Async, Future, Poll, Stream};
use std::{
io,
@@ -25,6 +25,7 @@ use std::{
use talpid_types::{
net::TunnelParameters,
tunnel::{BlockReason, TunnelStateTransition},
+ ErrorExt,
};
use tokio_core::reactor::Core;
diff --git a/talpid-types/Cargo.toml b/talpid-types/Cargo.toml
index 678a7832fc..d18b212fe2 100644
--- a/talpid-types/Cargo.toml
+++ b/talpid-types/Cargo.toml
@@ -11,6 +11,5 @@ serde = { version = "1.0", features = ["derive"] }
ipnetwork = "0.14"
base64 = "0.10"
hex = "0.3"
-error-chain = "0.12"
x25519-dalek = { version = "0.4.5", features = [ "std", "u64_backend" ], default-features = false }
rand = "0.6"
diff --git a/talpid-types/src/lib.rs b/talpid-types/src/lib.rs
index dfa0e09580..42a2dc4cf7 100644
--- a/talpid-types/src/lib.rs
+++ b/talpid-types/src/lib.rs
@@ -10,3 +10,34 @@
pub mod net;
pub mod tunnel;
+
+
+pub trait ErrorExt {
+ /// Creates a string representation of the entire error chain.
+ fn display_chain(&self) -> String;
+
+ /// Like [`display_chain`] but with an extra message at the start of the chain
+ fn display_chain_with_msg(&self, msg: &str) -> String;
+}
+
+impl<E: std::error::Error> ErrorExt for E {
+ fn display_chain(&self) -> String {
+ let mut s = format!("Error: {}", self);
+ let mut source = self.source();
+ while let Some(error) = source {
+ s.push_str(&format!("\nCaused by: {}", error));
+ source = error.source();
+ }
+ s
+ }
+
+ fn display_chain_with_msg(&self, msg: &str) -> String {
+ let mut s = format!("Error: {}\nCaused by: {}", msg, self);
+ let mut source = self.source();
+ while let Some(error) = source {
+ s.push_str(&format!("\nCaused by: {}", error));
+ source = error.source();
+ }
+ s
+ }
+}