Skip to main content

macp_core/
error.rs

1use thiserror::Error;
2
3/// Protocol error vocabulary. `#[non_exhaustive]`: downstream matches must
4/// carry a wildcard arm so new error variants are not a breaking change.
5#[non_exhaustive]
6#[derive(Debug, Error)]
7pub enum MacpError {
8    #[error("InvalidMacpVersion")]
9    InvalidMacpVersion,
10    #[error("InvalidEnvelope")]
11    InvalidEnvelope,
12    #[error("SessionAlreadyExists")]
13    SessionAlreadyExists,
14    #[error("UnknownSession")]
15    UnknownSession,
16    #[error("SessionNotOpen")]
17    SessionNotOpen,
18    #[error("TtlExpired")]
19    TtlExpired,
20    #[error("InvalidTtl")]
21    InvalidTtl,
22    #[error("UnknownMode")]
23    UnknownMode,
24    #[error("InvalidModeState")]
25    InvalidModeState,
26    #[error("InvalidPayload")]
27    InvalidPayload,
28    #[error("Forbidden")]
29    Forbidden,
30    #[error("Unauthenticated")]
31    Unauthenticated,
32    /// Kept for RFC error-code completeness.  The runtime currently represents
33    /// duplicate detection via `ProcessResult { duplicate: true }` at the Ack
34    /// level rather than returning this as an error.
35    #[error("DuplicateMessage")]
36    DuplicateMessage,
37    #[error("PayloadTooLarge")]
38    PayloadTooLarge,
39    #[error("RateLimited")]
40    RateLimited,
41    #[error("StorageFailed")]
42    StorageFailed,
43    #[error("InvalidSessionId")]
44    InvalidSessionId,
45    #[error("UnknownPolicyVersion")]
46    UnknownPolicyVersion,
47    #[error("PolicyDenied")]
48    PolicyDenied { reasons: Vec<String> },
49    #[error("InvalidPolicyDefinition")]
50    InvalidPolicyDefinition,
51}
52
53impl MacpError {
54    /// Returns the RFC error code string for this error variant.
55    pub fn error_code(&self) -> &'static str {
56        match self {
57            MacpError::InvalidMacpVersion => "UNSUPPORTED_PROTOCOL_VERSION",
58            MacpError::InvalidEnvelope => "INVALID_ENVELOPE",
59            MacpError::SessionAlreadyExists => "SESSION_ALREADY_EXISTS",
60            MacpError::UnknownSession => "SESSION_NOT_FOUND",
61            MacpError::SessionNotOpen => "SESSION_NOT_OPEN",
62            MacpError::TtlExpired => "SESSION_NOT_OPEN",
63            MacpError::InvalidTtl => "INVALID_ENVELOPE",
64            MacpError::UnknownMode => "MODE_NOT_SUPPORTED",
65            MacpError::InvalidModeState => "INVALID_ENVELOPE",
66            MacpError::InvalidPayload => "INVALID_ENVELOPE",
67            MacpError::Forbidden => "FORBIDDEN",
68            MacpError::Unauthenticated => "UNAUTHENTICATED",
69            MacpError::DuplicateMessage => "DUPLICATE_MESSAGE",
70            MacpError::PayloadTooLarge => "PAYLOAD_TOO_LARGE",
71            MacpError::RateLimited => "RATE_LIMITED",
72            MacpError::StorageFailed => "INTERNAL_ERROR",
73            MacpError::InvalidSessionId => "INVALID_SESSION_ID",
74            MacpError::UnknownPolicyVersion => "UNKNOWN_POLICY_VERSION",
75            MacpError::PolicyDenied { .. } => "POLICY_DENIED",
76            MacpError::InvalidPolicyDefinition => "INVALID_POLICY_DEFINITION",
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn error_code_mapping_covers_all_variants() {
87        let cases: Vec<(MacpError, &str)> = vec![
88            (
89                MacpError::InvalidMacpVersion,
90                "UNSUPPORTED_PROTOCOL_VERSION",
91            ),
92            (MacpError::InvalidEnvelope, "INVALID_ENVELOPE"),
93            (MacpError::SessionAlreadyExists, "SESSION_ALREADY_EXISTS"),
94            (MacpError::UnknownSession, "SESSION_NOT_FOUND"),
95            (MacpError::SessionNotOpen, "SESSION_NOT_OPEN"),
96            (MacpError::TtlExpired, "SESSION_NOT_OPEN"),
97            (MacpError::InvalidTtl, "INVALID_ENVELOPE"),
98            (MacpError::UnknownMode, "MODE_NOT_SUPPORTED"),
99            (MacpError::InvalidModeState, "INVALID_ENVELOPE"),
100            (MacpError::InvalidPayload, "INVALID_ENVELOPE"),
101            (MacpError::Forbidden, "FORBIDDEN"),
102            (MacpError::Unauthenticated, "UNAUTHENTICATED"),
103            (MacpError::DuplicateMessage, "DUPLICATE_MESSAGE"),
104            (MacpError::PayloadTooLarge, "PAYLOAD_TOO_LARGE"),
105            (MacpError::RateLimited, "RATE_LIMITED"),
106            (MacpError::StorageFailed, "INTERNAL_ERROR"),
107            (MacpError::InvalidSessionId, "INVALID_SESSION_ID"),
108            (MacpError::UnknownPolicyVersion, "UNKNOWN_POLICY_VERSION"),
109            (
110                MacpError::PolicyDenied {
111                    reasons: vec!["test".into()],
112                },
113                "POLICY_DENIED",
114            ),
115            (
116                MacpError::InvalidPolicyDefinition,
117                "INVALID_POLICY_DEFINITION",
118            ),
119        ];
120
121        for (error, expected_code) in cases {
122            assert_eq!(
123                error.error_code(),
124                expected_code,
125                "error_code() mismatch for {:?}",
126                error
127            );
128            assert!(!error.to_string().is_empty());
129        }
130    }
131
132    #[test]
133    fn display_matches_variant_name() {
134        assert_eq!(
135            MacpError::InvalidMacpVersion.to_string(),
136            "InvalidMacpVersion"
137        );
138        assert_eq!(MacpError::Forbidden.to_string(), "Forbidden");
139        assert_eq!(MacpError::TtlExpired.to_string(), "TtlExpired");
140        assert_eq!(MacpError::Unauthenticated.to_string(), "Unauthenticated");
141        assert_eq!(MacpError::DuplicateMessage.to_string(), "DuplicateMessage");
142        assert_eq!(MacpError::PayloadTooLarge.to_string(), "PayloadTooLarge");
143        assert_eq!(MacpError::RateLimited.to_string(), "RateLimited");
144        assert_eq!(MacpError::StorageFailed.to_string(), "StorageFailed");
145        assert_eq!(MacpError::InvalidSessionId.to_string(), "InvalidSessionId");
146        assert_eq!(
147            MacpError::UnknownPolicyVersion.to_string(),
148            "UnknownPolicyVersion"
149        );
150        assert_eq!(
151            MacpError::PolicyDenied {
152                reasons: vec!["test".into()]
153            }
154            .to_string(),
155            "PolicyDenied"
156        );
157    }
158}