1use thiserror::Error;
2
3#[derive(Error, Debug, Clone, PartialEq, Eq)]
8pub enum GateValidationError {
9 #[error("actor kind must not be empty")]
10 EmptyActorKind,
11 #[error("actor id must not be empty")]
12 EmptyActorId,
13 #[error("verb must not be empty")]
14 EmptyVerb,
15 #[error("deny reason must not be empty")]
16 EmptyDenyReason,
17 #[error("audit tag must not be empty")]
18 EmptyAuditTag,
19 #[error("rate limit window_secs must be > 0")]
20 ZeroRateLimitWindow,
21 #[error("rate limit max must be > 0")]
22 ZeroRateLimitMax,
23}
24
25#[derive(Error, Debug)]
27pub enum GateError {
28 #[error("policy error: {0}")]
29 Policy(String),
30 #[error("internal gate error: {0}")]
31 Internal(String),
32 #[error("validation error: {0}")]
33 Validation(#[from] GateValidationError),
34}
35
36impl GateError {
37 pub fn wire_reason(&self) -> &'static str {
51 match self {
52 Self::Internal(_) => "gate backend unavailable",
53 Self::Policy(_) => "gate policy evaluation failed",
54 Self::Validation(_) => "gate request validation failed",
55 }
56 }
57}
58
59#[cfg(test)]
60mod wire_reason_tests {
61 use super::{GateError, GateValidationError};
62
63 #[test]
64 fn wire_reason_never_echoes_backend_display_text() {
65 let canary = "postgres://svc:not-a-real-secret@internal-host";
66 let error = GateError::Internal(canary.to_string());
67
68 assert!(!error.wire_reason().contains(canary));
69 assert_eq!(error.wire_reason(), "gate backend unavailable");
70 }
71
72 #[test]
73 fn policy_and_internal_classify_into_distinguishable_stable_text() {
74 let internal = GateError::Internal("connection refused".to_string());
75 let policy = GateError::Policy("rule set has no allow clause".to_string());
76 let validation = GateError::Validation(GateValidationError::EmptyVerb);
77
78 assert_eq!(internal.wire_reason(), "gate backend unavailable");
79 assert_eq!(policy.wire_reason(), "gate policy evaluation failed");
80 assert_eq!(validation.wire_reason(), "gate request validation failed");
81 assert_ne!(internal.wire_reason(), policy.wire_reason());
82 assert_ne!(policy.wire_reason(), validation.wire_reason());
83 }
84}