error refactor for user endpoints

This commit is contained in:
Bryan Stitt 2022-08-16 19:29:00 +00:00
parent 8ebe7000ad
commit 305d89ddf4
9 changed files with 78 additions and 19 deletions

13
Cargo.lock generated

@ -401,6 +401,18 @@ dependencies = [
"mime", "mime",
] ]
[[package]]
name = "axum-macros"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6293dae2ec708e679da6736e857cf8532886ef258e92930f38279c12641628b8"
dependencies = [
"heck 0.4.0",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.65" version = "0.3.65"
@ -5482,6 +5494,7 @@ dependencies = [
"argh", "argh",
"axum", "axum",
"axum-client-ip", "axum-client-ip",
"axum-macros",
"counter", "counter",
"dashmap", "dashmap",
"derive_more", "derive_more",

@ -21,6 +21,7 @@ arc-swap = "1.5.1"
argh = "0.1.8" argh = "0.1.8"
axum = { version = "0.5.15", features = ["headers", "serde_json", "tokio-tungstenite", "ws"] } axum = { version = "0.5.15", features = ["headers", "serde_json", "tokio-tungstenite", "ws"] }
axum-client-ip = "0.2.0" axum-client-ip = "0.2.0"
axum-macros = "*"
counter = "0.5.6" counter = "0.5.6"
dashmap = "5.3.4" dashmap = "5.3.4"
derive_more = "0.99.17" derive_more = "0.99.17"

@ -1,11 +1,21 @@
use crate::jsonrpc::JsonRpcForwardedResponse;
use axum::{ use axum::{
http::StatusCode, http::StatusCode,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
Json, Json,
}; };
use serde_json::value::RawValue; use serde_json::value::RawValue;
use std::error::Error;
use crate::jsonrpc::JsonRpcForwardedResponse; pub struct ErrorResponse {
pub inner: Box<dyn Error>,
}
impl IntoResponse for ErrorResponse {
fn into_response(self) -> Response {
todo!("into_response based on the error type")
}
}
pub async fn handler_404() -> Response { pub async fn handler_404() -> Response {
let err = anyhow::anyhow!("nothing to see here"); let err = anyhow::anyhow!("nothing to see here");
@ -13,6 +23,7 @@ pub async fn handler_404() -> Response {
anyhow_error_into_response(Some(StatusCode::NOT_FOUND), None, err) anyhow_error_into_response(Some(StatusCode::NOT_FOUND), None, err)
} }
/// TODO: generic error?
/// handle errors by converting them into something that implements `IntoResponse` /// handle errors by converting them into something that implements `IntoResponse`
/// TODO: use this. i can't get <https://docs.rs/axum/latest/axum/error_handling/index.html> to work /// TODO: use this. i can't get <https://docs.rs/axum/latest/axum/error_handling/index.html> to work
/// TODO: i think we want a custom result type instead. put the anyhow result inside. then `impl IntoResponse for CustomResult` /// TODO: i think we want a custom result type instead. put the anyhow result inside. then `impl IntoResponse for CustomResult`

@ -1,9 +1,8 @@
use crate::app::Web3ProxyApp;
use axum::{http::StatusCode, response::IntoResponse, Extension, Json}; use axum::{http::StatusCode, response::IntoResponse, Extension, Json};
use serde_json::json; use serde_json::json;
use std::sync::Arc; use std::sync::Arc;
use crate::app::Web3ProxyApp;
/// Health check page for load balancers to use /// Health check page for load balancers to use
pub async fn health(Extension(app): Extension<Arc<Web3ProxyApp>>) -> impl IntoResponse { pub async fn health(Extension(app): Extension<Arc<Web3ProxyApp>>) -> impl IntoResponse {
if app.balanced_rpcs.synced() { if app.balanced_rpcs.synced() {

@ -1,3 +1,7 @@
use super::errors::anyhow_error_into_response;
use super::rate_limit::RateLimitResult;
use crate::stats::{Protocol, ProxyRequestLabels};
use crate::{app::Web3ProxyApp, jsonrpc::JsonRpcRequestEnum};
use axum::extract::Path; use axum::extract::Path;
use axum::response::Response; use axum::response::Response;
use axum::{http::StatusCode, response::IntoResponse, Extension, Json}; use axum::{http::StatusCode, response::IntoResponse, Extension, Json};
@ -6,11 +10,6 @@ use std::sync::Arc;
use tracing::{error_span, Instrument}; use tracing::{error_span, Instrument};
use uuid::Uuid; use uuid::Uuid;
use super::errors::anyhow_error_into_response;
use super::rate_limit::RateLimitResult;
use crate::stats::{Protocol, ProxyRequestLabels};
use crate::{app::Web3ProxyApp, jsonrpc::JsonRpcRequestEnum};
pub async fn public_proxy_web3_rpc( pub async fn public_proxy_web3_rpc(
Json(payload): Json<JsonRpcRequestEnum>, Json(payload): Json<JsonRpcRequestEnum>,
Extension(app): Extension<Arc<Web3ProxyApp>>, Extension(app): Extension<Arc<Web3ProxyApp>>,

@ -6,10 +6,13 @@ mod rate_limit;
mod users; mod users;
mod ws_proxy; mod ws_proxy;
use ::http::Request; use crate::app::Web3ProxyApp;
use ::http::{Request, StatusCode};
use axum::{ use axum::{
body::Body, body::Body,
error_handling::HandleError,
handler::Handler, handler::Handler,
response::Response,
routing::{get, post}, routing::{get, post},
Extension, Router, Extension, Router,
}; };
@ -19,7 +22,15 @@ use tower_http::trace::TraceLayer;
use tower_request_id::{RequestId, RequestIdLayer}; use tower_request_id::{RequestId, RequestIdLayer};
use tracing::{error_span, info}; use tracing::{error_span, info};
use crate::app::Web3ProxyApp; // handle errors by converting them into something that implements
// `IntoResponse`
async fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) {
// TODO: i dont like this, but lets see if it works. need to moved to the errors module and replace the version that is there
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", err),
)
}
/// http and websocket frontend for customers /// http and websocket frontend for customers
pub async fn serve(port: u16, proxy_app: Arc<Web3ProxyApp>) -> anyhow::Result<()> { pub async fn serve(port: u16, proxy_app: Arc<Web3ProxyApp>) -> anyhow::Result<()> {
@ -42,6 +53,12 @@ pub async fn serve(port: u16, proxy_app: Arc<Web3ProxyApp>) -> anyhow::Result<()
) )
}); });
// create user endpoint needs some work sothat
let some_fallible_service = tower::service_fn(|_req| async {
// thing_that_might_fail().await?;
Ok::<_, anyhow::Error>(Response::new(Body::empty()))
});
// build our application with a route // build our application with a route
// order most to least common // order most to least common
let app = Router::new() let app = Router::new()
@ -51,7 +68,18 @@ pub async fn serve(port: u16, proxy_app: Arc<Web3ProxyApp>) -> anyhow::Result<()
.route("/u/:user_key", get(ws_proxy::user_websocket_handler)) .route("/u/:user_key", get(ws_proxy::user_websocket_handler))
.route("/health", get(http::health)) .route("/health", get(http::health))
.route("/status", get(http::status)) .route("/status", get(http::status))
// .route(
// "/login",
// HandleError::new(
// move |app| async { users::get_login(app).await },
// handle_anyhow_error,
// ),
// )
.route("/users", post(users::create_user)) .route("/users", post(users::create_user))
.route(
"/foo",
HandleError::new(some_fallible_service, handle_anyhow_error),
)
.layer(Extension(proxy_app)) .layer(Extension(proxy_app))
// create a unique id for each request and add it to our tracing logs // create a unique id for each request and add it to our tracing logs
.layer(request_tracing_layer) .layer(request_tracing_layer)

@ -1,3 +1,5 @@
use super::errors::anyhow_error_into_response;
use crate::app::{UserCacheValue, Web3ProxyApp};
use axum::response::Response; use axum::response::Response;
use entities::user_keys; use entities::user_keys;
use redis_rate_limit::ThrottleResult; use redis_rate_limit::ThrottleResult;
@ -10,10 +12,6 @@ use tokio::time::Instant;
use tracing::{debug, warn}; use tracing::{debug, warn};
use uuid::Uuid; use uuid::Uuid;
use crate::app::{UserCacheValue, Web3ProxyApp};
use super::errors::anyhow_error_into_response;
pub enum RateLimitResult { pub enum RateLimitResult {
AllowedIp(IpAddr), AllowedIp(IpAddr),
AllowedUser(u64), AllowedUser(u64),

@ -7,11 +7,14 @@
// I wonder how we handle payment // I wonder how we handle payment
// probably have to do manual withdrawals // probably have to do manual withdrawals
use super::{errors::anyhow_error_into_response, rate_limit::RateLimitResult};
use crate::app::Web3ProxyApp;
use axum::{ use axum::{
response::{IntoResponse, Response}, response::{IntoResponse, Response},
Extension, Json, Extension, Json,
}; };
use axum_client_ip::ClientIp; use axum_client_ip::ClientIp;
use axum_macros::debug_handler;
use entities::user; use entities::user;
use ethers::{prelude::Address, types::Bytes}; use ethers::{prelude::Address, types::Bytes};
use reqwest::StatusCode; use reqwest::StatusCode;
@ -19,9 +22,15 @@ use sea_orm::ActiveModelTrait;
use serde::Deserialize; use serde::Deserialize;
use std::sync::Arc; use std::sync::Arc;
use crate::app::Web3ProxyApp; #[debug_handler]
pub async fn get_login(Extension(app): Extension<Arc<Web3ProxyApp>>) -> Result<Response, Response> {
// let redis: RedisPool = app...;
let redis_pool = app.redis_pool.as_ref().unwrap();
use super::{errors::anyhow_error_into_response, rate_limit::RateLimitResult}; let redis_conn = redis_pool.get().await.unwrap();
todo!("how should this work? probably keep stuff in redis ")
}
pub async fn create_user( pub async fn create_user(
// this argument tells axum to parse the request body // this argument tells axum to parse the request body
@ -30,13 +39,14 @@ pub async fn create_user(
Extension(app): Extension<Arc<Web3ProxyApp>>, Extension(app): Extension<Arc<Web3ProxyApp>>,
ClientIp(ip): ClientIp, ClientIp(ip): ClientIp,
) -> Response { ) -> Response {
// TODO: return a Result instead
let _ip = match app.rate_limit_by_ip(ip).await { let _ip = match app.rate_limit_by_ip(ip).await {
Ok(x) => match x.try_into_response().await { Ok(x) => match x.try_into_response().await {
Ok(RateLimitResult::AllowedIp(x)) => x, Ok(RateLimitResult::AllowedIp(x)) => x,
Err(err_response) => return err_response, Err(err_response) => return err_response,
_ => unimplemented!(), _ => unimplemented!(),
}, },
Err(err) => return anyhow_error_into_response(None, None, err).into_response(), Err(err) => return anyhow_error_into_response(None, None, err),
}; };
// TODO: check invite_code against the app's config or database // TODO: check invite_code against the app's config or database
@ -65,7 +75,7 @@ pub async fn create_user(
// TODO: proper error message // TODO: proper error message
let user = user.insert(db).await.unwrap(); let user = user.insert(db).await.unwrap();
// // TODO: create
// TODO: do not expose user ids // TODO: do not expose user ids
(StatusCode::CREATED, Json(user)).into_response() (StatusCode::CREATED, Json(user)).into_response()

@ -193,7 +193,7 @@ impl JsonRpcForwardedResponse {
id, id,
result: None, result: None,
error: Some(JsonRpcErrorData { error: Some(JsonRpcErrorData {
// TODO: set this jsonrpc error code to match the http status code? or maybe the other way around? // TODO: set this jsonrpc error code to match the http status code? or maybe the other way around? maybe take it as an arg
code: -32099, code: -32099,
// TODO: some errors should be included here. others should not. i think anyhow might not be the right choice // TODO: some errors should be included here. others should not. i think anyhow might not be the right choice
// message: "internal server error".to_string(), // message: "internal server error".to_string(),