1use crate::{Capability, SandboxError};
12use thiserror::Error;
13
14#[derive(Debug, Error)]
33pub enum AccessDenied {
34 #[error("capability denied: '{operation}' requires {required}, available: {available}")]
36 CapabilityDenied {
37 operation: String,
39 required: Capability,
41 available: Capability,
43 },
44
45 #[error(transparent)]
47 ResourceDenied(#[from] SandboxError),
48
49 #[error("session denied: {0}")]
51 SessionDenied(String),
52}
53
54impl AccessDenied {
55 #[must_use]
57 pub fn layer(&self) -> &'static str {
58 match self {
59 Self::CapabilityDenied { .. } => "capability",
60 Self::ResourceDenied(_) => "resource",
61 Self::SessionDenied(_) => "session",
62 }
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn capability_denied_display() {
72 let err = AccessDenied::CapabilityDenied {
73 operation: "write_file".to_string(),
74 required: Capability::WRITE,
75 available: Capability::READ,
76 };
77
78 let msg = err.to_string();
79 assert!(msg.contains("write_file"), "got: {msg}");
80 assert!(msg.contains("capability denied"), "got: {msg}");
81 assert_eq!(err.layer(), "capability");
82 }
83
84 #[test]
85 fn resource_denied_from_sandbox_error() {
86 let sandbox_err = SandboxError::OutsideBoundary {
87 path: "/etc/passwd".to_string(),
88 root: "/home/user/project".to_string(),
89 };
90 let err = AccessDenied::from(sandbox_err);
91
92 let msg = err.to_string();
93 assert!(msg.contains("access denied"), "got: {msg}");
94 assert_eq!(err.layer(), "resource");
95 }
96
97 #[test]
98 fn session_denied_display() {
99 let err = AccessDenied::SessionDenied("requires elevation".to_string());
100
101 let msg = err.to_string();
102 assert!(msg.contains("requires elevation"), "got: {msg}");
103 assert_eq!(err.layer(), "session");
104 }
105}