diff --git a/lib/ain-ocean/src/api/mod.rs b/lib/ain-ocean/src/api/mod.rs index 91fa882f162..a0cf7f9e3b6 100644 --- a/lib/ain-ocean/src/api/mod.rs +++ b/lib/ain-ocean/src/api/mod.rs @@ -14,6 +14,7 @@ mod masternode; // mod rawtx; mod cache; mod common; +mod path; mod query; mod response; mod stats; diff --git a/lib/ain-ocean/src/api/path.rs b/lib/ain-ocean/src/api/path.rs new file mode 100644 index 00000000000..029879b3c28 --- /dev/null +++ b/lib/ain-ocean/src/api/path.rs @@ -0,0 +1,102 @@ +use axum::{ + async_trait, + extract::{path::ErrorKind, rejection::PathRejection, FromRequestParts}, + http::{request::Parts, StatusCode}, +}; +use serde::{de::DeserializeOwned, Serialize}; + +use crate::error::ApiError; + +// We define our own `Path` extractor that customizes the error from `axum::extract::Path` +#[derive(Debug)] +pub struct Path(pub T); + +#[async_trait] +impl FromRequestParts for Path +where + // these trait bounds are copied from `impl FromRequest for axum::extract::path::Path` + T: DeserializeOwned + Send, + S: Send + Sync, +{ + type Rejection = ApiError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + match axum::extract::Path::::from_request_parts(parts, state).await { + Ok(value) => Ok(Self(value.0)), + Err(rejection) => { + let error = match rejection { + PathRejection::FailedToDeserializePathParams(inner) => { + let kind = inner.into_kind(); + let error = match &kind { + ErrorKind::WrongNumberOfParameters { .. } => ApiError::new( + StatusCode::BAD_REQUEST, + kind.to_string(), + parts.uri.to_string(), + ), + + ErrorKind::ParseErrorAtKey { key, .. } => ApiError::new( + StatusCode::BAD_REQUEST, + format!("key: {key}, {kind}"), + parts.uri.to_string(), + ), + + ErrorKind::ParseErrorAtIndex { index, .. } => ApiError::new( + StatusCode::BAD_REQUEST, + format!("index: {index}, {kind}"), + parts.uri.to_string(), + ), + + ErrorKind::ParseError { .. } => ApiError::new( + StatusCode::BAD_REQUEST, + kind.to_string(), + parts.uri.to_string(), + ), + + ErrorKind::InvalidUtf8InPathParam { key } => ApiError::new( + StatusCode::BAD_REQUEST, + format!("key: {key}, {kind}"), + parts.uri.to_string(), + ), + + ErrorKind::UnsupportedType { .. } => { + // this error is caused by the programmer using an unsupported type + // (such as nested maps) so respond with `500` instead + ApiError::new( + StatusCode::INTERNAL_SERVER_ERROR, + kind.to_string(), + parts.uri.to_string(), + ) + } + + ErrorKind::Message(msg) => ApiError::new( + StatusCode::BAD_REQUEST, + msg.clone(), + parts.uri.to_string(), + ), + + _ => ApiError::new( + StatusCode::BAD_REQUEST, + format!("Unhandled deserialization error: {kind}"), + parts.uri.to_string(), + ), + }; + + error + } + PathRejection::MissingPathParams(error) => ApiError::new( + StatusCode::INTERNAL_SERVER_ERROR, + error.to_string(), + parts.uri.to_string(), + ), + _ => ApiError::new( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Unhandled path rejection: {rejection}"), + parts.uri.to_string(), + ), + }; + + Err(error) + } + } + } +} diff --git a/lib/ain-ocean/src/api/transactions.rs b/lib/ain-ocean/src/api/transactions.rs index 097c543044e..04c310f03b5 100644 --- a/lib/ain-ocean/src/api/transactions.rs +++ b/lib/ain-ocean/src/api/transactions.rs @@ -1,15 +1,11 @@ use std::sync::Arc; use ain_macros::ocean_endpoint; -use axum::{ - extract::{Path, Query}, - routing::get, - Extension, Json, Router, -}; +use axum::{extract::Query, routing::get, Extension, Router}; use bitcoin::Txid; use serde::Deserialize; -use super::{query::PaginationQuery, response::ApiPagedResponse, AppContext}; +use super::{path::Path, query::PaginationQuery, response::ApiPagedResponse, AppContext}; use crate::{ api::response::Response, error::ApiError, diff --git a/lib/ain-ocean/src/indexer/transaction.rs b/lib/ain-ocean/src/indexer/transaction.rs index b4607e3e9ec..5d99adf295d 100644 --- a/lib/ain-ocean/src/indexer/transaction.rs +++ b/lib/ain-ocean/src/indexer/transaction.rs @@ -60,6 +60,7 @@ pub fn index_transaction(services: &Arc, ctx: Context) -> Result<()> { let tx = TransactionMapper { id: txid, + txid, order, hash: ctx.tx.hash.clone(), block: ctx.block.clone(), diff --git a/lib/ain-ocean/src/model/transaction.rs b/lib/ain-ocean/src/model/transaction.rs index 1ba344e05be..bbe713b54e9 100644 --- a/lib/ain-ocean/src/model/transaction.rs +++ b/lib/ain-ocean/src/model/transaction.rs @@ -1,21 +1,25 @@ use bitcoin::{BlockHash, Txid}; use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, DisplayFromStr}; use super::BlockContext; pub type TransactionByBlockHashKey = (BlockHash, usize); +#[serde_as] #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Transaction { - pub id: Txid, - pub order: usize, + pub id: Txid, // unique id of the transaction, same as the txid + pub txid: Txid, + pub order: usize, // tx order pub block: BlockContext, pub hash: String, pub version: u32, pub size: u64, pub v_size: u64, pub weight: u64, + #[serde_as(as = "DisplayFromStr")] pub total_vout_value: f64, pub lock_time: u64, pub vin_count: usize,