Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
use super::registration::AnyErrorRegistration;
|
||||
|
||||
/// A version of [`ErrorExt`] that works for [`anyhow::Error`] (which does not
|
||||
/// implement [`std::error::Error`]).
|
||||
pub trait AnyhowErrorExt {
|
||||
/// Returns whether or not an error is something that is actionable by our
|
||||
/// engineering team.
|
||||
fn is_actionable(&self) -> bool;
|
||||
|
||||
/// Reports the error.
|
||||
fn report_error(&self);
|
||||
}
|
||||
|
||||
impl AnyhowErrorExt for anyhow::Error {
|
||||
fn is_actionable(&self) -> bool {
|
||||
for cause in self.chain() {
|
||||
for imp in inventory::iter::<&'static dyn AnyErrorRegistration>() {
|
||||
if imp.downcast_and_is_actionable(cause) == Some(false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn report_error(&self) {
|
||||
#[cfg(feature = "crash_reporting")]
|
||||
sentry::integrations::anyhow::capture_anyhow(self);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::ErrorExt;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! register_error {
|
||||
($error:ty) => {
|
||||
impl $crate::errors::RegisteredError for $error {}
|
||||
|
||||
$crate::errors::submit! {
|
||||
$crate::errors::ErrorRegistration::<$error>::adapt()
|
||||
}
|
||||
};
|
||||
}
|
||||
pub use register_error;
|
||||
|
||||
/// Marker trait for known error events. We rely on this to implement [`ErrorExt`] for [`anyhow::Error`]
|
||||
/// in a way that delegates to errors in the context chain.
|
||||
///
|
||||
/// DO NOT implement this trait directly - use the [`register_error!`] macro instead.
|
||||
pub trait RegisteredError {}
|
||||
|
||||
/// A type-erased version of [`ErrorRegistration`]. This is only used by the
|
||||
/// [`register_error!`] macro implementation.
|
||||
#[doc(hidden)]
|
||||
pub trait AnyErrorRegistration: Sync {
|
||||
// Returns true if
|
||||
fn downcast_and_is_actionable(&self, error: &(dyn std::error::Error + 'static))
|
||||
-> Option<bool>;
|
||||
}
|
||||
|
||||
/// Adapter for statically registering all [`ErrorExt`] implementations.
|
||||
#[doc(hidden)]
|
||||
pub struct ErrorRegistration<T: ErrorExt + 'static> {
|
||||
/// Marker that `ErrorRegistration` references `T`, but doesn't own a `T` value.
|
||||
/// See https://doc.rust-lang.org/nomicon/phantom-data.html
|
||||
_marker: PhantomData<fn(T) -> T>,
|
||||
}
|
||||
|
||||
impl<T: ErrorExt + 'static> ErrorRegistration<T> {
|
||||
pub const fn adapt() -> &'static dyn AnyErrorRegistration {
|
||||
&Self {
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ErrorExt + 'static> AnyErrorRegistration for ErrorRegistration<T> {
|
||||
fn downcast_and_is_actionable(
|
||||
&self,
|
||||
error: &(dyn std::error::Error + 'static),
|
||||
) -> Option<bool> {
|
||||
let err = error.downcast_ref::<T>()?;
|
||||
Some(err.is_actionable())
|
||||
}
|
||||
}
|
||||
|
||||
// Collect adapters for all registered error types. Because `inventory::collect!` requires a
|
||||
// concrete type, we use `&static dyn Trait` to erase the generics.
|
||||
inventory::collect!(&'static dyn AnyErrorRegistration);
|
||||
@@ -0,0 +1,48 @@
|
||||
use http::StatusCode;
|
||||
|
||||
use super::{register_error, ErrorExt};
|
||||
|
||||
impl ErrorExt for reqwest::Error {
|
||||
fn is_actionable(&self) -> bool {
|
||||
// Outside of timeouts, there's nothing we can do about errors
|
||||
// that occur prior to the successful receipt of an HTTP
|
||||
// response.
|
||||
|
||||
// There's no way to check for connection errors via web APIs, so
|
||||
// `is_connect` can only be called on native platforms.
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
if self.is_connect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.is_request() || self.is_body() || self.is_decode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we're getting a capacity error from the server, then that should trip a server-side
|
||||
// alert. A duplicate report in Sentry isn't helpful.
|
||||
if self.status() == Some(StatusCode::TOO_MANY_REQUESTS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Internal server errors (5xx) are server-side issues that we can't act upon from the client.
|
||||
if self.status().is_some_and(|status| status.is_server_error()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we're making a request to the staging server and get back
|
||||
// a 403 Forbidden, the user is probably not whitelisted to talk
|
||||
// to staging from their current IP address, so downgrade to a
|
||||
// warning.
|
||||
if let (Some(url), Some(status)) = (self.url(), self.status()) {
|
||||
if let Some(domain) = url.domain() {
|
||||
if domain == "staging.warp.dev" && status == StatusCode::FORBIDDEN {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
register_error!(reqwest::Error);
|
||||
@@ -0,0 +1,19 @@
|
||||
use super::{register_error, ErrorExt};
|
||||
|
||||
impl ErrorExt for tokio::task::JoinError {
|
||||
fn is_actionable(&self) -> bool {
|
||||
// If the task was cancelled (aborted), this is expected behavior and not actionable.
|
||||
if self.is_cancelled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the task panicked, this is actionable - we need to know about panics.
|
||||
if self.is_panic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Other join errors are actionable.
|
||||
true
|
||||
}
|
||||
}
|
||||
register_error!(tokio::task::JoinError);
|
||||
@@ -0,0 +1,25 @@
|
||||
use http::StatusCode;
|
||||
|
||||
use super::{register_error, ErrorExt};
|
||||
|
||||
impl ErrorExt for websocket::tungstenite::Error {
|
||||
fn is_actionable(&self) -> bool {
|
||||
match self {
|
||||
Self::Http(res) => {
|
||||
// Capacity errors from the server aren't actionable client-side.
|
||||
if res.status() == StatusCode::TOO_MANY_REQUESTS {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Internal server errors (5xx) are server-side issues that we can't act upon from the client.
|
||||
if res.status().is_server_error() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
register_error!(websocket::tungstenite::Error);
|
||||
Reference in New Issue
Block a user