1use thiserror::Error;
20
21#[derive(Debug, Error, PartialEq, Eq, Clone)]
23pub enum AuthorityError {
24 #[error("chain broken at index {index}: {reason}")]
25 ChainBroken { index: usize, reason: String },
26
27 #[error("delegation depth {depth} exceeds maximum {max_depth}")]
28 DepthExceeded { depth: usize, max_depth: usize },
29
30 #[error("scope widening detected at index {index}")]
31 ScopeWidening { index: usize },
32
33 #[error("expired link at index {index}")]
34 ExpiredLink { index: usize },
35
36 #[error("invalid signature at index {index}")]
37 InvalidSignature { index: usize },
38
39 #[error("delegation signing payload encoding failed: {reason}")]
40 SigningPayloadEncoding { reason: String },
41
42 #[error("delegation audit hash encoding failed: {reason}")]
43 AuditHashEncoding { reason: String },
44
45 #[error("delegation audit chain broken at sequence {sequence}")]
46 AuditChainBroken { sequence: u64 },
47
48 #[error("invalid delegation: {reason}")]
49 InvalidDelegation { reason: String },
50
51 #[error("duplicate delegation: {id}")]
52 DuplicateDelegation { id: String },
53
54 #[error("circular delegation detected: {0}")]
55 CircularDelegation(String),
56
57 #[error("delegation not found: {0}")]
58 NotFound(String),
59
60 #[error("empty chain")]
61 EmptyChain,
62
63 #[error("permission not granted: {0}")]
64 PermissionDenied(String),
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn error_display_chain_broken() {
73 let e = AuthorityError::ChainBroken {
74 index: 2,
75 reason: "gap".into(),
76 };
77 assert!(e.to_string().contains("2"));
78 assert!(e.to_string().contains("gap"));
79 }
80
81 #[test]
82 fn error_display_depth_exceeded() {
83 let e = AuthorityError::DepthExceeded {
84 depth: 6,
85 max_depth: 5,
86 };
87 assert!(e.to_string().contains("6"));
88 }
89
90 #[test]
91 fn error_display_scope_widening() {
92 let e = AuthorityError::ScopeWidening { index: 1 };
93 assert!(e.to_string().contains("1"));
94 }
95
96 #[test]
97 fn error_display_expired_link() {
98 let e = AuthorityError::ExpiredLink { index: 0 };
99 assert!(e.to_string().contains("0"));
100 }
101
102 #[test]
103 fn error_display_invalid_signature() {
104 let e = AuthorityError::InvalidSignature { index: 3 };
105 assert!(e.to_string().contains("3"));
106 }
107
108 #[test]
109 fn error_display_signing_payload_encoding() {
110 let e = AuthorityError::SigningPayloadEncoding {
111 reason: "writer".into(),
112 };
113 assert!(e.to_string().contains("writer"));
114 }
115
116 #[test]
117 fn error_display_audit_hash_encoding() {
118 let e = AuthorityError::AuditHashEncoding {
119 reason: "writer".into(),
120 };
121 assert!(e.to_string().contains("writer"));
122 }
123
124 #[test]
125 fn error_display_audit_chain_broken() {
126 let e = AuthorityError::AuditChainBroken { sequence: 7 };
127 assert!(e.to_string().contains("7"));
128 }
129
130 #[test]
131 fn error_display_invalid_delegation() {
132 let e = AuthorityError::InvalidDelegation {
133 reason: "empty scope".into(),
134 };
135 assert!(e.to_string().contains("empty scope"));
136 }
137
138 #[test]
139 fn error_display_duplicate_delegation() {
140 let e = AuthorityError::DuplicateDelegation { id: "abc".into() };
141 assert!(e.to_string().contains("abc"));
142 }
143
144 #[test]
145 fn error_display_circular() {
146 let e = AuthorityError::CircularDelegation("A->B->A".into());
147 assert!(e.to_string().contains("A->B->A"));
148 }
149
150 #[test]
151 fn error_display_not_found() {
152 let e = AuthorityError::NotFound("link-x".into());
153 assert!(e.to_string().contains("link-x"));
154 }
155
156 #[test]
157 fn error_display_empty_chain() {
158 let e = AuthorityError::EmptyChain;
159 assert!(e.to_string().contains("empty"));
160 }
161
162 #[test]
163 fn error_display_permission_denied() {
164 let e = AuthorityError::PermissionDenied("write".into());
165 assert!(e.to_string().contains("write"));
166 }
167
168 #[test]
169 fn error_clone_eq() {
170 let e1 = AuthorityError::EmptyChain;
171 assert_eq!(e1.clone(), e1);
172 }
173}