refactor(api): centralize typed HTTP errors (#3172)

This commit is contained in:
Andrus Salumets
2026-07-27 04:46:59 +00:00
committed by GitHub
parent f1cc3a1600
commit 4d56aa6c03
5 changed files with 287 additions and 113 deletions
+197 -6
View File
@@ -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<String>) -> 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<T, E>(result: Result<T, E>) -> Response
where
T: Serialize,
E: Into<ApiError>,
{
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]");
}
}
+83 -97
View File
@@ -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<StorageAdapter, RuntimeServiceId>(
@@ -423,7 +412,7 @@ where
path = paths::MANTLE_STATUS,
responses(
(status = 200, description = "Query the mempool status of the cl service", body = Vec<<T as Transaction>::Hash>),
(status = 500, description = "Internal server error", body = String),
(status = 500, description = "Internal server error", body = ErrorBody),
)
)]
pub async fn mantle_status<StorageAdapter, RuntimeServiceId>(
@@ -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<RuntimeServiceId>(
@@ -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<RuntimeServiceId>(
@@ -513,12 +502,12 @@ where
let relay = match handle.relay::<TimeService>().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<HeaderId>),
(status = 500, description = "Internal server error", body = String),
(status = 500, description = "Internal server error", body = ErrorBody),
)
)]
pub async fn cryptarchia_headers<RuntimeServiceId>(
@@ -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<RuntimeServiceId>(
@@ -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<RuntimeServiceId>(
@@ -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<RuntimeServiceId>(
@@ -632,7 +621,7 @@ where
path = paths::BLEND_NETWORK_INFO,
responses(
(status = 200, description = "Query the blend network information", body = Option<lb_blend_service::message::NetworkInfo<PeerId>>),
(status = 500, description = "Internal server error", body = String),
(status = 500, description = "Internal server error", body = ErrorBody),
)
)]
pub async fn blend_info<BlendService, BroadcastSettings, RuntimeServiceId>(
@@ -660,7 +649,7 @@ where
request_body = BlendJoinNetworkRequestBody,
responses(
(status = 200, description = "Join the blend network", body = Option<lb_core::sdp::DeclarationId>),
(status = 500, description = "Internal server error", body = String),
(status = 500, description = "Internal server error", body = ErrorBody),
)
)]
pub async fn blend_join_network<BlendService, BroadcastSettings, RuntimeServiceId>(
@@ -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<StorageAdapter, RuntimeServiceId>(
@@ -750,7 +739,7 @@ where
path = paths::MEMPOOL_VIEW,
responses(
(status = 200, description = "Get current tip mempool transaction hashes", body = Vec<TxHash>),
(status = 500, description = "Internal server error", body = String),
(status = 500, description = "Internal server error", body = ErrorBody),
)
)]
pub async fn mempool_view<StorageAdapter, RuntimeServiceId>(
@@ -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<RuntimeServiceId>(
@@ -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<WalletService, StorageAdapter, RuntimeServiceId>(
@@ -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<lb_core::sdp::DeclarationId, lb_core::sdp::Declaration>),
(status = 500, description = "Internal server error", body = String),
(status = 500, description = "Internal server error", body = ErrorBody),
)
)]
pub async fn get_sdp_declarations<RuntimeServiceId>(
@@ -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<lb_core::sdp::DeclarationId, lb_core::sdp::Declaration>),
(status = 500, description = "Internal server error", body = String),
(status = 500, description = "Internal server error", body = ErrorBody),
)
)]
pub async fn get_sdp_snapshot<RuntimeServiceId>(
@@ -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<ChainLeader, RuntimeServiceId>(
@@ -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<StorageBackend, RuntimeServiceId>(
@@ -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<HttpStorageAdapter, RuntimeServiceId>(
@@ -1343,9 +1332,9 @@ where
RuntimeServiceId:
AsServiceId<StorageService<RocksBackend, RuntimeServiceId>> + 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::<SignedMantleTx<Unverified>>(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<RuntimeServiceId>(
@@ -1375,17 +1364,17 @@ where
RuntimeServiceId:
AsServiceId<Cryptarchia<RuntimeServiceId>> + 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::<Cryptarchia<RuntimeServiceId>, 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<RuntimeServiceId>(
@@ -1410,9 +1399,9 @@ where
RuntimeServiceId:
AsServiceId<Cryptarchia<RuntimeServiceId>> + 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::<Cryptarchia<RuntimeServiceId>, RuntimeServiceId>::new(relay);
@@ -1422,7 +1411,7 @@ where
None => match consensus::cryptarchia_info::<RuntimeServiceId>(&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<StorageBackend, ConsensusService, RuntimeServiceId>(
@@ -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<StorageBackend, RuntimeServiceId>(
@@ -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<HttpStorageAdapter, RuntimeServiceId>(
@@ -1584,28 +1573,26 @@ where
RuntimeServiceId:
AsServiceId<StorageService<RocksBackend, RuntimeServiceId>> + 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::<SignedMantleTx<Unverified>>(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<WalletService, RuntimeServiceId>(
@@ -1644,9 +1631,9 @@ pub mod wallet {
RuntimeServiceId: Debug + Send + Sync + Display + 'static + AsServiceId<WalletService>,
{
let wallet_api = {
let wallet_relay = match get_relay_or_500::<WalletService, _>(&handle).await {
let wallet_relay = match get_relay::<WalletService, _>(&handle).await {
Ok(relay) => relay,
Err(error_response) => return error_response,
Err(error) => return error.into_response(),
};
WalletApi::<WalletService, RuntimeServiceId>::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<WalletService, RuntimeServiceId>(
@@ -1688,9 +1674,9 @@ pub mod wallet {
WalletService: WalletServiceData + 'static,
RuntimeServiceId: Debug + Send + Sync + Display + 'static + AsServiceId<WalletService>,
{
let wallet_relay = match get_relay_or_500::<WalletService, _>(&handle).await {
let wallet_relay = match get_relay::<WalletService, _>(&handle).await {
Ok(relay) => relay,
Err(error_response) => return error_response,
Err(error) => return error.into_response(),
};
let wallet_api = WalletApi::<WalletService, RuntimeServiceId>::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<WalletService, StorageAdapter, RuntimeServiceId>(
@@ -1761,9 +1747,9 @@ pub mod wallet {
>,
{
let wallet_api = {
let wallet_relay = match get_relay_or_500::<WalletService, _>(&handle).await {
let wallet_relay = match get_relay::<WalletService, _>(&handle).await {
Ok(relay) => relay,
Err(error_response) => return error_response,
Err(error) => return error.into_response(),
};
WalletApi::<WalletService, RuntimeServiceId>::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<WalletService, StorageAdapter, RuntimeServiceId>(
@@ -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<WalletService, StorageAdapter, RuntimeServiceId>(
@@ -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<WalletService, StorageAdapter, RuntimeServiceId>(
+1 -1
View File
@@ -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;
@@ -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<Service, RuntimeServiceId>(
use crate::api::errors::ApiError;
pub async fn get_relay<Service, RuntimeServiceId>(
handle: &OverwatchHandle<RuntimeServiceId>,
) -> Result<OutboundRelay<<Service as ServiceData>::Message>, Response>
) -> Result<OutboundRelay<<Service as ServiceData>::Message>, ApiError>
where
Service: ServiceData,
Service::Message: 'static,
RuntimeServiceId: Debug + Sync + Display + AsServiceId<Service>,
{
handle
.relay()
.await
.map_err(|error| (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response())
handle.relay().await.map_err(ApiError::internal)
}
+1 -1
View File
@@ -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<RuntimeServiceId>(