#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum KubeError {
#[error("kubernetes config: {0}")]
Config(String),
#[error("no kubernetes cluster found: {0}")]
NoCluster(String),
#[error("kubernetes object not found: {0}")]
NotFound(String),
#[error("kubernetes write conflict: {0}")]
Conflict(String),
#[error("kubernetes denied {verb} on {resource}; add it to the Role")]
Forbidden {
verb: String,
resource: String,
},
#[error("kubernetes API error: {0}")]
Api(String),
#[error("ConfigMap {name} key {key} does not decode: {message}")]
Decode {
name: String,
key: String,
message: String,
},
}
impl KubeError {
#[must_use]
pub const fn class(&self) -> &'static str {
match self {
Self::Config(_) => "config",
Self::NoCluster(_) => "no_cluster",
Self::NotFound(_) => "not_found",
Self::Conflict(_) => "conflict",
Self::Forbidden { .. } => "forbidden",
Self::Api(_) => "api",
Self::Decode { .. } => "decode",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classes_are_short_and_have_no_detail() {
let cases = [
(KubeError::Config("secret-ish".into()), "config"),
(KubeError::NoCluster("x".into()), "no_cluster"),
(KubeError::NotFound("x".into()), "not_found"),
(KubeError::Conflict("x".into()), "conflict"),
(
KubeError::Forbidden {
verb: "get".into(),
resource: "leases".into(),
},
"forbidden",
),
(KubeError::Api("https://10.0.0.1".into()), "api"),
(
KubeError::Decode {
name: "n".into(),
key: "k".into(),
message: "m".into(),
},
"decode",
),
];
for (err, class) in cases {
assert_eq!(err.class(), class);
assert!(!err.to_string().is_empty());
}
}
#[test]
fn forbidden_names_the_fix() {
let e = KubeError::Forbidden {
verb: "update".into(),
resource: "leases.coordination.k8s.io".into(),
};
assert_eq!(
e.to_string(),
"kubernetes denied update on leases.coordination.k8s.io; add it to the Role"
);
}
}