web3-proxy/web3_proxy/src/frontend/errors.rs

241 lines
8.7 KiB
Rust
Raw Normal View History

2022-10-18 00:47:58 +03:00
//! Utlities for logging errors for admins and displaying errors to users.
use super::authorization::Authorization;
use crate::jsonrpc::JsonRpcForwardedResponse;
2022-08-10 05:37:34 +03:00
use axum::{
2022-10-27 00:39:26 +03:00
headers,
2022-08-10 05:37:34 +03:00
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
2022-08-17 00:43:39 +03:00
use derive_more::From;
2022-10-27 00:39:26 +03:00
use http::header::InvalidHeaderValue;
use ipnet::AddrParseError;
2022-11-16 10:19:56 +03:00
use log::{trace, warn};
2022-11-14 21:24:52 +03:00
use migration::sea_orm::DbErr;
2022-09-15 20:57:24 +03:00
use redis_rate_limiter::redis::RedisError;
2022-10-27 00:39:26 +03:00
use reqwest::header::ToStrError;
use std::error::Error;
2022-11-03 02:14:16 +03:00
use tokio::{task::JoinError, time::Instant};
2022-06-05 22:58:47 +03:00
2022-10-20 23:26:14 +03:00
// TODO: take "IntoResponse" instead of Response?
2022-08-17 00:43:39 +03:00
pub type FrontendResult = Result<Response, FrontendErrorResponse>;
2022-10-20 23:26:14 +03:00
// TODO:
2022-10-29 01:52:47 +03:00
#[derive(Debug, From)]
2022-08-17 00:43:39 +03:00
pub enum FrontendErrorResponse {
Anyhow(anyhow::Error),
Box(Box<dyn Error>),
2022-08-27 08:42:25 +03:00
Database(DbErr),
2022-10-27 00:39:26 +03:00
HeadersError(headers::Error),
HeaderToString(ToStrError),
InvalidHeaderValue(InvalidHeaderValue),
IpAddrParse(AddrParseError),
2022-11-03 02:14:16 +03:00
JoinError(JoinError),
2022-10-27 00:39:26 +03:00
NotFound,
RateLimited(Authorization, Option<Instant>),
2022-10-27 00:39:26 +03:00
Redis(RedisError),
Response(Response),
2022-10-20 23:26:14 +03:00
/// simple way to return an error message to the user and an anyhow to our logs
StatusCode(StatusCode, String, Option<anyhow::Error>),
2022-10-31 23:05:58 +03:00
UlidDecodeError(ulid::DecodeError),
2022-09-12 17:33:55 +03:00
UnknownKey,
2022-08-16 22:29:00 +03:00
}
2022-08-17 00:43:39 +03:00
impl IntoResponse for FrontendErrorResponse {
2022-08-16 22:29:00 +03:00
fn into_response(self) -> Response {
// TODO: include the request id in these so that users can give us something that will point to logs
let (status_code, response) = match self {
Self::Anyhow(err) => {
2022-11-12 11:24:32 +03:00
warn!("anyhow. err={:?}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
2022-09-10 03:58:33 +03:00
JsonRpcForwardedResponse::from_string(
// TODO: is it safe to expose all of our anyhow strings?
2022-09-10 03:58:33 +03:00
err.to_string(),
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
None,
),
)
}
Self::Box(err) => {
2022-11-12 11:24:32 +03:00
warn!("boxed err={:?}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
JsonRpcForwardedResponse::from_str(
// TODO: make this better. maybe include the error type?
"boxed error!",
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
None,
),
)
}
2022-10-27 00:39:26 +03:00
Self::Database(err) => {
2022-11-12 11:24:32 +03:00
warn!("database err={:?}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
JsonRpcForwardedResponse::from_str(
2022-10-27 00:39:26 +03:00
"database error!",
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
None,
),
)
}
2022-10-27 00:39:26 +03:00
Self::HeadersError(err) => {
2022-11-12 11:24:32 +03:00
warn!("HeadersError {:?}", err);
2022-10-27 00:39:26 +03:00
(
StatusCode::BAD_REQUEST,
JsonRpcForwardedResponse::from_str(
&format!("{}", err),
Some(StatusCode::BAD_REQUEST.as_u16().into()),
None,
),
)
2022-08-21 12:39:38 +03:00
}
2022-10-27 00:39:26 +03:00
Self::IpAddrParse(err) => {
2022-11-12 11:24:32 +03:00
warn!("IpAddrParse err={:?}", err);
2022-10-20 23:26:14 +03:00
(
2022-10-27 00:39:26 +03:00
StatusCode::BAD_REQUEST,
2022-10-20 23:26:14 +03:00
JsonRpcForwardedResponse::from_str(
2022-10-27 00:39:26 +03:00
&format!("{}", err),
Some(StatusCode::BAD_REQUEST.as_u16().into()),
2022-10-20 23:26:14 +03:00
None,
),
)
}
2022-10-27 00:39:26 +03:00
Self::InvalidHeaderValue(err) => {
2022-11-12 11:24:32 +03:00
warn!("InvalidHeaderValue err={:?}", err);
(
2022-10-27 00:39:26 +03:00
StatusCode::BAD_REQUEST,
JsonRpcForwardedResponse::from_str(
2022-10-27 00:39:26 +03:00
&format!("{}", err),
Some(StatusCode::BAD_REQUEST.as_u16().into()),
None,
),
)
}
2022-11-03 02:14:16 +03:00
Self::JoinError(err) => {
2022-11-12 11:24:32 +03:00
warn!("JoinError. likely shutting down. err={:?}", err);
2022-11-03 02:14:16 +03:00
(
StatusCode::INTERNAL_SERVER_ERROR,
JsonRpcForwardedResponse::from_str(
"Unable to complete request",
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
None,
),
)
}
2022-10-27 00:39:26 +03:00
Self::NotFound => {
// TODO: emit a stat?
// TODO: instead of an error, show a normal html page for 404
(
StatusCode::NOT_FOUND,
JsonRpcForwardedResponse::from_str(
"not found!",
Some(StatusCode::NOT_FOUND.as_u16().into()),
None,
),
)
}
// TODO: this should actually by the id of the key. multiple users might control one key
Self::RateLimited(authorization, retry_at) => {
// TODO: emit a stat
let retry_msg = if let Some(retry_at) = retry_at {
let retry_in = retry_at.duration_since(Instant::now()).as_secs();
format!(" Retry in {} seconds", retry_in)
} else {
"".to_string()
};
// create a string with either the IP or the rpc_key_id
let msg = if authorization.checks.rpc_key_id.is_none() {
format!("too many requests from {}.{}", authorization.ip, retry_msg)
} else {
format!(
"too many requests from rpc key #{}.{}",
authorization.checks.rpc_key_id.unwrap(),
retry_msg
)
};
(
StatusCode::TOO_MANY_REQUESTS,
JsonRpcForwardedResponse::from_string(
msg,
Some(StatusCode::TOO_MANY_REQUESTS.as_u16().into()),
None,
),
)
}
2022-10-27 00:39:26 +03:00
Self::Redis(err) => {
2022-11-12 11:24:32 +03:00
warn!("redis err={:?}", err);
2022-10-27 00:39:26 +03:00
(
StatusCode::INTERNAL_SERVER_ERROR,
JsonRpcForwardedResponse::from_str(
"redis error!",
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
None,
),
)
}
Self::Response(r) => {
debug_assert_ne!(r.status(), StatusCode::OK);
return r;
}
Self::StatusCode(status_code, err_msg, err) => {
2022-11-16 10:19:56 +03:00
// different status codes should get different error levels. 500s should warn. 400s should stat
let code = status_code.as_u16();
if (500..600).contains(&code) {
warn!("user error {} {:?}: {:?}", code, err_msg, err);
} else {
trace!("user error {} {:?}: {:?}", code, err_msg, err);
}
2022-10-27 00:39:26 +03:00
(
status_code,
2022-11-16 10:19:56 +03:00
JsonRpcForwardedResponse::from_str(&err_msg, Some(code.into()), None),
2022-10-27 00:39:26 +03:00
)
}
Self::HeaderToString(err) => {
2022-11-12 11:24:32 +03:00
// // trace!(?err, "HeaderToString");
2022-10-27 00:39:26 +03:00
(
StatusCode::BAD_REQUEST,
JsonRpcForwardedResponse::from_str(
&format!("{}", err),
Some(StatusCode::BAD_REQUEST.as_u16().into()),
None,
),
)
}
2022-10-31 23:05:58 +03:00
Self::UlidDecodeError(err) => {
2022-11-12 11:24:32 +03:00
// // trace!(?err, "UlidDecodeError");
2022-10-31 23:05:58 +03:00
(
StatusCode::BAD_REQUEST,
JsonRpcForwardedResponse::from_str(
&format!("{}", err),
Some(StatusCode::BAD_REQUEST.as_u16().into()),
None,
),
)
}
// TODO: stat?
2022-09-12 17:33:55 +03:00
Self::UnknownKey => (
StatusCode::UNAUTHORIZED,
JsonRpcForwardedResponse::from_str(
"unknown api key!",
Some(StatusCode::UNAUTHORIZED.as_u16().into()),
None,
),
),
};
(status_code, Json(response)).into_response()
2022-08-16 22:29:00 +03:00
}
}
2022-06-16 05:53:37 +03:00
2022-08-10 05:37:34 +03:00
pub async fn handler_404() -> Response {
FrontendErrorResponse::NotFound.into_response()
2022-06-05 22:58:47 +03:00
}