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}
115
116impl ApcErrorKind {
117    /// Get the associated HTTP status code
118    pub fn status(&self) -> StatusCode {
119        match self {
120            Self::ParseIntError(_) | Self::ParseUrlError(_) => StatusCode::BAD_REQUEST,
121            _ => StatusCode::INTERNAL_SERVER_ERROR,
122        }
123    }
124
125    pub fn is_sentry_event(&self) -> bool {
126        match self {
127            // TODO: Add additional messages to ignore here.
128            // Non-actionable Endpoint errors
129            Self::PayloadError(_) => false,
130            _ => true,
131        }
132    }
133
134    pub fn metric_label(&self) -> Option<&'static str> {
135        // TODO: add labels for skipped stuff
136        match self {
137            Self::PayloadError(_) => Some("payload"),
138            _ => None,
139        }
140    }
141}
142
143/// Interface for reporting our Error types to Sentry or as metrics
144pub trait ReportableError: std::error::Error {
145    /// Like [Error::source] but returns the source (if any) of this error as a
146    /// [ReportableError] if it implements the trait. Otherwise callers of this
147    /// method will likely subsequently call [Error::source] to return the
148    /// source (if any) as the parent [Error] trait.
149    fn reportable_source(&self) -> Option<&(dyn ReportableError + 'static)> {
150        None
151    }
152
153    /// Return a `Backtrace` for this Error if one was captured
154    fn backtrace(&self) -> Option<&Backtrace> {
155        None
156    }
157    /// Whether this error is reported to Sentry
158    fn is_sentry_event(&self) -> bool {
159        true
160    }
161
162    /// Errors that don't emit Sentry events (!is_sentry_event()) emit an
163    /// increment metric instead with this label
164    fn metric_label(&self) -> Option<&'static str> {
165        None
166    }
167
168    /// Experimental: return tag key value pairs for metrics and Sentry
169    fn tags(&self) -> Vec<(&str, String)> {
170        vec![]
171    }
172
173    /// Experimental: return key value pairs for Sentry Event's extra data
174    /// TODO: should probably return Vec<(&str, Value)> or Vec<(String, Value)>
175    fn extras(&self) -> Vec<(&str, String)> {
176        vec![]
177    }
178}
179
180impl ReportableError for ApcError {
181    fn backtrace(&self) -> Option<&Backtrace> {
182        Some(&self.backtrace)
183    }
184
185    fn is_sentry_event(&self) -> bool {
186        self.kind.is_sentry_event()
187    }
188
189    fn metric_label(&self) -> Option<&'static str> {
190        self.kind.metric_label()
191    }
192}