1use thiserror::Error;
8
9#[derive(Debug, Error)]
11pub enum EditError {
12 #[error("policy block: {0}")]
15 PolicyBlock(#[from] PolicyBlockError),
16
17 #[error("runtime error: {0}")]
20 Runtime(#[from] anyhow::Error),
21}
22
23#[derive(Debug, Error)]
25pub enum PolicyBlockError {
26 #[error("precondition mismatch: {message}")]
28 PreconditionMismatch {
29 message: String,
31 },
32
33 #[error("safety gate denial: {message}")]
35 SafetyGateDenial {
36 message: String,
38 },
39
40 #[error("policy denial: {message}")]
42 PolicyDenial {
43 message: String,
45 },
46
47 #[error("caps exceeded: {message}")]
49 CapsExceeded {
50 message: String,
52 },
53}
54
55impl EditError {
56 pub fn is_policy_block(&self) -> bool {
58 matches!(self, EditError::PolicyBlock(_))
59 }
60
61 pub fn exit_code(&self) -> u8 {
63 match self {
64 EditError::PolicyBlock(_) => 2,
65 EditError::Runtime(_) => 1,
66 }
67 }
68}
69
70pub type EditResult<T> = Result<T, EditError>;
72
73#[cfg(test)]
74mod tests {
75 use super::{EditError, PolicyBlockError};
76
77 #[test]
78 fn policy_block_reports_exit_code_2() {
79 let err = EditError::from(PolicyBlockError::PreconditionMismatch {
80 message: "oops".to_string(),
81 });
82 assert!(err.is_policy_block());
83 assert_eq!(err.exit_code(), 2);
84 assert!(err.to_string().contains("policy block"));
85 }
86
87 #[test]
88 fn runtime_error_reports_exit_code_1() {
89 let err = EditError::from(anyhow::anyhow!("boom"));
90 assert!(!err.is_policy_block());
91 assert_eq!(err.exit_code(), 1);
92 assert!(err.to_string().contains("runtime error"));
93 }
94
95 #[test]
96 fn policy_block_display_includes_variant() {
97 let err = PolicyBlockError::SafetyGateDenial {
98 message: "guarded".to_string(),
99 };
100 assert!(err.to_string().contains("safety gate denial"));
101 assert!(err.to_string().contains("guarded"));
102 }
103}