1use std::fmt;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum JwtError {
24 MissingToken,
27 InvalidHeader,
29 MalformedToken,
31 DeserializationFailed,
33 InvalidSignature,
35 Expired,
37 NotYetValid,
39 InvalidIssuer,
41 InvalidAudience,
43 Revoked,
45 RevocationCheckFailed,
47 AlgorithmMismatch,
49 SigningFailed,
52 SerializationFailed,
54}
55
56impl JwtError {
57 pub fn code(&self) -> &'static str {
62 match self {
63 Self::MissingToken => "jwt:missing_token",
64 Self::InvalidHeader => "jwt:invalid_header",
65 Self::MalformedToken => "jwt:malformed_token",
66 Self::DeserializationFailed => "jwt:deserialization_failed",
67 Self::InvalidSignature => "jwt:invalid_signature",
68 Self::Expired => "jwt:expired",
69 Self::NotYetValid => "jwt:not_yet_valid",
70 Self::InvalidIssuer => "jwt:invalid_issuer",
71 Self::InvalidAudience => "jwt:invalid_audience",
72 Self::Revoked => "jwt:revoked",
73 Self::RevocationCheckFailed => "jwt:revocation_check_failed",
74 Self::AlgorithmMismatch => "jwt:algorithm_mismatch",
75 Self::SigningFailed => "jwt:signing_failed",
76 Self::SerializationFailed => "jwt:serialization_failed",
77 }
78 }
79}
80
81impl fmt::Display for JwtError {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 Self::MissingToken => write!(f, "missing token"),
85 Self::InvalidHeader => write!(f, "invalid token header"),
86 Self::MalformedToken => write!(f, "malformed token"),
87 Self::DeserializationFailed => write!(f, "failed to deserialize token claims"),
88 Self::InvalidSignature => write!(f, "invalid token signature"),
89 Self::Expired => write!(f, "token has expired"),
90 Self::NotYetValid => write!(f, "token is not yet valid"),
91 Self::InvalidIssuer => write!(f, "invalid token issuer"),
92 Self::InvalidAudience => write!(f, "invalid token audience"),
93 Self::Revoked => write!(f, "token has been revoked"),
94 Self::RevocationCheckFailed => write!(f, "token revocation check failed"),
95 Self::AlgorithmMismatch => write!(f, "token algorithm mismatch"),
96 Self::SigningFailed => write!(f, "failed to sign token"),
97 Self::SerializationFailed => write!(f, "failed to serialize token claims"),
98 }
99 }
100}
101
102impl std::error::Error for JwtError {}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::Error;
108
109 #[test]
110 fn all_variants_have_unique_codes() {
111 let variants = [
112 JwtError::MissingToken,
113 JwtError::InvalidHeader,
114 JwtError::MalformedToken,
115 JwtError::DeserializationFailed,
116 JwtError::InvalidSignature,
117 JwtError::Expired,
118 JwtError::NotYetValid,
119 JwtError::InvalidIssuer,
120 JwtError::InvalidAudience,
121 JwtError::Revoked,
122 JwtError::RevocationCheckFailed,
123 JwtError::AlgorithmMismatch,
124 JwtError::SigningFailed,
125 JwtError::SerializationFailed,
126 ];
127 let mut codes: Vec<&str> = variants.iter().map(|v| v.code()).collect();
128 let len_before = codes.len();
129 codes.sort();
130 codes.dedup();
131 assert_eq!(codes.len(), len_before, "duplicate error codes found");
132 }
133
134 #[test]
135 fn all_codes_start_with_jwt_prefix() {
136 let variants = [
137 JwtError::MissingToken,
138 JwtError::Expired,
139 JwtError::SigningFailed,
140 ];
141 for v in &variants {
142 assert!(
143 v.code().starts_with("jwt:"),
144 "code {} missing prefix",
145 v.code()
146 );
147 }
148 }
149
150 #[test]
151 fn display_is_human_readable() {
152 assert_eq!(JwtError::Expired.to_string(), "token has expired");
153 assert_eq!(JwtError::MissingToken.to_string(), "missing token");
154 }
155
156 #[test]
157 fn recoverable_via_source_as() {
158 let err = Error::unauthorized("unauthorized")
159 .chain(JwtError::Expired)
160 .with_code(JwtError::Expired.code());
161 let jwt_err = err.source_as::<JwtError>();
162 assert_eq!(jwt_err, Some(&JwtError::Expired));
163 assert_eq!(err.error_code(), Some("jwt:expired"));
164 }
165}