#[derive(Debug, thiserror::Error)]
pub enum RightsizeError {
#[error("{}", format_unsupported(feature, backend, remedy))]
UnsupportedByBackend {
feature: String,
backend: String,
remedy: Option<String>,
},
#[error("{message}")]
PortBindConflict {
message: String,
#[source]
source: Option<Box<RightsizeError>>,
},
#[error("{0}")]
ContainerLaunch(String),
#[error("{0}")]
Backend(String),
#[error("{0}")]
Provision(String),
#[error(transparent)]
Io(#[from] std::io::Error),
}
fn format_unsupported(feature: &str, backend: &str, remedy: &Option<String>) -> String {
let base = format!("Feature '{feature}' is not supported by the '{backend}' backend");
match remedy {
Some(r) => format!("{base} — {r}"),
None => base,
}
}
impl RightsizeError {
pub fn unsupported(feature: impl Into<String>, backend: impl Into<String>) -> Self {
Self::UnsupportedByBackend {
feature: feature.into(),
backend: backend.into(),
remedy: None,
}
}
pub fn unsupported_with_remedy(
feature: impl Into<String>,
backend: impl Into<String>,
remedy: impl Into<String>,
) -> Self {
Self::UnsupportedByBackend {
feature: feature.into(),
backend: backend.into(),
remedy: Some(remedy.into()),
}
}
}
pub type Result<T> = std::result::Result<T, RightsizeError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unsupported_without_remedy_renders_base_sentence_only() {
let e = RightsizeError::unsupported("network alias 'bad'", "microsandbox");
assert_eq!(
e.to_string(),
"Feature 'network alias 'bad'' is not supported by the 'microsandbox' backend"
);
}
#[test]
fn unsupported_with_remedy_appends_it_after_an_em_dash() {
let e = RightsizeError::unsupported_with_remedy(
"network links (no nc/busybox in consumer image 'X')",
"microsandbox",
"run this test with RIGHTSIZE_BACKEND=docker instead",
);
assert_eq!(
e.to_string(),
"Feature 'network links (no nc/busybox in consumer image 'X')' is not supported by \
the 'microsandbox' backend — run this test with RIGHTSIZE_BACKEND=docker instead"
);
}
#[test]
fn port_bind_conflict_displays_its_message() {
let e = RightsizeError::PortBindConflict {
message: "address already in use".into(),
source: None,
};
assert_eq!(e.to_string(), "address already in use");
}
#[test]
fn container_launch_and_backend_display_their_payload() {
let e = RightsizeError::ContainerLaunch("timed out waiting for port 6379".into());
assert_eq!(e.to_string(), "timed out waiting for port 6379");
let e = RightsizeError::Backend("500: already allocated".into());
assert_eq!(e.to_string(), "500: already allocated");
}
}