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

140 lines
5.0 KiB
Rust
Raw Normal View History

use crate::{app::UserKeyData, jsonrpc::JsonRpcForwardedResponse};
2022-08-10 05:37:34 +03:00
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
2022-08-17 00:43:39 +03:00
use derive_more::From;
2022-09-15 20:57:24 +03:00
use redis_rate_limiter::redis::RedisError;
2022-08-27 08:42:25 +03:00
use sea_orm::DbErr;
use std::{error::Error, net::IpAddr};
use tokio::time::Instant;
use tracing::warn;
2022-06-05 22:58:47 +03:00
2022-08-17 00:43:39 +03:00
// TODO: take "IntoResult" instead?
pub type FrontendResult = Result<Response, FrontendErrorResponse>;
#[derive(From)]
pub enum FrontendErrorResponse {
Anyhow(anyhow::Error),
Box(Box<dyn Error>),
Redis(RedisError),
2022-08-21 12:39:38 +03:00
Response(Response),
2022-08-27 08:42:25 +03:00
Database(DbErr),
RateLimitedUser(UserKeyData, Option<Instant>),
RateLimitedIp(IpAddr, Option<Instant>),
2022-09-12 17:33:55 +03:00
UnknownKey,
NotFound,
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) => {
warn!(?err, "anyhow");
(
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) => {
warn!(?err, "boxed");
(
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,
),
)
}
Self::Redis(err) => {
warn!(?err, "redis");
(
StatusCode::INTERNAL_SERVER_ERROR,
JsonRpcForwardedResponse::from_str(
"redis error!",
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
None,
),
)
}
2022-08-21 12:39:38 +03:00
Self::Response(r) => {
debug_assert_ne!(r.status(), StatusCode::OK);
2022-08-21 12:39:38 +03:00
return r;
}
Self::Database(err) => {
warn!(?err, "database");
(
StatusCode::INTERNAL_SERVER_ERROR,
JsonRpcForwardedResponse::from_str(
"database error!",
Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()),
None,
),
)
}
Self::RateLimitedIp(ip, _retry_at) => {
// TODO: emit a stat
// TODO: include retry_at in the error
// TODO: if retry_at is None, give an unauthorized status code?
(
StatusCode::TOO_MANY_REQUESTS,
JsonRpcForwardedResponse::from_string(
format!("too many requests from ip {}!", ip),
Some(StatusCode::TOO_MANY_REQUESTS.as_u16().into()),
None,
),
)
}
// TODO: this should actually by the id of the key. multiple users might control one key
Self::RateLimitedUser(user_data, _retry_at) => {
// TODO: emit a stat
// TODO: include retry_at in the error
(
StatusCode::TOO_MANY_REQUESTS,
JsonRpcForwardedResponse::from_string(
// TODO: better error
format!("too many requests from {:?}!", user_data),
Some(StatusCode::TOO_MANY_REQUESTS.as_u16().into()),
None,
),
)
}
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,
),
),
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,
),
)
}
};
(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
}