1use crate::AwpVersion;
2
3#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
18pub enum AwpError {
19 #[error("invalid request: {0}")]
20 InvalidRequest(String),
21
22 #[error("unauthorized: {0}")]
23 Unauthorized(String),
24
25 #[error("forbidden: {0}")]
26 Forbidden(String),
27
28 #[error("not found: {0}")]
29 NotFound(String),
30
31 #[error("rate limited: retry after {retry_after_secs}s")]
32 RateLimited { retry_after_secs: u64 },
33
34 #[error("version mismatch: requested {requested}, current {current}")]
35 VersionMismatch { requested: AwpVersion, current: AwpVersion },
36
37 #[error("internal error: {0}")]
38 InternalError(String),
39
40 #[error("service unavailable: {0}")]
41 ServiceUnavailable(String),
42}
43
44impl AwpError {
45 pub fn status_code(&self) -> u16 {
47 match self {
48 AwpError::InvalidRequest(_) => 400,
49 AwpError::Unauthorized(_) => 401,
50 AwpError::Forbidden(_) => 403,
51 AwpError::NotFound(_) => 404,
52 AwpError::RateLimited { .. } => 429,
53 AwpError::VersionMismatch { .. } => 406,
54 AwpError::InternalError(_) => 500,
55 AwpError::ServiceUnavailable(_) => 503,
56 }
57 }
58
59 pub fn error_code(&self) -> &str {
61 match self {
62 AwpError::InvalidRequest(_) => "invalid_request",
63 AwpError::Unauthorized(_) => "unauthorized",
64 AwpError::Forbidden(_) => "forbidden",
65 AwpError::NotFound(_) => "not_found",
66 AwpError::RateLimited { .. } => "rate_limited",
67 AwpError::VersionMismatch { .. } => "version_mismatch",
68 AwpError::InternalError(_) => "internal_error",
69 AwpError::ServiceUnavailable(_) => "service_unavailable",
70 }
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use crate::CURRENT_VERSION;
78
79 #[test]
80 fn test_status_code_invalid_request() {
81 assert_eq!(AwpError::InvalidRequest("bad".into()).status_code(), 400);
82 }
83
84 #[test]
85 fn test_status_code_unauthorized() {
86 assert_eq!(AwpError::Unauthorized("no token".into()).status_code(), 401);
87 }
88
89 #[test]
90 fn test_status_code_forbidden() {
91 assert_eq!(AwpError::Forbidden("denied".into()).status_code(), 403);
92 }
93
94 #[test]
95 fn test_status_code_not_found() {
96 assert_eq!(AwpError::NotFound("missing".into()).status_code(), 404);
97 }
98
99 #[test]
100 fn test_status_code_rate_limited() {
101 assert_eq!(AwpError::RateLimited { retry_after_secs: 30 }.status_code(), 429);
102 }
103
104 #[test]
105 fn test_status_code_version_mismatch() {
106 let err = AwpError::VersionMismatch {
107 requested: AwpVersion { major: 2, minor: 0 },
108 current: CURRENT_VERSION,
109 };
110 assert_eq!(err.status_code(), 406);
111 }
112
113 #[test]
114 fn test_status_code_internal_error() {
115 assert_eq!(AwpError::InternalError("oops".into()).status_code(), 500);
116 }
117
118 #[test]
119 fn test_status_code_service_unavailable() {
120 assert_eq!(AwpError::ServiceUnavailable("down".into()).status_code(), 503);
121 }
122
123 #[test]
124 fn test_display_non_empty() {
125 let errors: Vec<AwpError> = vec![
126 AwpError::InvalidRequest("bad".into()),
127 AwpError::Unauthorized("no token".into()),
128 AwpError::Forbidden("denied".into()),
129 AwpError::NotFound("missing".into()),
130 AwpError::RateLimited { retry_after_secs: 30 },
131 AwpError::VersionMismatch {
132 requested: AwpVersion { major: 2, minor: 0 },
133 current: CURRENT_VERSION,
134 },
135 AwpError::InternalError("oops".into()),
136 AwpError::ServiceUnavailable("down".into()),
137 ];
138 for err in errors {
139 let msg = err.to_string();
140 assert!(!msg.is_empty(), "Display for {err:?} should be non-empty");
141 }
142 }
143
144 #[test]
145 fn test_error_codes() {
146 assert_eq!(AwpError::InvalidRequest("x".into()).error_code(), "invalid_request");
147 assert_eq!(AwpError::Unauthorized("x".into()).error_code(), "unauthorized");
148 assert_eq!(AwpError::Forbidden("x".into()).error_code(), "forbidden");
149 assert_eq!(AwpError::NotFound("x".into()).error_code(), "not_found");
150 assert_eq!(AwpError::RateLimited { retry_after_secs: 1 }.error_code(), "rate_limited");
151 assert_eq!(
152 AwpError::VersionMismatch { requested: CURRENT_VERSION, current: CURRENT_VERSION }
153 .error_code(),
154 "version_mismatch"
155 );
156 assert_eq!(AwpError::InternalError("x".into()).error_code(), "internal_error");
157 assert_eq!(AwpError::ServiceUnavailable("x".into()).error_code(), "service_unavailable");
158 }
159}