autopush_common/db/bigtable/bigtable_client/
error.rs1use std::fmt::{self, Display};
2
3use actix_web::http::StatusCode;
4use deadpool::managed::{PoolError, TimeoutType};
5use thiserror::Error;
6
7use crate::errors::ReportableError;
8
9#[derive(PartialEq, Eq, Debug)]
10pub enum MutateRowStatus {
11 OK,
12 Cancelled,
13 Unknown,
14 InvalidArgument,
15 DeadlineExceeded,
16 NotFound,
17 AlreadyExists,
18 PermissionDenied,
19 ResourceExhausted,
20 FailedPrecondition,
21 Aborted,
22 OutOfRange,
23 Unimplemented,
24 Internal,
25 Unavailable,
26 DataLoss,
27 Unauthenticated,
28}
29
30impl MutateRowStatus {
31 pub fn is_ok(&self) -> bool {
32 self == &Self::OK
33 }
34}
35
36impl From<i32> for MutateRowStatus {
37 fn from(v: i32) -> Self {
38 match v {
39 0 => Self::OK,
40 1 => Self::Cancelled,
41 2 => Self::Unknown,
42 3 => Self::InvalidArgument,
43 4 => Self::DeadlineExceeded,
44 5 => Self::NotFound,
45 6 => Self::AlreadyExists,
46 7 => Self::PermissionDenied,
47 8 => Self::ResourceExhausted,
48 9 => Self::FailedPrecondition,
49 10 => Self::Aborted,
50 11 => Self::OutOfRange,
51 12 => Self::Unimplemented,
52 13 => Self::Internal,
53 14 => Self::Unavailable,
54 15 => Self::DataLoss,
55 16 => Self::Unauthenticated,
56 _ => Self::Unknown,
57 }
58 }
59}
60
61impl Display for MutateRowStatus {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 f.write_str(match self {
64 MutateRowStatus::OK => "Ok",
65 MutateRowStatus::Cancelled => "Cancelled",
66 MutateRowStatus::Unknown => "Unknown",
67 MutateRowStatus::InvalidArgument => "Invalid Argument",
68 MutateRowStatus::DeadlineExceeded => "Deadline Exceeded",
69 MutateRowStatus::NotFound => "Not Found",
70 MutateRowStatus::AlreadyExists => "Already Exists",
71 MutateRowStatus::PermissionDenied => "Permission Denied",
72 MutateRowStatus::ResourceExhausted => "Resource Exhausted",
73 MutateRowStatus::FailedPrecondition => "Failed Precondition",
74 MutateRowStatus::Aborted => "Aborted",
75 MutateRowStatus::OutOfRange => "Out of Range",
76 MutateRowStatus::Unimplemented => "Unimplemented",
77 MutateRowStatus::Internal => "Internal",
78 MutateRowStatus::Unavailable => "Unavailable",
79 MutateRowStatus::DataLoss => "Data Loss",
80 MutateRowStatus::Unauthenticated => "Unauthenticated",
81 })
82 }
83}
84
85impl MutateRowStatus {
86 pub fn status(&self) -> StatusCode {
87 match self {
88 MutateRowStatus::OK => StatusCode::OK,
89 MutateRowStatus::Aborted
91 | MutateRowStatus::DeadlineExceeded
92 | MutateRowStatus::Internal
93 | MutateRowStatus::ResourceExhausted
94 | MutateRowStatus::Unavailable => StatusCode::SERVICE_UNAVAILABLE,
95 _ => StatusCode::INTERNAL_SERVER_ERROR,
96 }
97 }
98}
99
100#[derive(Debug, Error)]
101pub enum BigTableError {
102 #[error("Invalid Row Response: {0}")]
103 InvalidRowResponse(#[source] tonic::Status),
104
105 #[error("Invalid Chunk")]
106 InvalidChunk(String),
107
108 #[error("BigTable read error: {0}")]
109 Read(#[source] tonic::Status),
110
111 #[error("BigTable write timestamp error: {0}")]
112 WriteTime(#[source] std::time::SystemTimeError),
113
114 #[error("Bigtable write error: {0}")]
115 Write(#[source] tonic::Status),
116
117 #[error("BigTable connection error: {0}")]
118 Connect(#[source] tonic::transport::Error),
119
120 #[error("BigTable authentication error: {0}")]
121 Auth(#[source] gcp_auth::Error),
122
123 #[error("Bigtable RPC attempt exceeded its deadline")]
124 AttemptTimeout,
125
126 #[error("Bigtable request preparation exceeded its deadline")]
127 PreSendTimeout,
128
129 #[error("Bigtable operation exhausted its total retry budget")]
130 OperationTimeout,
131
132 #[error("Bigtable status response: {0:?}")]
135 Status(MutateRowStatus, String),
136
137 #[error("Pool Error: {0}")]
139 Pool(Box<PoolError<BigTableError>>),
140
141 #[error("Pool Timeout: {0:?}")]
143 PoolTimeout(TimeoutType),
144
145 #[error("BigTable config error: {0}")]
146 Config(String),
147
148 #[error("Circuit breaker open: BigTable temporarily unavailable")]
149 CircuitBreakerOpen,
150}
151
152impl BigTableError {
153 pub fn status(&self) -> StatusCode {
154 match self {
155 BigTableError::PoolTimeout(_)
156 | BigTableError::CircuitBreakerOpen
157 | BigTableError::AttemptTimeout
158 | BigTableError::PreSendTimeout
159 | BigTableError::OperationTimeout => StatusCode::SERVICE_UNAVAILABLE,
160 BigTableError::Status(e, _) => e.status(),
161 _ => StatusCode::INTERNAL_SERVER_ERROR,
162 }
163 }
164}
165
166impl ReportableError for BigTableError {
167 fn is_sentry_event(&self) -> bool {
168 #[allow(clippy::match_like_matches_macro)]
169 match self {
170 BigTableError::PoolTimeout(_)
171 | BigTableError::CircuitBreakerOpen
172 | BigTableError::AttemptTimeout
173 | BigTableError::PreSendTimeout
174 | BigTableError::OperationTimeout => false,
175 _ => true,
176 }
177 }
178
179 fn metric_label(&self) -> Option<&'static str> {
180 let err = match self {
181 BigTableError::InvalidRowResponse(_) => "storage.bigtable.error.invalid_row_response",
182 BigTableError::InvalidChunk(_) => "storage.bigtable.error.invalid_chunk",
183 BigTableError::Read(_) => "storage.bigtable.error.read",
184 BigTableError::Write(_) => "storage.bigtable.error.write",
185 BigTableError::Connect(_) => "storage.bigtable.error.connect",
186 BigTableError::Auth(_) => "storage.bigtable.error.auth",
187 BigTableError::AttemptTimeout => "storage.bigtable.error.attempt_timeout",
188 BigTableError::PreSendTimeout => "storage.bigtable.error.pre_send_timeout",
189 BigTableError::OperationTimeout => "storage.bigtable.error.operation_timeout",
190 BigTableError::Status(_, _) => "storage.bigtable.error.status",
191 BigTableError::WriteTime(_) => "storage.bigtable.error.writetime",
192 BigTableError::Pool(_) => "storage.bigtable.error.pool",
193 BigTableError::PoolTimeout(_) => "storage.bigtable.error.pool_timeout",
194 BigTableError::Config(_) => "storage.bigtable.error.config",
195 BigTableError::CircuitBreakerOpen => "storage.bigtable.error.circuit_breaker",
196 };
197 Some(err)
198 }
199
200 fn tags(&self) -> Vec<(&str, String)> {
201 #[allow(clippy::match_like_matches_macro)]
202 match self {
203 BigTableError::PoolTimeout(tt) => vec![("type", format!("{tt:?}").to_lowercase())],
204 _ => vec![],
205 }
206 }
207
208 fn extras(&self) -> Vec<(&str, String)> {
209 match self {
210 BigTableError::InvalidRowResponse(s) => vec![("error", s.to_string())],
211 BigTableError::InvalidChunk(s) => vec![("error", s.to_string())],
212 BigTableError::Read(s) => vec![("error", s.to_string())],
213 BigTableError::Write(s) => vec![("error", s.to_string())],
214 BigTableError::Connect(e) => vec![("error", e.to_string())],
215 BigTableError::Auth(e) => vec![("error", e.to_string())],
216 BigTableError::Status(code, s) => {
217 vec![("code", code.to_string()), ("error", s.to_string())]
218 }
219 BigTableError::WriteTime(s) => vec![("error", s.to_string())],
220 BigTableError::Pool(e) => vec![("error", e.to_string())],
221 _ => vec![],
222 }
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn expected_timeout_failures_are_metrics_only() {
232 assert!(!BigTableError::AttemptTimeout.is_sentry_event());
233 assert!(!BigTableError::PreSendTimeout.is_sentry_event());
234 assert!(!BigTableError::OperationTimeout.is_sentry_event());
235 assert!(BigTableError::Read(tonic::Status::internal("boom")).is_sentry_event());
236 }
237}