diff --git a/nodes/node/binary/src/api/errors.rs b/nodes/node/binary/src/api/errors.rs index f5aefa41c..ce6bc219d 100644 --- a/nodes/node/binary/src/api/errors.rs +++ b/nodes/node/binary/src/api/errors.rs @@ -1,10 +1,84 @@ -use axum::response::{IntoResponse, Response}; +use axum::{ + Json, + response::{IntoResponse, Response}, +}; use http::StatusCode; use lb_api_service::http::DynError; +use serde::Serialize; + +#[derive(Debug, thiserror::Error)] +pub enum ApiError { + #[error("{0}")] + BadRequest(String), + #[error("{0}")] + NotFound(String), + #[error("Not found")] + NotFoundEmpty, + #[error("Internal server error")] + InternalServerError, + #[error(transparent)] + Internal(#[from] DynError), +} + +impl ApiError { + pub fn internal(error: impl std::error::Error + Send + Sync + 'static) -> Self { + Self::Internal(Box::new(error)) + } + + pub fn internal_message(message: impl Into) -> Self { + Self::Internal(DynError::from(message.into())) + } +} + +/// Body returned for every API error response, mirroring the +/// `{code, message}` envelope used by the Ethereum Beacon API. +#[derive(Debug, Serialize, utoipa::ToSchema)] +pub struct ErrorBody { + pub code: u16, + pub message: String, +} + +fn error_response(status: StatusCode, message: String) -> Response { + ( + status, + Json(ErrorBody { + code: status.as_u16(), + message, + }), + ) + .into_response() +} + +impl IntoResponse for ApiError { + fn into_response(self) -> Response { + match self { + Self::BadRequest(message) => error_response(StatusCode::BAD_REQUEST, message), + Self::NotFound(message) => error_response(StatusCode::NOT_FOUND, message), + error @ Self::NotFoundEmpty => error_response(StatusCode::NOT_FOUND, error.to_string()), + error @ Self::InternalServerError => { + error_response(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()) + } + Self::Internal(error) => { + error_response(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()) + } + } + } +} + +pub fn json_response(result: Result) -> Response +where + T: Serialize, + E: Into, +{ + match result { + Ok(value) => (StatusCode::OK, Json(value)).into_response(), + Err(error) => error.into().into_response(), + } +} impl IntoResponse for BlocksStreamRequestError { fn into_response(self) -> Response { - (StatusCode::BAD_REQUEST, self.to_string()).into_response() + ApiError::BadRequest(self.to_string()).into_response() } } @@ -32,7 +106,7 @@ pub enum BlocksStreamWindowError { impl IntoResponse for BlocksStreamWindowError { fn into_response(self) -> Response { - (StatusCode::BAD_REQUEST, self.to_string()).into_response() + ApiError::BadRequest(self.to_string()).into_response() } } @@ -54,9 +128,126 @@ impl IntoResponse for BlocksStreamHandlerError { match self { Self::Query(err) => err.into_response(), Self::InvalidWindow(err) => err.into_response(), - Self::Internal(err) => { - (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response() - } + Self::Internal(err) => ApiError::Internal(err).into_response(), } } } + +#[cfg(test)] +mod tests { + use axum::body; + use http::header::CONTENT_TYPE; + + use super::*; + + #[tokio::test] + async fn api_error_maps_variants_to_status_codes() { + let cases = [ + ( + ApiError::BadRequest("bad request".into()), + StatusCode::BAD_REQUEST, + ), + ( + ApiError::NotFound("not found".into()), + StatusCode::NOT_FOUND, + ), + (ApiError::NotFoundEmpty, StatusCode::NOT_FOUND), + ( + ApiError::InternalServerError, + StatusCode::INTERNAL_SERVER_ERROR, + ), + ]; + + for (error, expected_status) in cases { + assert_eq!(error.into_response().status(), expected_status); + } + } + + async fn envelope_of(response: Response) -> (StatusCode, serde_json::Value) { + let status = response.status(); + let content_type = response + .headers() + .get(CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .map(ToOwned::to_owned); + let body = body::to_bytes(response.into_body(), usize::MAX) + .await + .expect("response body should be readable"); + + assert_eq!(content_type.as_deref(), Some("application/json")); + let envelope = serde_json::from_slice(&body).expect("body should be valid JSON"); + (status, envelope) + } + + #[tokio::test] + async fn bad_request_returns_json_envelope() { + let response = ApiError::BadRequest("invalid query".into()).into_response(); + let (status, envelope) = envelope_of(response).await; + + assert_eq!(status, StatusCode::BAD_REQUEST); + assert_eq!( + envelope, + serde_json::json!({ "code": 400, "message": "invalid query" }) + ); + } + + #[tokio::test] + async fn not_found_returns_json_envelope() { + let response = ApiError::NotFound("Block not found".into()).into_response(); + let (status, envelope) = envelope_of(response).await; + + assert_eq!(status, StatusCode::NOT_FOUND); + assert_eq!( + envelope, + serde_json::json!({ "code": 404, "message": "Block not found" }) + ); + } + + #[tokio::test] + async fn generic_internal_error_returns_json_envelope() { + let response = ApiError::InternalServerError.into_response(); + let (status, envelope) = envelope_of(response).await; + + assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!( + envelope, + serde_json::json!({ "code": 500, "message": "Internal server error" }) + ); + } + + #[tokio::test] + async fn internal_error_returns_json_envelope() { + let response = ApiError::from(DynError::from("service unavailable")).into_response(); + let (status, envelope) = envelope_of(response).await; + + assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!( + envelope, + serde_json::json!({ "code": 500, "message": "service unavailable" }) + ); + } + + #[tokio::test] + async fn empty_not_found_returns_json_envelope() { + let response = ApiError::NotFoundEmpty.into_response(); + let (status, envelope) = envelope_of(response).await; + + assert_eq!(status, StatusCode::NOT_FOUND); + assert_eq!( + envelope, + serde_json::json!({ "code": 404, "message": "Not found" }) + ); + } + + #[tokio::test] + async fn json_response_preserves_success_response() { + let response = json_response::<_, ApiError>(Ok(vec![1, 2, 3])); + let status = response.status(); + let body = body::to_bytes(response.into_body(), usize::MAX) + .await + .expect("response body should be readable"); + + assert_eq!(status, StatusCode::OK); + assert_eq!(body, "[1,2,3]"); + } +} diff --git a/nodes/node/binary/src/api/handlers.rs b/nodes/node/binary/src/api/handlers.rs index 6d2543299..0c9b7d919 100644 --- a/nodes/node/binary/src/api/handlers.rs +++ b/nodes/node/binary/src/api/handlers.rs @@ -79,10 +79,10 @@ use tracing::debug; use crate::{ TimeService, api::{ - errors::{BlocksStreamHandlerError, BlocksStreamWindowError}, + errors::{ApiError, BlocksStreamHandlerError, BlocksStreamWindowError}, openapi::schema, queries::{BlockRangeQuery, BlocksStreamRequest}, - responses::{self, overwatch::get_relay_or_500}, + responses::{self, overwatch::get_relay}, serializers::{ blocks::{ApiBlock, ApiBlockOwned, ApiProcessedBlockEventOwned}, transactions::ApiSignedTransaction, @@ -351,18 +351,7 @@ where #[macro_export] macro_rules! make_request_and_return_response { - ($cond:expr) => {{ - match $cond.await { - ::std::result::Result::Ok(val) => ::axum::response::IntoResponse::into_response(( - ::axum::http::StatusCode::OK, - ::axum::Json(val), - )), - ::std::result::Result::Err(e) => ::axum::response::IntoResponse::into_response(( - ::axum::http::StatusCode::INTERNAL_SERVER_ERROR, - e.to_string(), - )), - } - }}; + ($cond:expr) => {{ $crate::api::errors::json_response($cond.await) }}; } #[utoipa::path( @@ -370,7 +359,7 @@ macro_rules! make_request_and_return_response { path = paths::MANTLE_METRICS, responses( (status = 200, description = "Get the mempool metrics of the cl service", body = inline(schema::MempoolMetrics)), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn mantle_metrics( @@ -423,7 +412,7 @@ where path = paths::MANTLE_STATUS, responses( (status = 200, description = "Query the mempool status of the cl service", body = Vec<::Hash>), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn mantle_status( @@ -483,7 +472,7 @@ pub struct CryptarchiaInfoQuery { path = paths::CRYPTARCHIA_INFO, responses( (status = 200, description = "Query consensus information", body = lb_consensus::CryptarchiaInfo), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn cryptarchia_info( @@ -501,7 +490,7 @@ where path = paths::TIME_INFO, responses( (status = 200, description = "Query time service information", body = TimeInfo), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn time_info( @@ -513,12 +502,12 @@ where let relay = match handle.relay::().await { Ok(relay) => relay, Err(error) => { - return (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(); + return ApiError::internal(error).into_response(); } }; let (sender, receiver) = oneshot::channel(); if let Err((error, _)) = relay.send(TimeServiceMessage::Info { sender }).await { - return (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(); + return ApiError::internal(error).into_response(); } match receiver.await { Ok(Ok(service_info)) => { @@ -530,8 +519,8 @@ where }; (StatusCode::OK, Json(api_info)).into_response() } - Ok(Err(error)) => (StatusCode::INTERNAL_SERVER_ERROR, error).into_response(), - Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(), + Ok(Err(error)) => ApiError::internal_message(error).into_response(), + Err(error) => ApiError::internal(error).into_response(), } } @@ -540,7 +529,7 @@ where path = paths::CRYPTARCHIA_HEADERS, responses( (status = 200, description = "Query header ids", body = Vec), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn cryptarchia_headers( @@ -562,7 +551,7 @@ where path = paths::CRYPTARCHIA_LIB_STREAM, responses( (status = 200, description = "Request a stream for lib blocks"), - (status = 500, description = "Internal server error", body = StreamBody), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn cryptarchia_lib_stream( @@ -575,7 +564,7 @@ where let stream = mantle::lib_block_stream(&handle).await; match stream { Ok(stream) => responses::ndjson::from_stream_result(stream), - Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(), + Err(error) => ApiError::Internal(error).into_response(), } } @@ -584,7 +573,7 @@ where path = paths::NETWORK_INFO, responses( (status = 200, description = "Query the network information", body = lb_network_service::backends::libp2p::Libp2pInfo), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn libp2p_info( @@ -606,7 +595,7 @@ where request_body = DialPeerRequestBody, responses( (status = 200, description = "Dial a network peer", body = PeerId), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn dial_peer( @@ -632,7 +621,7 @@ where path = paths::BLEND_NETWORK_INFO, responses( (status = 200, description = "Query the blend network information", body = Option>), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn blend_info( @@ -660,7 +649,7 @@ where request_body = BlendJoinNetworkRequestBody, responses( (status = 200, description = "Join the blend network", body = Option), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn blend_join_network( @@ -688,7 +677,7 @@ where path = paths::MEMPOOL_ADD_TX, responses( (status = 200, description = "Add transaction to the mempool"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn add_tx( @@ -750,7 +739,7 @@ where path = paths::MEMPOOL_VIEW, responses( (status = 200, description = "Get current tip mempool transaction hashes", body = Vec), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn mempool_view( @@ -926,7 +915,7 @@ where path = paths::CHANNEL, responses( (status = 200, description = "Channel state"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn channel( @@ -945,7 +934,7 @@ where path = paths::CHANNEL_DEPOSIT, responses( (status = 200, description = "Submit a channel deposit"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn channel_deposit( @@ -1045,7 +1034,7 @@ where path = paths::SDP_POST_DECLARATION, responses( (status = 200, description = "Post declaration to SDP service", body = lb_core::sdp::DeclarationId), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn post_declaration< @@ -1093,7 +1082,7 @@ where path = paths::SDP_POST_ACTIVITY, responses( (status = 200, description = "Post activity to SDP service"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn post_activity< @@ -1141,7 +1130,7 @@ where path = paths::SDP_POST_WITHDRAWAL, responses( (status = 200, description = "Post withdrawal to SDP service"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn post_withdrawal< @@ -1189,7 +1178,7 @@ where path = paths::SDP_POST_SET_DECLARATION_ID, responses( (status = 200, description = "Post declaration to SDP service to be set as current", body = lb_core::sdp::DeclarationId), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn post_set_declaration_id< @@ -1239,7 +1228,7 @@ where path = paths::MANTLE_SDP_DECLARATIONS, responses( (status = 200, description = "Get current SDP declarations keyed by declaration id", body = std::collections::HashMap), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn get_sdp_declarations( @@ -1257,7 +1246,7 @@ where path = paths::MANTLE_SDP_SNAPSHOT, responses( (status = 200, description = "Get the SDP snapshot for the current epoch keyed by declaration id", body = std::collections::HashMap), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn get_sdp_snapshot( @@ -1275,7 +1264,7 @@ where path = paths::LEADER_CLAIM, responses( (status = 200, description = "Leader claim transaction submitted", body = lb_api_service::http::consensus::leader::LeaderClaimResponseBody), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn leader_claim( @@ -1294,7 +1283,7 @@ where params(BlockRangeQuery), responses( (status = 200, description = "Get blocks"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn immutable_blocks( @@ -1330,8 +1319,8 @@ where path = paths::BLOCKS_DETAIL, responses( (status = 200, description = "Block found"), - (status = 404, description = "Block not found"), - (status = 500, description = "Internal server error", body = String), + (status = 404, description = "Block not found", body = ErrorBody), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn block( @@ -1343,9 +1332,9 @@ where RuntimeServiceId: AsServiceId> + Debug + Sync + Display, { - let relay = match get_relay_or_500(&handle).await { + let relay = match get_relay(&handle).await { Ok(relay) => relay, - Err(error_response) => return error_response, + Err(error) => return error.into_response(), }; let block = HttpStorageAdapter::get_block::>(relay, id).await; match block { @@ -1353,8 +1342,8 @@ where let api_block = ApiBlock::from(&block); (StatusCode::OK, Json(api_block)).into_response() } - Ok(None) => (StatusCode::NOT_FOUND,).into_response(), - Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response(), + Ok(None) => ApiError::NotFoundEmpty.into_response(), + Err(_) => ApiError::InternalServerError.into_response(), } } @@ -1363,8 +1352,8 @@ where path = paths::BLOCK_EVENTS, responses( (status = 200, description = "Block events", body = Events), - (status = 404, description = "Block not found"), - (status = 500, description = "Internal server error", body = String), + (status = 404, description = "Block not found", body = ErrorBody), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn block_events( @@ -1375,17 +1364,17 @@ where RuntimeServiceId: AsServiceId> + Debug + Sync + Display + Send + 'static, { - let relay = match get_relay_or_500(&handle).await { + let relay = match get_relay(&handle).await { Ok(relay) => relay, - Err(error_response) => return error_response, + Err(error) => return error.into_response(), }; let chain_api = CryptarchiaServiceApi::, RuntimeServiceId>::new(relay); match chain_api.get_block_events(id).await { Ok(Some(events)) => (StatusCode::OK, Json(events)).into_response(), - Ok(None) => (StatusCode::NOT_FOUND, "Block not found").into_response(), - Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response(), + Ok(None) => ApiError::NotFound("Block not found".into()).into_response(), + Err(_) => ApiError::InternalServerError.into_response(), } } @@ -1399,7 +1388,7 @@ pub struct GasPricesQuery { path = paths::MANTLE_GAS_PRICES, responses( (status = 200, description = "Get the gas prices from the ledger state at the tip"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn get_gas_prices( @@ -1410,9 +1399,9 @@ where RuntimeServiceId: AsServiceId> + Debug + Sync + Display + Send + 'static, { - let relay = match get_relay_or_500(&handle).await { + let relay = match get_relay(&handle).await { Ok(relay) => relay, - Err(error_response) => return error_response, + Err(error) => return error.into_response(), }; let chain_api = CryptarchiaServiceApi::, RuntimeServiceId>::new(relay); @@ -1422,7 +1411,7 @@ where None => match consensus::cryptarchia_info::(&handle).await { Ok(info) => info.cryptarchia_info.tip, Err(error) => { - return (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(); + return ApiError::Internal(error).into_response(); } }, }; @@ -1437,8 +1426,8 @@ where }) .into_response() } - Ok(None) => (StatusCode::NOT_FOUND, "Ledger state not found for block").into_response(), - Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(), + Ok(None) => ApiError::NotFound("Ledger state not found for block".into()).into_response(), + Err(error) => ApiError::internal(error).into_response(), } } @@ -1447,7 +1436,7 @@ where path = paths::BLOCKS_STREAM, responses( (status = 200, description = "Stream of processed blocks with chain state"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn blocks_stream( @@ -1473,7 +1462,7 @@ where .map(|stream| stream.map(ApiProcessedBlockEventOwned::from)); match stream { Ok(stream) => responses::ndjson::from_stream(stream), - Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(), + Err(error) => ApiError::Internal(error).into_response(), } } @@ -1485,8 +1474,8 @@ where (status = 200, description = "Stream of processed blocks with chain state in slot order. \ When immutable_only=true and slot_to is omitted, the stream anchors at LIB slot by \ default."), - (status = 400, description = "Invalid request parameters", body = String), - (status = 500, description = "Internal server error", body = String), + (status = 400, description = "Invalid request parameters", body = ErrorBody), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn blocks_range_stream( @@ -1571,8 +1560,8 @@ where path = paths::TRANSACTION, responses( (status = 200, description = "Transaction found"), - (status = 404, description = "Transaction not found"), - (status = 500, description = "Internal server error", body = String), + (status = 404, description = "Transaction not found", body = ErrorBody), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn transaction( @@ -1584,28 +1573,26 @@ where RuntimeServiceId: AsServiceId> + Debug + Sync + Display, { - let relay = match get_relay_or_500(&handle).await { + let relay = match get_relay(&handle).await { Ok(relay) => relay, - Err(error_response) => return error_response, + Err(error) => return error.into_response(), }; let Ok(transactions) = HttpStorageAdapter::get_transactions::>(relay, id).await else { - return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response(); + return ApiError::InternalServerError.into_response(); }; match transactions.as_slice() { - [] => (StatusCode::NOT_FOUND,).into_response(), + [] => ApiError::NotFoundEmpty.into_response(), [transaction] => { let api_transaction = ApiSignedTransaction::from(transaction); (StatusCode::OK, Json(api_transaction)).into_response() } - _ => { - let error_body = serde_json::json!({ - "error": "Multiple transactions found", - "len": transactions.len() - }); - (StatusCode::INTERNAL_SERVER_ERROR, Json(error_body)).into_response() - } + _ => ApiError::internal_message(format!( + "Multiple transactions found ({})", + transactions.len() + )) + .into_response(), } } @@ -1631,7 +1618,7 @@ pub mod wallet { path = paths::wallet::BALANCE, responses( (status = 200, description = "Get wallet balance"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn get_balance( @@ -1644,9 +1631,9 @@ pub mod wallet { RuntimeServiceId: Debug + Send + Sync + Display + 'static + AsServiceId, { let wallet_api = { - let wallet_relay = match get_relay_or_500::(&handle).await { + let wallet_relay = match get_relay::(&handle).await { Ok(relay) => relay, - Err(error_response) => return error_response, + Err(error) => return error.into_response(), }; WalletApi::::new(wallet_relay) }; @@ -1663,12 +1650,11 @@ pub mod wallet { address, } .into_response(), - Ok(lb_wallet_service::TipResponse { response: None, .. }) => ( - StatusCode::NOT_FOUND, - "The requested address could not be found in the wallet", - ) - .into_response(), - Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(), + Ok(lb_wallet_service::TipResponse { response: None, .. }) => { + ApiError::NotFound("The requested address could not be found in the wallet".into()) + .into_response() + } + Err(error) => ApiError::internal(error).into_response(), } } @@ -1677,7 +1663,7 @@ pub mod wallet { path = paths::LEADER_CLAIM_VOUCHERS, responses( (status = 200, description = "Get claimable wallet vouchers"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn get_claimable_vouchers( @@ -1688,9 +1674,9 @@ pub mod wallet { WalletService: WalletServiceData + 'static, RuntimeServiceId: Debug + Send + Sync + Display + 'static + AsServiceId, { - let wallet_relay = match get_relay_or_500::(&handle).await { + let wallet_relay = match get_relay::(&handle).await { Ok(relay) => relay, - Err(error_response) => return error_response, + Err(error) => return error.into_response(), }; let wallet_api = WalletApi::::new(wallet_relay); @@ -1706,7 +1692,7 @@ pub mod wallet { WalletClaimableVouchersResponseBody { tip, vouchers }.into_response() } - Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(), + Err(error) => ApiError::internal(error).into_response(), } } @@ -1715,7 +1701,7 @@ pub mod wallet { path = paths::wallet::TRANSACTIONS_TRANSFER_FUNDS, responses( (status = 200, description = "Make transfer"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn post_transactions_transfer_funds( @@ -1761,9 +1747,9 @@ pub mod wallet { >, { let wallet_api = { - let wallet_relay = match get_relay_or_500::(&handle).await { + let wallet_relay = match get_relay::(&handle).await { Ok(relay) => relay, - Err(error_response) => return error_response, + Err(error) => return error.into_response(), }; WalletApi::::new(wallet_relay) }; @@ -1798,12 +1784,12 @@ pub mod wallet { >(&handle, transaction.clone(), Hashable::hash) .await { - return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(); + return ApiError::Internal(e).into_response(); } WalletTransferFundsResponseBody::from(transaction).into_response() } - Err(error) => (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response(), + Err(error) => ApiError::internal(error).into_response(), } } @@ -1812,7 +1798,7 @@ pub mod wallet { path = paths::wallet::SIGN_TX_ED25519, responses( (status = 200, description = "Signed transaction"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn sign_tx_ed25519( @@ -1872,7 +1858,7 @@ pub mod wallet { path = paths::wallet::SIGN_TX_ZK, responses( (status = 200, description = "Signed transaction"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn sign_tx_zk( @@ -1932,7 +1918,7 @@ pub mod wallet { path = paths::wallet::FUND, responses( (status = 200, description = "Funded transaction with fee transfer proof"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = ErrorBody), ) )] pub async fn fund( diff --git a/nodes/node/binary/src/api/openapi.rs b/nodes/node/binary/src/api/openapi.rs index d09bba646..e0a570290 100644 --- a/nodes/node/binary/src/api/openapi.rs +++ b/nodes/node/binary/src/api/openapi.rs @@ -35,7 +35,7 @@ use utoipa::OpenApi; crate::api::handlers::wallet::fund, crate::api::tracing::reload_tracing_filter, ), - components(schemas(schema::Status, schema::MempoolMetrics)), + components(schemas(schema::Status, schema::MempoolMetrics, crate::api::errors::ErrorBody)), tags() )] pub struct ApiDoc; diff --git a/nodes/node/binary/src/api/responses/overwatch.rs b/nodes/node/binary/src/api/responses/overwatch.rs index a40b8e6e5..e6bc3f30f 100644 --- a/nodes/node/binary/src/api/responses/overwatch.rs +++ b/nodes/node/binary/src/api/responses/overwatch.rs @@ -1,22 +1,19 @@ use std::fmt::{Debug, Display}; -use axum::response::{IntoResponse as _, Response}; -use http::StatusCode; use overwatch::{ overwatch::OverwatchHandle, services::{AsServiceId, ServiceData, relay::OutboundRelay}, }; -pub async fn get_relay_or_500( +use crate::api::errors::ApiError; + +pub async fn get_relay( handle: &OverwatchHandle, -) -> Result::Message>, Response> +) -> Result::Message>, ApiError> where Service: ServiceData, Service::Message: 'static, RuntimeServiceId: Debug + Sync + Display + AsServiceId, { - handle - .relay() - .await - .map_err(|error| (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response()) + handle.relay().await.map_err(ApiError::internal) } diff --git a/nodes/node/binary/src/api/tracing.rs b/nodes/node/binary/src/api/tracing.rs index d883ae87d..7005685a9 100644 --- a/nodes/node/binary/src/api/tracing.rs +++ b/nodes/node/binary/src/api/tracing.rs @@ -17,7 +17,7 @@ const LOG_TARGET: &str = node::api::TRACING; path = paths::admin::TRACING_FILTER, responses( (status = 200, description = "Tracing filter reloaded"), - (status = 500, description = "Internal server error", body = String), + (status = 500, description = "Internal server error", body = crate::api::errors::ErrorBody), ) )] pub async fn reload_tracing_filter(