use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ErrorPolicy {
#[default]
Block,
Allow,
}
impl ErrorPolicy {
pub fn blocks_on_error(&self) -> bool {
matches!(self, ErrorPolicy::Block)
}
pub fn allows_on_error(&self) -> bool {
matches!(self, ErrorPolicy::Allow)
}
pub fn apply_to_error(&self, error: &anyhow::Error) -> ErrorPolicyResult {
match self {
ErrorPolicy::Block => ErrorPolicyResult::Blocked {
reason: format!("Policy check error (fail-closed): {}", error),
},
ErrorPolicy::Allow => ErrorPolicyResult::Allowed {
warning: format!("Policy check error (fail-open): {}", error),
},
}
}
}
#[derive(Debug, Clone)]
pub enum ErrorPolicyResult {
Blocked { reason: String },
Allowed { warning: String },
}
pub fn log_fail_safe(reason: &str, config_path: Option<&str>) {
tracing::warn!(
event = "assay.failsafe.triggered",
reason = %reason,
config_path = %config_path.unwrap_or("none"),
action = "allowed",
"Fail-safe triggered: {}", reason
);
}
impl ErrorPolicyResult {
pub fn is_blocked(&self) -> bool {
matches!(self, ErrorPolicyResult::Blocked { .. })
}
pub fn is_allowed(&self) -> bool {
matches!(self, ErrorPolicyResult::Allowed { .. })
}
pub fn message(&self) -> &str {
match self {
ErrorPolicyResult::Blocked { reason } => reason,
ErrorPolicyResult::Allowed { warning } => warning,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_is_block() {
assert_eq!(ErrorPolicy::default(), ErrorPolicy::Block);
}
#[test]
fn test_block_policy() {
let policy = ErrorPolicy::Block;
assert!(policy.blocks_on_error());
assert!(!policy.allows_on_error());
let error = anyhow::anyhow!("Schema parse failed");
let result = policy.apply_to_error(&error);
assert!(result.is_blocked());
}
#[test]
fn test_allow_policy() {
let policy = ErrorPolicy::Allow;
assert!(!policy.blocks_on_error());
assert!(policy.allows_on_error());
let error = anyhow::anyhow!("Network timeout");
let result = policy.apply_to_error(&error);
assert!(result.is_allowed());
}
#[test]
fn test_serde_roundtrip() {
let block: ErrorPolicy = serde_yaml::from_str("block").unwrap();
assert_eq!(block, ErrorPolicy::Block);
let allow: ErrorPolicy = serde_yaml::from_str("allow").unwrap();
assert_eq!(allow, ErrorPolicy::Allow);
#[derive(Deserialize)]
struct Config {
on_error: ErrorPolicy,
}
let config: Config = serde_yaml::from_str("on_error: block").unwrap();
assert_eq!(config.on_error, ErrorPolicy::Block);
let config: Config = serde_yaml::from_str("on_error: allow").unwrap();
assert_eq!(config.on_error, ErrorPolicy::Allow);
}
}