use gwk_domain::protocol::KernelErrorCode;
pub const OK: u8 = 0;
pub const USAGE: u8 = 2;
pub const REFUSED: u8 = 3;
pub const NOT_FOUND: u8 = 4;
pub const UNAVAILABLE: u8 = 5;
pub const INTEGRITY: u8 = 6;
pub const INTERNAL: u8 = 10;
pub const fn exit_for(code: KernelErrorCode) -> u8 {
match code {
KernelErrorCode::Validation
| KernelErrorCode::DuplicateKey
| KernelErrorCode::FrameSize => USAGE,
KernelErrorCode::Sealed
| KernelErrorCode::AlreadyActive
| KernelErrorCode::StaleVersion
| KernelErrorCode::IllegalEdge
| KernelErrorCode::Authority
| KernelErrorCode::IdempotencyConflict
| KernelErrorCode::Fenced => REFUSED,
KernelErrorCode::NotFound => NOT_FOUND,
KernelErrorCode::Handshake
| KernelErrorCode::UnsupportedVersion
| KernelErrorCode::Capability
| KernelErrorCode::Overloaded
| KernelErrorCode::SlowConsumer
| KernelErrorCode::Privilege
| KernelErrorCode::Storage => UNAVAILABLE,
KernelErrorCode::Schema
| KernelErrorCode::BlobIntegrity
| KernelErrorCode::BlobTombstoned => INTEGRITY,
}
}
#[derive(Debug)]
pub struct Failure {
pub code: KernelErrorCode,
pub message: String,
pub exit: u8,
}
impl Failure {
pub fn new(code: KernelErrorCode, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
exit: exit_for(code),
}
}
pub fn usage(message: impl Into<String>) -> Self {
Self::new(KernelErrorCode::Validation, message)
}
pub fn unreachable(message: impl Into<String>) -> Self {
Self::new(KernelErrorCode::Storage, message)
}
pub fn internal(message: impl Into<String>) -> Self {
Self {
code: KernelErrorCode::Storage,
message: message.into(),
exit: INTERNAL,
}
}
pub fn to_json(&self) -> serde_json::Value {
serde_json::json!({
"type": "error",
"code": self.code.as_str(),
"message": self.message,
})
}
}
impl std::fmt::Display for Failure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.code.as_str(), self.message)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_refusal_earns_one_of_the_documented_exits() {
for code in KernelErrorCode::ALL {
let exit = exit_for(*code);
assert!(
[USAGE, REFUSED, NOT_FOUND, UNAVAILABLE, INTEGRITY].contains(&exit),
"{code:?} maps to an undocumented exit {exit}"
);
}
assert_eq!(exit_for(KernelErrorCode::NotFound), NOT_FOUND);
assert_eq!(exit_for(KernelErrorCode::Sealed), REFUSED);
assert_eq!(exit_for(KernelErrorCode::BlobTombstoned), INTEGRITY);
assert_eq!(Failure::internal("boom").exit, INTERNAL);
}
#[test]
fn a_local_failure_reports_in_the_protocols_own_error_shape() {
let json = Failure::usage("no such projection: tsak").to_json();
assert_eq!(json["type"], "error");
assert_eq!(json["code"], "validation");
assert!(
json["message"].as_str().expect("message").contains("tsak"),
"{json}"
);
}
}