server: openapi fixes

This commit is contained in:
nym21
2025-12-16 18:03:23 +01:00
parent 593af69230
commit f7f065c6e0
44 changed files with 279 additions and 286 deletions
+3 -4
View File
@@ -7,7 +7,7 @@ use crate::Query;
const DEFAULT_BLOCK_COUNT: u32 = 10;
impl Query {
pub fn block(&self, hash: &str) -> Result<BlockInfo> {
pub fn block(&self, hash: &BlockHash) -> Result<BlockInfo> {
let height = self.height_by_hash(hash)?;
self.block_by_height(height)
}
@@ -58,11 +58,10 @@ impl Query {
// === Helper methods ===
pub fn height_by_hash(&self, hash: &str) -> Result<Height> {
pub fn height_by_hash(&self, hash: &BlockHash) -> Result<Height> {
let indexer = self.indexer();
let blockhash: BlockHash = hash.parse().map_err(|_| Error::Str("Invalid block hash"))?;
let prefix = BlockHashPrefix::from(&blockhash);
let prefix = BlockHashPrefix::from(hash);
indexer
.stores
+2 -2
View File
@@ -1,11 +1,11 @@
use brk_error::{Error, Result};
use brk_types::Height;
use brk_types::{BlockHash, Height};
use vecdb::{AnyVec, GenericStoredVec};
use crate::Query;
impl Query {
pub fn block_raw(&self, hash: &str) -> Result<Vec<u8>> {
pub fn block_raw(&self, hash: &BlockHash) -> Result<Vec<u8>> {
let height = self.height_by_hash(hash)?;
self.block_raw_by_height(height)
}
+2 -2
View File
@@ -1,11 +1,11 @@
use brk_error::Result;
use brk_types::{BlockStatus, Height};
use brk_types::{BlockHash, BlockStatus, Height};
use vecdb::{AnyVec, GenericStoredVec};
use crate::Query;
impl Query {
pub fn block_status(&self, hash: &str) -> Result<BlockStatus> {
pub fn block_status(&self, hash: &BlockHash) -> Result<BlockStatus> {
let height = self.height_by_hash(hash)?;
self.block_status_by_height(height)
}
+6 -6
View File
@@ -1,24 +1,24 @@
use brk_error::{Error, Result};
use brk_types::{Height, Transaction, TxIndex, Txid};
use brk_types::{BlockHash, Height, Transaction, TxIndex, Txid};
use vecdb::{AnyVec, GenericStoredVec, TypedVecIterator};
use super::BLOCK_TXS_PAGE_SIZE;
use crate::Query;
impl Query {
pub fn block_txids(&self, hash: &str) -> Result<Vec<Txid>> {
pub fn block_txids(&self, hash: &BlockHash) -> Result<Vec<Txid>> {
let height = self.height_by_hash(hash)?;
self.block_txids_by_height(height)
}
pub fn block_txs(&self, hash: &str, start_index: usize) -> Result<Vec<Transaction>> {
pub fn block_txs(&self, hash: &BlockHash, start_index: TxIndex) -> Result<Vec<Transaction>> {
let height = self.height_by_hash(hash)?;
self.block_txs_by_height(height, start_index)
self.block_txs_by_height(height, start_index.into())
}
pub fn block_txid_at_index(&self, hash: &str, index: usize) -> Result<Txid> {
pub fn block_txid_at_index(&self, hash: &BlockHash, index: TxIndex) -> Result<Txid> {
let height = self.height_by_hash(hash)?;
self.block_txid_at_index_by_height(height, index)
self.block_txid_at_index_by_height(height, index.into())
}
// === Helper methods ===
+7 -37
View File
@@ -1,9 +1,9 @@
use std::{io::Cursor, str::FromStr};
use std::io::Cursor;
use bitcoin::{consensus::Decodable, hex::DisplayHex};
use brk_error::{Error, Result};
use brk_types::{
Sats, Transaction, TxIn, TxInIndex, TxIndex, TxOut, TxOutspend, TxStatus, Txid, TxidPath,
Sats, Transaction, TxIn, TxInIndex, TxIndex, TxOut, TxOutspend, TxStatus, Txid, TxidParam,
TxidPrefix, Vin, Vout, Weight,
};
use vecdb::{GenericStoredVec, TypedVecIterator};
@@ -11,13 +11,7 @@ use vecdb::{GenericStoredVec, TypedVecIterator};
use crate::Query;
impl Query {
pub fn transaction(&self, TxidPath { txid }: TxidPath) -> Result<Transaction> {
let Ok(txid) = bitcoin::Txid::from_str(&txid) else {
return Err(Error::InvalidTxid);
};
let txid = Txid::from(txid);
pub fn transaction(&self, TxidParam { txid }: TxidParam) -> Result<Transaction> {
// First check mempool for unconfirmed transactions
if let Some(mempool) = self.mempool()
&& let Some(tx_with_hex) = mempool.get_txs().get(&txid)
@@ -40,13 +34,7 @@ impl Query {
self.transaction_by_index(txindex)
}
pub fn transaction_status(&self, TxidPath { txid }: TxidPath) -> Result<TxStatus> {
let Ok(txid) = bitcoin::Txid::from_str(&txid) else {
return Err(Error::InvalidTxid);
};
let txid = Txid::from(txid);
pub fn transaction_status(&self, TxidParam { txid }: TxidParam) -> Result<TxStatus> {
// First check mempool for unconfirmed transactions
if let Some(mempool) = self.mempool()
&& mempool.get_txs().contains_key(&txid)
@@ -79,13 +67,7 @@ impl Query {
})
}
pub fn transaction_hex(&self, TxidPath { txid }: TxidPath) -> Result<String> {
let Ok(txid) = bitcoin::Txid::from_str(&txid) else {
return Err(Error::InvalidTxid);
};
let txid = Txid::from(txid);
pub fn transaction_hex(&self, TxidParam { txid }: TxidParam) -> Result<String> {
// First check mempool for unconfirmed transactions
if let Some(mempool) = self.mempool()
&& let Some(tx_with_hex) = mempool.get_txs().get(&txid)
@@ -108,13 +90,7 @@ impl Query {
self.transaction_hex_by_index(txindex)
}
pub fn outspend(&self, TxidPath { txid }: TxidPath, vout: Vout) -> Result<TxOutspend> {
let Ok(txid) = bitcoin::Txid::from_str(&txid) else {
return Err(Error::InvalidTxid);
};
let txid = Txid::from(txid);
pub fn outspend(&self, TxidParam { txid }: TxidParam, vout: Vout) -> Result<TxOutspend> {
// Mempool outputs are unspent in on-chain terms
if let Some(mempool) = self.mempool()
&& mempool.get_txs().contains_key(&txid)
@@ -156,13 +132,7 @@ impl Query {
self.outspend_details(txinindex)
}
pub fn outspends(&self, TxidPath { txid }: TxidPath) -> Result<Vec<TxOutspend>> {
let Ok(txid) = bitcoin::Txid::from_str(&txid) else {
return Err(Error::InvalidTxid);
};
let txid = Txid::from(txid);
pub fn outspends(&self, TxidParam { txid }: TxidParam) -> Result<Vec<TxOutspend>> {
// Mempool outputs are unspent in on-chain terms
if let Some(mempool) = self.mempool()
&& let Some(tx_with_hex) = mempool.get_txs().get(&txid)
+16 -13
View File
@@ -5,7 +5,10 @@ use axum::{
response::Redirect,
routing::get,
};
use brk_types::{Address, AddressStats, AddressTxidsParam, AddressValidation, Txid, Utxo};
use brk_types::{
AddressParam, AddressStats, AddressTxidsParam, AddressValidation, Txid, Utxo,
ValidateAddressParam,
};
use crate::{CacheStrategy, extended::TransformResponseExtended};
@@ -24,10 +27,10 @@ impl AddressRoutes for ApiRouter<AppState> {
"/api/address/{address}",
get_with(async |
headers: HeaderMap,
Path(address): Path<Address>,
Path(path): Path<AddressParam>,
State(state): State<AppState>
| {
state.cached_json(&headers, CacheStrategy::Height, move |q| q.address(address)).await
state.cached_json(&headers, CacheStrategy::Height, move |q| q.address(path.address)).await
}, |op| op
.addresses_tag()
.summary("Address information")
@@ -43,11 +46,11 @@ impl AddressRoutes for ApiRouter<AppState> {
"/api/address/{address}/txs",
get_with(async |
headers: HeaderMap,
Path(address): Path<Address>,
Path(path): Path<AddressParam>,
Query(params): Query<AddressTxidsParam>,
State(state): State<AppState>
| {
state.cached_json(&headers, CacheStrategy::Height, move |q| q.address_txids(address, params.after_txid, params.limit)).await
state.cached_json(&headers, CacheStrategy::Height, move |q| q.address_txids(path.address, params.after_txid, params.limit)).await
}, |op| op
.addresses_tag()
.summary("Address transaction IDs")
@@ -63,10 +66,10 @@ impl AddressRoutes for ApiRouter<AppState> {
"/api/address/{address}/utxo",
get_with(async |
headers: HeaderMap,
Path(address): Path<Address>,
Path(path): Path<AddressParam>,
State(state): State<AppState>
| {
state.cached_json(&headers, CacheStrategy::Height, move |q| q.address_utxos(address)).await
state.cached_json(&headers, CacheStrategy::Height, move |q| q.address_utxos(path.address)).await
}, |op| op
.addresses_tag()
.summary("Address UTXOs")
@@ -82,11 +85,11 @@ impl AddressRoutes for ApiRouter<AppState> {
"/api/address/{address}/txs/mempool",
get_with(async |
headers: HeaderMap,
Path(address): Path<Address>,
Path(path): Path<AddressParam>,
State(state): State<AppState>
| {
// Mempool txs for an address - use MaxAge since it's volatile
state.cached_json(&headers, CacheStrategy::MaxAge(5), move |q| q.address_mempool_txids(address)).await
state.cached_json(&headers, CacheStrategy::MaxAge(5), move |q| q.address_mempool_txids(path.address)).await
}, |op| op
.addresses_tag()
.summary("Address mempool transactions")
@@ -101,11 +104,11 @@ impl AddressRoutes for ApiRouter<AppState> {
"/api/address/{address}/txs/chain",
get_with(async |
headers: HeaderMap,
Path(address): Path<Address>,
Path(path): Path<AddressParam>,
Query(params): Query<AddressTxidsParam>,
State(state): State<AppState>
| {
state.cached_json(&headers, CacheStrategy::Height, move |q| q.address_txids(address, params.after_txid, 25)).await
state.cached_json(&headers, CacheStrategy::Height, move |q| q.address_txids(path.address, params.after_txid, 25)).await
}, |op| op
.addresses_tag()
.summary("Address confirmed transactions")
@@ -121,10 +124,10 @@ impl AddressRoutes for ApiRouter<AppState> {
"/api/v1/validate-address/{address}",
get_with(async |
headers: HeaderMap,
Path(address): Path<String>,
Path(path): Path<ValidateAddressParam>,
State(state): State<AppState>
| {
state.cached_json(&headers, CacheStrategy::Static, move |_q| Ok(AddressValidation::from_address(&address))).await
state.cached_json(&headers, CacheStrategy::Static, move |_q| Ok(AddressValidation::from_address(&path.address))).await
}, |op| op
.addresses_tag()
.summary("Validate address")
+13 -15
View File
@@ -7,8 +7,8 @@ use axum::{
};
use brk_query::BLOCK_TXS_PAGE_SIZE;
use brk_types::{
BlockHashPath, BlockHashStartIndexPath, BlockHashTxIndexPath, BlockInfo, BlockStatus,
BlockTimestamp, Height, HeightPath, StartHeightPath, TimestampPath, Transaction, Txid,
BlockHashParam, BlockHashStartIndex, BlockHashTxIndex, BlockInfo, BlockStatus, BlockTimestamp,
HeightParam, StartHeightParam, TimestampParam, Transaction, Txid,
};
use crate::{CacheStrategy, extended::TransformResponseExtended};
@@ -30,7 +30,7 @@ impl BlockRoutes for ApiRouter<AppState> {
"/api/block/{hash}",
get_with(
async |headers: HeaderMap,
Path(path): Path<BlockHashPath>,
Path(path): Path<BlockHashParam>,
State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Height, move |q| q.block(&path.hash)).await
},
@@ -52,7 +52,7 @@ impl BlockRoutes for ApiRouter<AppState> {
"/api/block/{hash}/status",
get_with(
async |headers: HeaderMap,
Path(path): Path<BlockHashPath>,
Path(path): Path<BlockHashParam>,
State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Height, move |q| q.block_status(&path.hash)).await
},
@@ -74,10 +74,9 @@ impl BlockRoutes for ApiRouter<AppState> {
"/api/block-height/{height}",
get_with(
async |headers: HeaderMap,
Path(path): Path<HeightPath>,
Path(path): Path<HeightParam>,
State(state): State<AppState>| {
let height = Height::from(path.height);
state.cached_json(&headers, CacheStrategy::Height, move |q| q.block_by_height(height)).await
state.cached_json(&headers, CacheStrategy::Height, move |q| q.block_by_height(path.height)).await
},
|op| {
op.blocks_tag()
@@ -97,10 +96,9 @@ impl BlockRoutes for ApiRouter<AppState> {
"/api/blocks/{start_height}",
get_with(
async |headers: HeaderMap,
Path(path): Path<StartHeightPath>,
Path(path): Path<StartHeightParam>,
State(state): State<AppState>| {
let start_height = path.start_height.map(Height::from);
state.cached_json(&headers, CacheStrategy::Height, move |q| q.blocks(start_height)).await
state.cached_json(&headers, CacheStrategy::Height, move |q| q.blocks(path.start_height)).await
},
|op| {
op.blocks_tag()
@@ -119,7 +117,7 @@ impl BlockRoutes for ApiRouter<AppState> {
"/api/block/{hash}/txids",
get_with(
async |headers: HeaderMap,
Path(path): Path<BlockHashPath>,
Path(path): Path<BlockHashParam>,
State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Height, move |q| q.block_txids(&path.hash)).await
},
@@ -141,7 +139,7 @@ impl BlockRoutes for ApiRouter<AppState> {
"/api/block/{hash}/txs/{start_index}",
get_with(
async |headers: HeaderMap,
Path(path): Path<BlockHashStartIndexPath>,
Path(path): Path<BlockHashStartIndex>,
State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Height, move |q| q.block_txs(&path.hash, path.start_index)).await
},
@@ -164,7 +162,7 @@ impl BlockRoutes for ApiRouter<AppState> {
"/api/block/{hash}/txid/{index}",
get_with(
async |headers: HeaderMap,
Path(path): Path<BlockHashTxIndexPath>,
Path(path): Path<BlockHashTxIndex>,
State(state): State<AppState>| {
state.cached_text(&headers, CacheStrategy::Height, move |q| q.block_txid_at_index(&path.hash, path.index).map(|t| t.to_string())).await
},
@@ -186,7 +184,7 @@ impl BlockRoutes for ApiRouter<AppState> {
"/api/v1/mining/blocks/timestamp/{timestamp}",
get_with(
async |headers: HeaderMap,
Path(path): Path<TimestampPath>,
Path(path): Path<TimestampParam>,
State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Height, move |q| q.block_by_timestamp(path.timestamp)).await
},
@@ -206,7 +204,7 @@ impl BlockRoutes for ApiRouter<AppState> {
"/api/block/{hash}/raw",
get_with(
async |headers: HeaderMap,
Path(path): Path<BlockHashPath>,
Path(path): Path<BlockHashParam>,
State(state): State<AppState>| {
state.cached_bytes(&headers, CacheStrategy::Height, move |q| q.block_raw(&path.hash)).await
},
+7 -8
View File
@@ -10,7 +10,7 @@ use brk_query::{
};
use brk_traversable::TreeNode;
use brk_types::{
Index, IndexInfo, Limit, Metric, MetricCount, MetricData, MetricWithIndex, Metrics,
Index, IndexInfo, LimitParam, MetricCount, MetricData, MetricParam, MetricWithIndex, Metrics,
};
use crate::{CacheStrategy, extended::TransformResponseExtended};
@@ -109,14 +109,13 @@ impl ApiMetricsRoutes for ApiRouter<AppState> {
async |
headers: HeaderMap,
State(state): State<AppState>,
Path(metric): Path<Metric>,
Query(limit): Query<Limit>
Path(path): Path<MetricParam>,
Query(query): Query<LimitParam>
| {
state.cached_json(&headers, CacheStrategy::Static, move |q| Ok(q.match_metric(&metric, limit))).await
state.cached_json(&headers, CacheStrategy::Static, move |q| Ok(q.match_metric(&path.metric, query.limit))).await
},
|op| op
.metrics_tag()
// .path_param::<Metric>()
.summary("Search metrics")
.description("Fuzzy search for metrics by name. Supports partial matches and typos.")
.ok_response::<Vec<String>>()
@@ -129,13 +128,13 @@ impl ApiMetricsRoutes for ApiRouter<AppState> {
async |
headers: HeaderMap,
State(state): State<AppState>,
Path(metric): Path<Metric>
Path(path): Path<MetricParam>
| {
state.cached_json(&headers, CacheStrategy::Static, move |q| {
if let Some(indexes) = q.metric_to_indexes(metric.clone()) {
if let Some(indexes) = q.metric_to_indexes(path.metric.clone()) {
return Ok(indexes.clone())
}
Err(q.metric_not_found_error(&metric))
Err(q.metric_not_found_error(&path.metric))
}).await
},
|op| op
+18 -18
View File
@@ -6,9 +6,9 @@ use axum::{
routing::get,
};
use brk_types::{
BlockCountPath, BlockFeeRatesEntry, BlockFeesEntry, BlockRewardsEntry, BlockSizesWeights,
BlockCountParam, BlockFeeRatesEntry, BlockFeesEntry, BlockRewardsEntry, BlockSizesWeights,
DifficultyAdjustment, DifficultyAdjustmentEntry, HashrateSummary, PoolDetail, PoolInfo,
PoolSlugPath, PoolsSummary, RewardStats, TimePeriod,
PoolSlugParam, PoolsSummary, RewardStats, TimePeriodParam,
};
use crate::{CacheStrategy, extended::TransformResponseExtended};
@@ -61,8 +61,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/pools/{time_period}",
get_with(
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("{:?}", time_period)), move |q| q.mining_pools(time_period)).await
async |headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("{:?}", path.time_period)), move |q| q.mining_pools(path.time_period)).await
},
|op| {
op.mining_tag()
@@ -77,7 +77,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/pool/{slug}",
get_with(
async |headers: HeaderMap, Path(path): Path<PoolSlugPath>, State(state): State<AppState>| {
async |headers: HeaderMap, Path(path): Path<PoolSlugParam>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(path.slug), move |q| q.pool_detail(path.slug)).await
},
|op| {
@@ -110,8 +110,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/hashrate/{time_period}",
get_with(
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("hashrate-{:?}", time_period)), move |q| q.hashrate(Some(time_period))).await
async |headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("hashrate-{:?}", path.time_period)), move |q| q.hashrate(Some(path.time_period))).await
},
|op| {
op.mining_tag()
@@ -142,8 +142,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/difficulty-adjustments/{time_period}",
get_with(
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("diff-adj-{:?}", time_period)), move |q| q.difficulty_adjustments(Some(time_period))).await
async |headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("diff-adj-{:?}", path.time_period)), move |q| q.difficulty_adjustments(Some(path.time_period))).await
},
|op| {
op.mining_tag()
@@ -158,8 +158,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/blocks/fees/{time_period}",
get_with(
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("fees-{:?}", time_period)), move |q| q.block_fees(time_period)).await
async |headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("fees-{:?}", path.time_period)), move |q| q.block_fees(path.time_period)).await
},
|op| {
op.mining_tag()
@@ -174,8 +174,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/blocks/rewards/{time_period}",
get_with(
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("rewards-{:?}", time_period)), move |q| q.block_rewards(time_period)).await
async |headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("rewards-{:?}", path.time_period)), move |q| q.block_rewards(path.time_period)).await
},
|op| {
op.mining_tag()
@@ -190,8 +190,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/blocks/fee-rates/{time_period}",
get_with(
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("feerates-{:?}", time_period)), move |q| q.block_fee_rates(time_period)).await
async |headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("feerates-{:?}", path.time_period)), move |q| q.block_fee_rates(path.time_period)).await
},
|op| {
op.mining_tag()
@@ -206,8 +206,8 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/blocks/sizes-weights/{time_period}",
get_with(
async |headers: HeaderMap, Path(time_period): Path<TimePeriod>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("sizes-{:?}", time_period)), move |q| q.block_sizes_weights(time_period)).await
async |headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("sizes-{:?}", path.time_period)), move |q| q.block_sizes_weights(path.time_period)).await
},
|op| {
op.mining_tag()
@@ -222,7 +222,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route(
"/api/v1/mining/reward-stats/{block_count}",
get_with(
async |headers: HeaderMap, Path(path): Path<BlockCountPath>, State(state): State<AppState>| {
async |headers: HeaderMap, Path(path): Path<BlockCountParam>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::height_with(format!("reward-stats-{}", path.block_count)), move |q| q.reward_stats(path.block_count)).await
},
|op| {
@@ -5,7 +5,7 @@ use axum::{
response::Redirect,
routing::get,
};
use brk_types::{Transaction, TxOutspend, TxStatus, TxidPath, TxidVoutPath};
use brk_types::{Transaction, TxOutspend, TxStatus, TxidParam, TxidVout};
use crate::{CacheStrategy, extended::TransformResponseExtended};
@@ -25,7 +25,7 @@ impl TxRoutes for ApiRouter<AppState> {
get_with(
async |
headers: HeaderMap,
Path(txid): Path<TxidPath>,
Path(txid): Path<TxidParam>,
State(state): State<AppState>
| {
state.cached_json(&headers, CacheStrategy::Height, move |q| q.transaction(txid)).await
@@ -48,7 +48,7 @@ impl TxRoutes for ApiRouter<AppState> {
get_with(
async |
headers: HeaderMap,
Path(txid): Path<TxidPath>,
Path(txid): Path<TxidParam>,
State(state): State<AppState>
| {
state.cached_json(&headers, CacheStrategy::Height, move |q| q.transaction_status(txid)).await
@@ -71,7 +71,7 @@ impl TxRoutes for ApiRouter<AppState> {
get_with(
async |
headers: HeaderMap,
Path(txid): Path<TxidPath>,
Path(txid): Path<TxidParam>,
State(state): State<AppState>
| {
state.cached_text(&headers, CacheStrategy::Height, move |q| q.transaction_hex(txid)).await
@@ -94,10 +94,10 @@ impl TxRoutes for ApiRouter<AppState> {
get_with(
async |
headers: HeaderMap,
Path(path): Path<TxidVoutPath>,
Path(path): Path<TxidVout>,
State(state): State<AppState>
| {
let txid = TxidPath { txid: path.txid };
let txid = TxidParam { txid: path.txid };
state.cached_json(&headers, CacheStrategy::Height, move |q| q.outspend(txid, path.vout)).await
},
|op| op
@@ -118,7 +118,7 @@ impl TxRoutes for ApiRouter<AppState> {
get_with(
async |
headers: HeaderMap,
Path(txid): Path<TxidPath>,
Path(txid): Path<TxidParam>,
State(state): State<AppState>
| {
state.cached_json(&headers, CacheStrategy::Height, move |q| q.outspends(txid)).await
+9 -23
View File
@@ -1,9 +1,9 @@
use std::{borrow::Cow, fmt, str::FromStr};
use std::{fmt, str::FromStr};
use bitcoin::ScriptBuf;
use brk_error::Error;
use derive_deref::Deref;
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize, Serializer};
use crate::AddressBytes;
@@ -11,29 +11,15 @@ use crate::AddressBytes;
use super::OutputType;
/// Bitcoin address string
#[derive(Debug, Deref, Deserialize)]
#[derive(Debug, Deref, Deserialize, JsonSchema)]
#[serde(transparent)]
#[schemars(
example = &"04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f",
example = &"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
example = &"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
)]
pub struct Address(String);
impl JsonSchema for Address {
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("Address")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "object",
"required": ["address"],
"properties": {
"address": {
"type": "string",
"description": "Bitcoin address string",
"examples": ["04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"]
}
}
})
}
}
impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
+9
View File
@@ -0,0 +1,9 @@
use schemars::JsonSchema;
use serde::Deserialize;
use crate::Address;
#[derive(Deserialize, JsonSchema)]
pub struct AddressParam {
pub address: Address,
}
@@ -2,8 +2,8 @@ use schemars::JsonSchema;
use serde::Deserialize;
#[derive(Deserialize, JsonSchema)]
pub struct BlockCountPath {
/// Number of blocks to include in the stats
pub struct BlockCountParam {
/// Number of recent blocks to include
#[schemars(example = 100)]
pub block_count: usize,
}
+16 -1
View File
@@ -4,12 +4,17 @@ use bitcoin::hashes::Hash;
use brk_error::Error;
use derive_deref::Deref;
use schemars::JsonSchema;
use serde::{Serialize, Serializer};
use serde::{Deserialize, Serialize, Serializer, de};
use vecdb::{Bytes, Formattable};
/// Block hash
#[derive(Debug, Deref, Clone, PartialEq, Eq, Bytes, JsonSchema)]
#[repr(C)]
#[schemars(
transparent,
with = "String",
example = &"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
)]
pub struct BlockHash([u8; 32]);
impl TryFrom<&str> for BlockHash {
@@ -76,6 +81,16 @@ impl Serialize for BlockHash {
}
}
impl<'de> Deserialize<'de> for BlockHash {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Self::from_str(&s).map_err(de::Error::custom)
}
}
impl Formattable for BlockHash {
#[inline(always)]
fn may_need_escaping() -> bool {
+9
View File
@@ -0,0 +1,9 @@
use schemars::JsonSchema;
use serde::Deserialize;
use crate::BlockHash;
#[derive(Deserialize, JsonSchema)]
pub struct BlockHashParam {
pub hash: BlockHash,
}
-9
View File
@@ -1,9 +0,0 @@
use schemars::JsonSchema;
use serde::Deserialize;
#[derive(Deserialize, JsonSchema)]
pub struct BlockHashPath {
/// Bitcoin block hash
#[schemars(example = &"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")]
pub hash: String,
}
@@ -0,0 +1,14 @@
use schemars::JsonSchema;
use serde::Deserialize;
use crate::{BlockHash, TxIndex};
#[derive(Deserialize, JsonSchema)]
pub struct BlockHashStartIndex {
/// Bitcoin block hash
pub hash: BlockHash,
/// Starting transaction index within the block (0-based)
#[schemars(example = 0)]
pub start_index: TxIndex,
}
@@ -1,13 +0,0 @@
use schemars::JsonSchema;
use serde::Deserialize;
#[derive(Deserialize, JsonSchema)]
pub struct BlockHashStartIndexPath {
/// Bitcoin block hash
#[schemars(example = &"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")]
pub hash: String,
/// Starting transaction index (0-based)
#[schemars(example = 0)]
pub start_index: usize,
}
@@ -1,13 +1,14 @@
use schemars::JsonSchema;
use serde::Deserialize;
use crate::{BlockHash, TxIndex};
#[derive(Deserialize, JsonSchema)]
pub struct BlockHashTxIndexPath {
pub struct BlockHashTxIndex {
/// Bitcoin block hash
#[schemars(example = &"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")]
pub hash: String,
pub hash: BlockHash,
/// Transaction index within the block (0-based)
#[schemars(example = 0)]
pub index: usize,
pub index: TxIndex,
}
+1
View File
@@ -30,6 +30,7 @@ use super::StoredU64;
JsonSchema,
Hash,
)]
#[schemars(example = 0, example = 210_000, example = 420_000, example = 840_000)]
pub struct Height(u32);
impl Height {
+9
View File
@@ -0,0 +1,9 @@
use schemars::JsonSchema;
use serde::Deserialize;
use crate::Height;
#[derive(Deserialize, JsonSchema)]
pub struct HeightParam {
pub height: Height,
}
-9
View File
@@ -1,9 +0,0 @@
use schemars::JsonSchema;
use serde::Deserialize;
#[derive(Deserialize, JsonSchema)]
pub struct HeightPath {
/// Bitcoin block height
#[schemars(example = 0)]
pub height: u32,
}
+30 -22
View File
@@ -9,6 +9,7 @@ mod addresshash;
mod addressindexoutpoint;
mod addressindextxindex;
mod addressmempoolstats;
mod addressparam;
mod addressstats;
mod addresstxidsparam;
mod addressvalidation;
@@ -17,14 +18,14 @@ mod bitcoin;
mod blkmetadata;
mod blkposition;
mod block;
mod blockcountpath;
mod blockcountparam;
mod blockfeesentry;
mod blockferatesentry;
mod blockhash;
mod blockhashpath;
mod blockhashparam;
mod blockhashprefix;
mod blockhashstartindexpath;
mod blockhashtxindexpath;
mod blockhashstartindex;
mod blockhashtxindex;
mod blockinfo;
mod blockrewardsentry;
mod blocksizeentry;
@@ -56,10 +57,11 @@ mod hashrateentry;
mod hashratesummary;
mod health;
mod height;
mod heightpath;
mod heightparam;
mod index;
mod indexinfo;
mod limit;
mod limitparam;
mod loadedaddressdata;
mod loadedaddressindex;
mod mempoolblock;
@@ -67,6 +69,7 @@ mod mempoolentryinfo;
mod mempoolinfo;
mod metric;
mod metriccount;
mod metricparam;
mod metricdata;
mod metrics;
mod metricselection;
@@ -102,7 +105,7 @@ mod pooldetail;
mod poolinfo;
mod pools;
mod poolslug;
mod poolslugpath;
mod poolslugparam;
mod poolssummary;
mod poolstats;
mod quarterindex;
@@ -111,7 +114,7 @@ mod recommendedfees;
mod rewardstats;
mod sats;
mod semesterindex;
mod startheightpath;
mod startheightparam;
mod stored_bool;
mod stored_f32;
mod stored_f64;
@@ -122,15 +125,15 @@ mod stored_u32;
mod stored_u64;
mod stored_u8;
mod timeperiod;
mod timeperiodpath;
mod timeperiodparam;
mod timestamp;
mod timestamppath;
mod timestampparam;
mod treenode;
mod tx;
mod txid;
mod txidpath;
mod txidparam;
mod txidprefix;
mod txidvoutpath;
mod txidvout;
mod txin;
mod txindex;
mod txinindex;
@@ -144,6 +147,7 @@ mod typeindex;
mod unit;
mod unknownoutputindex;
mod utxo;
mod validateaddressparam;
mod vin;
mod vout;
mod vsize;
@@ -158,6 +162,7 @@ pub use addresshash::*;
pub use addressindexoutpoint::*;
pub use addressindextxindex::*;
pub use addressmempoolstats::*;
pub use addressparam::*;
pub use addressstats::*;
pub use addresstxidsparam::*;
pub use addressvalidation::*;
@@ -166,14 +171,14 @@ pub use bitcoin::*;
pub use blkmetadata::*;
pub use blkposition::*;
pub use block::*;
pub use blockcountpath::*;
pub use blockcountparam::*;
pub use blockfeesentry::*;
pub use blockferatesentry::*;
pub use blockhash::*;
pub use blockhashpath::*;
pub use blockhashparam::*;
pub use blockhashprefix::*;
pub use blockhashstartindexpath::*;
pub use blockhashtxindexpath::*;
pub use blockhashstartindex::*;
pub use blockhashtxindex::*;
pub use blockinfo::*;
pub use blockrewardsentry::*;
pub use blocksizeentry::*;
@@ -205,10 +210,11 @@ pub use hashrateentry::*;
pub use hashratesummary::*;
pub use health::*;
pub use height::*;
pub use heightpath::*;
pub use heightparam::*;
pub use index::*;
pub use indexinfo::*;
pub use limit::*;
pub use limitparam::*;
pub use loadedaddressdata::*;
pub use loadedaddressindex::*;
pub use mempoolblock::*;
@@ -216,6 +222,7 @@ pub use mempoolentryinfo::*;
pub use mempoolinfo::*;
pub use metric::*;
pub use metriccount::*;
pub use metricparam::*;
pub use metricdata::*;
pub use metrics::*;
pub use metricselection::*;
@@ -251,7 +258,7 @@ pub use pooldetail::*;
pub use poolinfo::*;
pub use pools::*;
pub use poolslug::*;
pub use poolslugpath::*;
pub use poolslugparam::*;
pub use poolssummary::*;
pub use poolstats::*;
pub use quarterindex::*;
@@ -260,7 +267,7 @@ pub use recommendedfees::*;
pub use rewardstats::*;
pub use sats::*;
pub use semesterindex::*;
pub use startheightpath::*;
pub use startheightparam::*;
pub use stored_bool::*;
pub use stored_f32::*;
pub use stored_f64::*;
@@ -271,15 +278,15 @@ pub use stored_u16::*;
pub use stored_u32::*;
pub use stored_u64::*;
pub use timeperiod::*;
pub use timeperiodpath::*;
pub use timeperiodparam::*;
pub use timestamp::*;
pub use timestamppath::*;
pub use timestampparam::*;
pub use treenode::*;
pub use tx::*;
pub use txid::*;
pub use txidpath::*;
pub use txidparam::*;
pub use txidprefix::*;
pub use txidvoutpath::*;
pub use txidvout::*;
pub use txin::*;
pub use txindex::*;
pub use txinindex::*;
@@ -293,6 +300,7 @@ pub use typeindex::*;
pub use unit::*;
pub use unknownoutputindex::*;
pub use utxo::*;
pub use validateaddressparam::*;
pub use vin::*;
pub use vout::*;
pub use vsize::*;
+7 -26
View File
@@ -1,12 +1,11 @@
use std::borrow::Cow;
use derive_deref::Deref;
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
use schemars::JsonSchema;
use serde::Deserialize;
/// Maximum number of results to return. Defaults to 100 if not specified.
#[derive(Debug, Deref, Deserialize)]
#[serde(default = "default_search_limit")]
#[derive(Debug, Deref, Deserialize, JsonSchema)]
#[serde(transparent)]
#[schemars(default, example = 1, example = 10, example = 100)]
pub struct Limit(usize);
impl Limit {
@@ -14,26 +13,8 @@ impl Limit {
pub const DEFAULT: Self = Self(100);
}
fn default_search_limit() -> Limit {
Limit::DEFAULT
}
impl JsonSchema for Limit {
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("Limit")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "object",
"properties": {
"limit": {
"type": "integer",
"description": "Maximum number of results to return. Defaults to 100 if not specified.",
"default": 100,
"examples": [1, 10, 100, 1000, 10000, 100000]
}
}
})
impl Default for Limit {
fn default() -> Self {
Self::DEFAULT
}
}
+10
View File
@@ -0,0 +1,10 @@
use schemars::JsonSchema;
use serde::Deserialize;
use crate::Limit;
#[derive(Deserialize, JsonSchema)]
pub struct LimitParam {
#[serde(default)]
pub limit: Limit,
}
+9 -17
View File
@@ -1,12 +1,18 @@
use std::{borrow::Cow, fmt::Display};
use std::fmt::Display;
use derive_deref::Deref;
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
use schemars::JsonSchema;
use serde::Deserialize;
/// Metric name
#[derive(Debug, Clone, Deref, Deserialize)]
#[derive(Debug, Clone, Deref, Deserialize, JsonSchema)]
#[serde(transparent)]
#[schemars(
with = "String",
example = &"price_close",
example = &"market_cap",
example = &"realized_price"
)]
pub struct Metric(String);
impl From<String> for Metric {
@@ -28,17 +34,3 @@ impl Display for Metric {
write!(f, "{}", self.0)
}
}
impl JsonSchema for Metric {
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("Metric")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({
"type": "string",
"description": "Metric name",
"examples": ["price_close", "market_cap", "realized_price"]
})
}
}
+9
View File
@@ -0,0 +1,9 @@
use schemars::JsonSchema;
use serde::Deserialize;
use crate::Metric;
#[derive(Deserialize, JsonSchema)]
pub struct MetricParam {
pub metric: Metric,
}
+7 -2
View File
@@ -6,9 +6,14 @@ use serde::Deserialize;
use super::Metric;
/// A list of metrics
/// Comma-separated list of metric names
#[derive(Debug, Deref, JsonSchema)]
#[schemars(transparent)]
#[schemars(
with = "String",
example = &"price_close",
example = &"price_close,market_cap",
example = &"realized_price,nvt_ratio,mvrv"
)]
pub struct Metrics(Vec<Metric>);
const MAX_VECS: usize = 32;
+3
View File
@@ -5,6 +5,9 @@ use crate::{Index, Metric};
#[derive(Deserialize, JsonSchema)]
pub struct MetricWithIndex {
/// Metric name
pub metric: Metric,
/// Aggregation index
pub index: Index,
}
+1
View File
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
pub struct Pagination {
/// Pagination index
#[serde(default, alias = "p")]
#[schemars(example = 0, example = 1, example = 2)]
pub page: Option<usize>,
}
@@ -3,9 +3,7 @@ use serde::Deserialize;
use super::PoolSlug;
/// Path parameter for pool detail endpoint
#[derive(Deserialize, JsonSchema)]
pub struct PoolSlugPath {
/// Pool slug (e.g., "foundryusa", "f2pool", "antpool")
pub struct PoolSlugParam {
pub slug: PoolSlug,
}
@@ -1,9 +1,11 @@
use schemars::JsonSchema;
use serde::Deserialize;
use crate::Height;
#[derive(Deserialize, JsonSchema)]
pub struct StartHeightPath {
pub struct StartHeightParam {
/// Starting block height (optional, defaults to latest)
#[schemars(example = 800000)]
pub start_height: Option<u32>,
pub start_height: Option<Height>,
}
+9
View File
@@ -0,0 +1,9 @@
use schemars::JsonSchema;
use serde::Deserialize;
use super::TimePeriod;
#[derive(Deserialize, JsonSchema)]
pub struct TimePeriodParam {
pub time_period: TimePeriod,
}
-12
View File
@@ -1,12 +0,0 @@
use schemars::JsonSchema;
use serde::Deserialize;
use super::TimePeriod;
/// Path parameter for mining pool statistics time period
#[derive(Deserialize, JsonSchema)]
pub struct TimePeriodPath {
/// Time period for statistics.
/// Valid values: 24h, 3d, 1w, 1m, 3m, 6m, 1y, 2y, 3y
pub time_period: TimePeriod,
}
+2 -1
View File
@@ -8,7 +8,7 @@ use vecdb::{CheckedSub, Formattable, Pco};
use super::Date;
/// Timestamp
/// UNIX timestamp in seconds
#[derive(
Debug,
Default,
@@ -24,6 +24,7 @@ use super::Date;
Pco,
JsonSchema,
)]
#[schemars(example = 1672531200)]
pub struct Timestamp(u32);
pub const ONE_HOUR_IN_SEC: u32 = 60 * 60;
@@ -4,8 +4,6 @@ use serde::Deserialize;
use crate::Timestamp;
#[derive(Deserialize, JsonSchema)]
pub struct TimestampPath {
/// UNIX timestamp in seconds
#[schemars(example = 1672531200)]
pub struct TimestampParam {
pub timestamp: Timestamp,
}
+9
View File
@@ -0,0 +1,9 @@
use schemars::JsonSchema;
use serde::Deserialize;
use crate::Txid;
#[derive(Deserialize, JsonSchema)]
pub struct TxidParam {
pub txid: Txid,
}
-9
View File
@@ -1,9 +0,0 @@
use schemars::JsonSchema;
use serde::Deserialize;
#[derive(Deserialize, JsonSchema)]
pub struct TxidPath {
/// Bitcoin transaction id
#[schemars(example = &"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")]
pub txid: String,
}
+10
View File
@@ -0,0 +1,10 @@
use schemars::JsonSchema;
use serde::Deserialize;
use crate::{Txid, Vout};
#[derive(Deserialize, JsonSchema)]
pub struct TxidVout {
pub txid: Txid,
pub vout: Vout,
}
-15
View File
@@ -1,15 +0,0 @@
use schemars::JsonSchema;
use serde::Deserialize;
use crate::Vout;
#[derive(Deserialize, JsonSchema)]
pub struct TxidVoutPath {
/// Bitcoin transaction id
#[schemars(example = &"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")]
pub txid: String,
/// Output index
#[schemars(example = 0)]
pub vout: Vout,
}
+2 -1
View File
@@ -3,7 +3,7 @@ use std::ops::{Add, AddAssign};
use byteview::ByteView;
use derive_deref::{Deref, DerefMut};
use schemars::JsonSchema;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
use super::StoredU32;
@@ -20,6 +20,7 @@ use super::StoredU32;
DerefMut,
Default,
Serialize,
Deserialize,
Pco,
JsonSchema,
Hash,
@@ -0,0 +1,8 @@
use schemars::JsonSchema;
use serde::Deserialize;
#[derive(Deserialize, JsonSchema)]
pub struct ValidateAddressParam {
/// Bitcoin address to validate (can be any string)
pub address: String,
}
+1
View File
@@ -4,6 +4,7 @@ use serde::Serialize;
/// Input index in the spending transaction
#[derive(Debug, Deref, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, JsonSchema)]
#[schemars(example = 0)]
pub struct Vin(u16);
impl Vin {
+1
View File
@@ -20,6 +20,7 @@ use vecdb::{Bytes, Formattable};
Bytes,
Hash,
)]
#[schemars(example = 0)]
pub struct Vout(u16);
impl Vout {