mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-29 22:19:28 -07:00
global: snapshot
This commit is contained in:
@@ -5,8 +5,8 @@ use axum::{
|
||||
};
|
||||
use brk_query::BLOCK_TXS_PAGE_SIZE;
|
||||
use brk_types::{
|
||||
BlockHashParam, BlockHashStartIndex, BlockHashTxIndex, BlockInfo, BlockStatus, BlockTimestamp,
|
||||
HeightParam, TimestampParam, Transaction, Txid,
|
||||
BlockHash, BlockHashParam, BlockHashStartIndex, BlockHashTxIndex, BlockInfo, BlockInfoV1,
|
||||
BlockStatus, BlockTimestamp, Height, HeightParam, Hex, TimestampParam, Transaction, Txid,
|
||||
};
|
||||
|
||||
use crate::{CacheStrategy, extended::TransformResponseExtended};
|
||||
@@ -61,6 +61,46 @@ impl BlockRoutes for ApiRouter<AppState> {
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/blocks",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| {
|
||||
state
|
||||
.cached_json(&headers, CacheStrategy::Height, &uri, move |q| q.blocks_v1(None))
|
||||
.await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_blocks_v1")
|
||||
.blocks_tag()
|
||||
.summary("Recent blocks with extras")
|
||||
.description("Retrieve the last 10 blocks with extended data including pool identification and fee statistics.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-blocks-v1)*")
|
||||
.ok_response::<Vec<BlockInfoV1>>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/blocks/{height}",
|
||||
get_with(
|
||||
async |uri: Uri,
|
||||
headers: HeaderMap,
|
||||
Path(path): Path<HeightParam>,
|
||||
State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::Height, &uri, move |q| q.blocks_v1(Some(path.height))).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_blocks_v1_from_height")
|
||||
.blocks_tag()
|
||||
.summary("Blocks from height with extras")
|
||||
.description("Retrieve up to 10 blocks with extended data going backwards from the given height.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-blocks-v1)*")
|
||||
.ok_response::<Vec<BlockInfoV1>>()
|
||||
.not_modified()
|
||||
.bad_request()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/block-height/{height}",
|
||||
get_with(
|
||||
@@ -68,16 +108,16 @@ impl BlockRoutes for ApiRouter<AppState> {
|
||||
headers: HeaderMap,
|
||||
Path(path): Path<HeightParam>,
|
||||
State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::Height, &uri, move |q| q.block_by_height(path.height)).await
|
||||
state.cached_text(&headers, CacheStrategy::Height, &uri, move |q| q.block_hash_by_height(path.height).map(|h| h.to_string())).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_block_by_height")
|
||||
.blocks_tag()
|
||||
.summary("Block by height")
|
||||
.summary("Block hash by height")
|
||||
.description(
|
||||
"Retrieve block information by block height. Returns block metadata including hash, timestamp, difficulty, size, weight, and transaction count.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-block-height)*",
|
||||
"Retrieve the block hash at a given height. Returns the hash as plain text.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-block-height)*",
|
||||
)
|
||||
.ok_response::<BlockInfo>()
|
||||
.ok_response::<BlockHash>()
|
||||
.not_modified()
|
||||
.bad_request()
|
||||
.not_found()
|
||||
@@ -230,6 +270,79 @@ impl BlockRoutes for ApiRouter<AppState> {
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/blocks/tip/height",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| {
|
||||
state.cached_text(&headers, CacheStrategy::Height, &uri, |q| Ok(q.height().to_string())).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_block_tip_height")
|
||||
.blocks_tag()
|
||||
.summary("Block tip height")
|
||||
.description("Returns the height of the last block.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-block-tip-height)*")
|
||||
.ok_response::<Height>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/blocks/tip/hash",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| {
|
||||
state.cached_text(&headers, CacheStrategy::Height, &uri, |q| q.block_hash_by_height(q.height()).map(|h| h.to_string())).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_block_tip_hash")
|
||||
.blocks_tag()
|
||||
.summary("Block tip hash")
|
||||
.description("Returns the hash of the last block.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-block-tip-hash)*")
|
||||
.ok_response::<BlockHash>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/block/{hash}/header",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, Path(path): Path<BlockHashParam>, State(state): State<AppState>| {
|
||||
state.cached_text(&headers, CacheStrategy::Height, &uri, move |q| q.block_header_hex(&path.hash)).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_block_header")
|
||||
.blocks_tag()
|
||||
.summary("Block header")
|
||||
.description("Returns the hex-encoded block header.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-block-header)*")
|
||||
.ok_response::<Hex>()
|
||||
.not_modified()
|
||||
.not_found()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/block/{hash}",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, Path(path): Path<BlockHashParam>, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::Height, &uri, move |q| {
|
||||
let height = q.height_by_hash(&path.hash)?;
|
||||
q.block_by_height_v1(height)
|
||||
}).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_block_v1")
|
||||
.blocks_tag()
|
||||
.summary("Block (v1)")
|
||||
.description("Returns block details with extras by hash.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-block-v1)*")
|
||||
.ok_response::<BlockInfoV1>()
|
||||
.not_modified()
|
||||
.not_found()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/blocks/timestamp/{timestamp}",
|
||||
get_with(
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use aide::axum::{ApiRouter, routing::get_with};
|
||||
use axum::{
|
||||
extract::State,
|
||||
extract::{Query, State},
|
||||
http::{HeaderMap, Uri},
|
||||
response::Redirect,
|
||||
routing::get,
|
||||
};
|
||||
use brk_types::{Dollars, MempoolBlock, MempoolInfo, RecommendedFees, Txid};
|
||||
use brk_types::{
|
||||
Dollars, HistoricalPrice, MempoolBlock, MempoolInfo, MempoolRecentTx, OptionalTimestampParam,
|
||||
RecommendedFees, Txid,
|
||||
};
|
||||
|
||||
use crate::extended::TransformResponseExtended;
|
||||
use crate::{CacheStrategy, extended::TransformResponseExtended};
|
||||
|
||||
use super::AppState;
|
||||
|
||||
@@ -18,9 +19,8 @@ pub trait MempoolRoutes {
|
||||
impl MempoolRoutes for ApiRouter<AppState> {
|
||||
fn add_mempool_routes(self) -> Self {
|
||||
self
|
||||
.route("/api/mempool", get(Redirect::temporary("/api#tag/mempool")))
|
||||
.api_route(
|
||||
"/api/mempool/info",
|
||||
"/api/mempool",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, state.mempool_cache(), &uri, |q| q.mempool_info()).await
|
||||
@@ -51,6 +51,22 @@ impl MempoolRoutes for ApiRouter<AppState> {
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/mempool/recent",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, state.mempool_cache(), &uri, |q| q.mempool_recent()).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_mempool_recent")
|
||||
.mempool_tag()
|
||||
.summary("Recent mempool transactions")
|
||||
.description("Get the last 10 transactions to enter the mempool.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-mempool-recent)*")
|
||||
.ok_response::<Vec<MempoolRecentTx>>()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/mempool/price",
|
||||
get_with(
|
||||
@@ -87,6 +103,22 @@ impl MempoolRoutes for ApiRouter<AppState> {
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/fees/precise",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, state.mempool_cache(), &uri, |q| q.recommended_fees()).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_precise_fees")
|
||||
.mempool_tag()
|
||||
.summary("Precise recommended fees")
|
||||
.description("Get recommended fee rates with up to 3 decimal places, including sub-sat feerates.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees-precise)*")
|
||||
.ok_response::<RecommendedFees>()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/fees/mempool-blocks",
|
||||
get_with(
|
||||
@@ -103,5 +135,22 @@ impl MempoolRoutes for ApiRouter<AppState> {
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/historical-price",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, Query(params): Query<OptionalTimestampParam>, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::Height, &uri, move |q| q.historical_price(params.timestamp)).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_historical_price")
|
||||
.mempool_tag()
|
||||
.summary("Historical price")
|
||||
.description("Get historical BTC/USD price. Optionally specify a UNIX timestamp to get the price at that time.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-historical-price)*")
|
||||
.ok_response::<HistoricalPrice>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ use axum::{
|
||||
routing::get,
|
||||
};
|
||||
use brk_types::{
|
||||
BlockCountParam, BlockFeesEntry, BlockRewardsEntry, BlockSizesWeights, DifficultyAdjustment,
|
||||
DifficultyAdjustmentEntry, HashrateSummary, PoolDetail, PoolInfo, PoolSlugParam, PoolsSummary,
|
||||
BlockCountParam, BlockFeesEntry, BlockInfoV1, BlockRewardsEntry, BlockSizesWeights,
|
||||
DifficultyAdjustment, DifficultyAdjustmentEntry, HashrateSummary, PoolDetail,
|
||||
PoolHashrateEntry, PoolInfo, PoolSlugAndHeightParam, PoolSlugParam, PoolsSummary,
|
||||
RewardStats, TimePeriodParam,
|
||||
};
|
||||
|
||||
@@ -95,6 +96,94 @@ impl MiningRoutes for ApiRouter<AppState> {
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/pool/{slug}/blocks",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, Path(path): Path<PoolSlugParam>, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::Height, &uri, move |q| q.pool_blocks(path.slug, None)).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_pool_blocks")
|
||||
.mining_tag()
|
||||
.summary("Mining pool blocks")
|
||||
.description("Get the 10 most recent blocks mined by a specific pool.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-mining-pool-blocks)*")
|
||||
.ok_response::<Vec<BlockInfoV1>>()
|
||||
.not_modified()
|
||||
.not_found()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/pool/{slug}/blocks/{height}",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, Path(PoolSlugAndHeightParam {slug, height}): Path<PoolSlugAndHeightParam>, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::Height, &uri, move |q| q.pool_blocks(slug, Some(height))).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_pool_blocks_from")
|
||||
.mining_tag()
|
||||
.summary("Mining pool blocks from height")
|
||||
.description("Get 10 blocks mined by a specific pool before (and including) the given height.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-mining-pool-blocks)*")
|
||||
.ok_response::<Vec<BlockInfoV1>>()
|
||||
.not_modified()
|
||||
.not_found()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/pool/{slug}/hashrate",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, Path(path): Path<PoolSlugParam>, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::Height, &uri, move |q| q.pool_hashrate(path.slug)).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_pool_hashrate")
|
||||
.mining_tag()
|
||||
.summary("Mining pool hashrate")
|
||||
.description("Get hashrate history for a specific mining pool.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-mining-pool-hashrate)*")
|
||||
.ok_response::<Vec<PoolHashrateEntry>>()
|
||||
.not_modified()
|
||||
.not_found()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/hashrate/pools",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::Height, &uri, |q| q.pools_hashrate(None)).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_pools_hashrate")
|
||||
.mining_tag()
|
||||
.summary("All pools hashrate (all time)")
|
||||
.description("Get hashrate data for all mining pools.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-mining-pool-hashrates)*")
|
||||
.ok_response::<Vec<PoolHashrateEntry>>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/hashrate/pools/{time_period}",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::Height, &uri, move |q| q.pools_hashrate(Some(path.time_period))).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_pools_hashrate_by_period")
|
||||
.mining_tag()
|
||||
.summary("All pools hashrate")
|
||||
.description("Get hashrate data for all mining pools for a time period. Valid periods: 1m, 3m, 6m, 1y, 2y, 3y\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-mining-pool-hashrates)*")
|
||||
.ok_response::<Vec<PoolHashrateEntry>>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/mining/hashrate",
|
||||
get_with(
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
use aide::axum::{ApiRouter, routing::get_with};
|
||||
use aide::axum::{
|
||||
ApiRouter,
|
||||
routing::{get_with, post_with},
|
||||
};
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::{HeaderMap, Uri},
|
||||
response::Redirect,
|
||||
routing::get,
|
||||
};
|
||||
use brk_types::{Hex, Transaction, TxOutspend, TxStatus, TxidParam, TxidVout};
|
||||
use axum::extract::Query;
|
||||
use brk_types::{
|
||||
CpfpInfo, Hex, MerkleProof, Transaction, TxOutspend, TxStatus, Txid, TxidParam, TxidVout,
|
||||
TxidsParam,
|
||||
};
|
||||
|
||||
use crate::{CacheStrategy, extended::TransformResponseExtended};
|
||||
|
||||
@@ -18,8 +23,6 @@ pub trait TxRoutes {
|
||||
impl TxRoutes for ApiRouter<AppState> {
|
||||
fn add_tx_routes(self) -> Self {
|
||||
self
|
||||
.route("/api/tx", get(Redirect::temporary("/api/transactions")))
|
||||
.route("/api/transactions", get(Redirect::temporary("/api#tag/transactions")))
|
||||
.api_route(
|
||||
"/api/tx/{txid}",
|
||||
get_with(
|
||||
@@ -146,5 +149,92 @@ impl TxRoutes for ApiRouter<AppState> {
|
||||
.server_error(),
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/tx",
|
||||
post_with(
|
||||
async |State(state): State<AppState>, body: String| {
|
||||
let hex = body.trim().to_string();
|
||||
state.sync(|q| q.broadcast_transaction(&hex))
|
||||
.map(|txid| txid.to_string())
|
||||
.map_err(crate::Error::from)
|
||||
},
|
||||
|op| {
|
||||
op.id("post_tx")
|
||||
.transactions_tag()
|
||||
.summary("Broadcast transaction")
|
||||
.description("Broadcast a raw transaction to the network. The transaction should be provided as hex in the request body. The txid will be returned on success.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#post-transaction)*")
|
||||
.ok_response::<Txid>()
|
||||
.bad_request()
|
||||
.server_error()
|
||||
},
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/tx/{txid}/raw",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, Path(txid): Path<TxidParam>, State(state): State<AppState>| {
|
||||
state.cached_bytes(&headers, CacheStrategy::Height, &uri, move |q| q.transaction_raw(txid)).await
|
||||
},
|
||||
|op| op
|
||||
.id("get_tx_raw")
|
||||
.transactions_tag()
|
||||
.summary("Transaction raw")
|
||||
.description("Returns a transaction as binary data.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-transaction-raw)*")
|
||||
.ok_response::<Vec<u8>>()
|
||||
.not_modified()
|
||||
.bad_request()
|
||||
.not_found()
|
||||
.server_error(),
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/tx/{txid}/merkle-proof",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, Path(txid): Path<TxidParam>, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::Height, &uri, move |q| q.merkle_proof(txid)).await
|
||||
},
|
||||
|op| op
|
||||
.id("get_tx_merkle_proof")
|
||||
.transactions_tag()
|
||||
.summary("Transaction merkle proof")
|
||||
.description("Get the merkle inclusion proof for a transaction.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-transaction-merkle-proof)*")
|
||||
.ok_response::<MerkleProof>()
|
||||
.not_modified()
|
||||
.bad_request()
|
||||
.not_found()
|
||||
.server_error(),
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/cpfp/{txid}",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, Path(txid): Path<TxidParam>, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::MempoolHash(0), &uri, move |q| q.cpfp(txid)).await
|
||||
},
|
||||
|op| op
|
||||
.id("get_cpfp")
|
||||
.transactions_tag()
|
||||
.summary("CPFP info")
|
||||
.description("Returns ancestors and descendants for a CPFP transaction.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-children-pay-for-parent)*")
|
||||
.ok_response::<CpfpInfo>()
|
||||
.not_found()
|
||||
.server_error(),
|
||||
),
|
||||
)
|
||||
.api_route(
|
||||
"/api/v1/transaction-times",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, Query(params): Query<TxidsParam>, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::MempoolHash(0), &uri, move |q| q.transaction_times(¶ms.txids)).await
|
||||
},
|
||||
|op| op
|
||||
.id("get_transaction_times")
|
||||
.transactions_tag()
|
||||
.summary("Transaction first-seen times")
|
||||
.description("Returns timestamps when transactions were first seen in the mempool. Returns 0 for mined or unknown transactions.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-transaction-times)*")
|
||||
.ok_response::<Vec<u64>>()
|
||||
.server_error(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use aide::OperationOutput;
|
||||
use axum::{
|
||||
http::{StatusCode, header},
|
||||
response::{IntoResponse, Response},
|
||||
@@ -157,6 +158,10 @@ impl From<BrkError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl OperationOutput for Error {
|
||||
type Inner = ();
|
||||
}
|
||||
|
||||
impl IntoResponse for Error {
|
||||
fn into_response(self) -> Response {
|
||||
let body = build_error_body(self.status, self.code, self.message);
|
||||
|
||||
Reference in New Issue
Block a user