autopush_common/
errors.rs

1//! Error handling for common autopush functions
2
3use std::fmt::{self, Display};
4use std::io;
5use std::num;
6
7use actix_web::{
8    dev::ServiceResponse, http::StatusCode, middleware::ErrorHandlerResponse, HttpResponse,
9    HttpResponseBuilder, ResponseError,
10};
11// Sentry 0.29 uses the backtrace crate, not std::backtrace
12use backtrace::Backtrace;
13use serde::ser::{Serialize, SerializeMap, Serializer};
14use thiserror::Error;
15
16#[cfg(feature = "reliable_report")]
17use redis::RedisError;
18
19pub type Result<T> = std::result::Result<T, ApcError>;
20
21/// Render a 404 response
22pub fn render_404<B>(
23    res: ServiceResponse<B>,
24) -> std::result::Result<ErrorHandlerResponse<B>, actix_web::Error> {
25    // Replace the outbound error message with our own.
26    let resp = HttpResponseBuilder::new(StatusCode::NOT_FOUND).finish();
27    Ok(ErrorHandlerResponse::Response(
28        res.into_response(resp).map_into_right_body(),
29    ))
30}
31
32/// AutoPush Common error (To distinguish from endpoint's ApiError)
33#[derive(Debug)]
34pub struct ApcError {
35    pub kind: ApcErrorKind,
36    pub backtrace: Box<Backtrace>,
37}
38
39impl Display for ApcError {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        self.kind.fmt(f)
42    }
43}
44
45impl std::error::Error for ApcError {
46    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
47        self.kind.source()
48    }
49}
50
51// Forward From impls to ApiError from ApiErrorKind. Because From is reflexive,
52// this impl also takes care of From<ApiErrorKind>.
53impl<T> From<T> for ApcError
54where
55    ApcErrorKind: From<T>,
56{
57    fn from(item: T) -> Self {
58        ApcError {
59            kind: ApcErrorKind::from(item),
60            backtrace: Box::new(Backtrace::new()), // or std::backtrace::Backtrace::capture()
61        }
62    }
63}
64
65/// Return a structured response error for the ApcError
66impl ResponseError for ApcError {
67    fn status_code(&self) -> StatusCode {
68        self.kind.status()
69    }
70
71    fn error_response(&self) -> HttpResponse {
72        let mut builder = HttpResponse::build(self.status_code());
73        builder.json(self)
74    }
75}
76
77impl Serialize for ApcError {
78    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
79    where
80        S: Serializer,
81    {
82        let status = self.kind.status();
83        let mut map = serializer.serialize_map(Some(5))?;
84
85        map.serialize_entry("code", &status.as_u16())?;
86        map.serialize_entry("error", &status.canonical_reason())?;
87        map.serialize_entry("message", &self.kind.to_string())?;
88        // TODO: errno and url?
89        map.end()
90    }
91}
92
93#[derive(Error, Debug)]
94pub enum ApcErrorKind {
95    #[error(transparent)]
96    Io(#[from] io::Error),
97    #[error(transparent)]
98    UuidError(#[from] uuid::Error),
99    #[error(transparent)]
100    ParseIntError(#[from] num::ParseIntError),
101    #[error(transparent)]
102    ParseUrlError(#[from] url::ParseError),
103    #[error(transparent)]
104    ConfigError(#[from] config::ConfigError),
105    #[cfg(feature = "reliable_report")]
106    #[error(transparent)]
107    RedisError(#[from] RedisError),
108    #[error("Broadcast Error: {0}")]
109    BroadcastError(String),
110    #[error("Payload Error: {0}")]
111    PayloadError(String),
112    #[error("General Error: {0}")]
113    GeneralError(String),
114    #[error("Client not connected")]
115    ClientNotConnected,
116    #[error("Client notification channel full")]
117    ChannelFull,
118}
119
120impl ApcErrorKind {
121    /// Get the associated HTTP status code
122    pub fn status(&self) -> StatusCode {
123        match self {
124            Self::ParseIntError(_) | Self::ParseUrlError(_) => StatusCode::BAD_REQUEST,
125            _ => StatusCode::INTERNAL_SERVER_ERROR,
126        }
127    }
128
129    pub fn is_sentry_event(&self) -> bool {
130        match self {
131            // TODO: Add additional messages to ignore here.
132            // Non-actionable Endpoint errors
133            Self::PayloadError(_) => false,
134            // Backpressure on bounded channels — expected under load, track via metric
135            Self::ChannelFull => false,
136            _ => true,
137        }
138    }
139
140    pub fn metric_label(&self) -> Option<&'static str> {
141        // TODO: add labels for skipped stuff
142        match self {
143            Self::PayloadError(_) => Some("payload"),
144            Self::ChannelFull => Some("client.channel.full"),
145            _ => None,
146        }
147    }
148}
149
150/// Interface for reporting our Error types to Sentry or as metrics
151pub trait ReportableError: std::error::Error {
152    /// Like [Error::source] but returns the source (if any) of this error as a
153    /// [ReportableError] if it implements the trait. Otherwise callers of this
154    /// method will likely subsequently call [Error::source] to return the
155    /// source (if any) as the parent [Error] trait.
156    fn reportable_source(&self) -> Option<&(dyn ReportableError + 'static)> {
157        None
158    }
159
160    /// Return a `Backtrace` for this Error if one was captured
161    fn backtrace(&self) -> Option<&Backtrace> {
162        None
163    }
164    /// Whether this error is reported to Sentry
165    fn is_sentry_event(&self) -> bool {
166        true
167    }
168
169    /// Errors that don't emit Sentry events (!is_sentry_event()) emit an
170    /// increment metric instead with this label
171    fn metric_label(&self) -> Option<&'static str> {
172        None
173    }
174
175    /// Experimental: return tag key value pairs for metrics and Sentry
176    fn tags(&self) -> Vec<(&str, String)> {
177        vec![]
178    }
179
180    /// Experimental: return key value pairs for Sentry Event's extra data
181    /// TODO: should probably return Vec<(&str, Value)> or Vec<(String, Value)>
182    fn extras(&self) -> Vec<(&str, String)> {
183        vec![]
184    }
185}
186
187impl ReportableError for ApcError {
188    fn backtrace(&self) -> Option<&Backtrace> {
189        Some(&self.backtrace)
190    }
191
192    fn is_sentry_event(&self) -> bool {
193        self.kind.is_sentry_event()
194    }
195
196    fn metric_label(&self) -> Option<&'static str> {
197        self.kind.metric_label()
198    }
199}