use thiserror::Error;
#[allow(dead_code)]
#[derive(Debug, Error)]
pub enum Error {
IncompatibleVersion(String),
UnsupportedNetworkConfiguration(String),
NotExist(String),
InvalidEnvValue(String),
IOFailure(String),
FailedToDecode(String),
InvalidNetworkConfig(String),
TryAgainLater(String),
PluginNotAvailable(String),
PluginNotAvailableLimitedConnectivity(String),
Custom(u32, String, String),
}
impl Error {
#[must_use]
pub fn details(&self) -> String {
#[allow(clippy::match_same_arms)]
match self {
Self::IncompatibleVersion(details) => details.clone(),
Self::UnsupportedNetworkConfiguration(details) => details.clone(),
Self::NotExist(details) => details.clone(),
Self::InvalidEnvValue(details) => details.clone(),
Self::IOFailure(details) => details.clone(),
Self::FailedToDecode(details) => details.clone(),
Self::InvalidNetworkConfig(details) => details.clone(),
Self::TryAgainLater(details) => details.clone(),
Self::PluginNotAvailable(details) => details.clone(),
Self::PluginNotAvailableLimitedConnectivity(details) => details.clone(),
Self::Custom(_, _, details) => details.clone(),
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::IncompatibleVersion(_) => write!(f, "Incompatible CNI version"),
Self::UnsupportedNetworkConfiguration(_) => {
write!(f, "Unsupported network configuration")
}
Self::NotExist(_) => write!(f, "Container does not exist"),
Self::InvalidEnvValue(_) => {
write!(f, "Invalid necessary environment variables")
}
Self::IOFailure(_) => write!(f, "I/O failure"),
Self::FailedToDecode(_) => write!(f, "Failed to decode content"),
Self::InvalidNetworkConfig(_) => write!(f, "Invalid network config"),
Self::TryAgainLater(_) => write!(f, "Try again later"),
Self::PluginNotAvailable(_) => write!(f, "Plugin not available"),
Self::PluginNotAvailableLimitedConnectivity(_) => {
write!(f, "Plugin not available, limited connectivity")
}
Self::Custom(_, msg, _) => write!(f, "Custom error: {msg}"),
}
}
}
impl From<&Error> for u32 {
fn from(value: &Error) -> Self {
match value {
Error::IncompatibleVersion(_) => 1,
Error::UnsupportedNetworkConfiguration(_) => 2,
Error::NotExist(_) => 3,
Error::InvalidEnvValue(_) => 4,
Error::IOFailure(_) => 5,
Error::FailedToDecode(_) => 6,
Error::InvalidNetworkConfig(_) => 7,
Error::TryAgainLater(_) => 11,
Error::PluginNotAvailable(_) => 50,
Error::PluginNotAvailableLimitedConnectivity(_) => 51,
Error::Custom(code, _, _) => *code,
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::Error;
use crate::types::ErrorResult;
#[rstest]
#[case(Error::IncompatibleVersion("test".to_string()), 1)]
#[case(Error::UnsupportedNetworkConfiguration("test".to_string()), 2)]
#[case(Error::NotExist("test".to_string()), 3)]
#[case(Error::InvalidEnvValue("test".to_string()), 4)]
#[case(Error::IOFailure("test".to_string()), 5)]
#[case(Error::FailedToDecode("test".to_string()), 6)]
#[case(Error::InvalidNetworkConfig("test".to_string()), 7)]
#[case(Error::TryAgainLater("test".to_string()), 11)]
#[case(Error::PluginNotAvailable("test".to_string()), 50)]
#[case(Error::PluginNotAvailableLimitedConnectivity("test".to_string()), 51)]
#[case(Error::Custom(100, "msg".to_string(), "details".to_string()), 100)]
#[case(Error::Custom(255, "msg".to_string(), "details".to_string()), 255)]
fn test_error_code_conversion(#[case] error: Error, #[case] expected_code: u32) {
assert_eq!(u32::from(&error), expected_code);
}
#[rstest]
#[case(1, "version not supported", Error::IncompatibleVersion("version not supported".to_string()))]
#[case(2, "field xyz", Error::UnsupportedNetworkConfiguration("field xyz".to_string()))]
#[case(3, "container not found", Error::NotExist("container not found".to_string()))]
#[case(4, "CNI_COMMAND not set", Error::InvalidEnvValue("CNI_COMMAND not set".to_string()))]
#[case(5, "failed to read", Error::IOFailure("failed to read".to_string()))]
#[case(6, "invalid JSON", Error::FailedToDecode("invalid JSON".to_string()))]
#[case(7, "missing field", Error::InvalidNetworkConfig("missing field".to_string()))]
#[case(11, "resource busy", Error::TryAgainLater("resource busy".to_string()))]
fn test_error_result_to_error_conversion_standard(
#[case] code: u32,
#[case] details: &str,
#[case] expected: Error,
) {
let error_result = ErrorResult {
cni_version: "1.3.0".to_string(),
code,
msg: "Test message".to_string(),
details: details.to_string(),
};
let error = Error::from(&error_result);
assert_eq!(error.details(), expected.details());
assert_eq!(u32::from(&error), u32::from(&expected));
}
#[rstest]
#[case(101, "Custom error", "custom details")]
#[case(200, "Another custom", "more details")]
fn test_error_result_to_error_conversion_custom(
#[case] code: u32,
#[case] msg: &str,
#[case] details: &str,
) {
let error_result = ErrorResult {
cni_version: "1.3.0".to_string(),
code,
msg: msg.to_string(),
details: details.to_string(),
};
let error = Error::from(&error_result);
if let Error::Custom(result_code, result_msg, result_details) = error {
assert_eq!(result_code, code);
assert_eq!(result_msg, msg);
assert_eq!(result_details, details);
} else {
panic!("Expected Custom error, got: {error:?}");
}
}
#[test]
fn test_error_result_to_error_conversion_unknown() {
let error_result = ErrorResult {
cni_version: "1.3.0".to_string(),
code: 99,
msg: "Unknown".to_string(),
details: "unknown code".to_string(),
};
let error = Error::from(&error_result);
assert!(matches!(error, Error::FailedToDecode(_)));
assert!(error.details().contains("unknown error code: 99"));
}
}