mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-08 14:11:56 -07:00
server: openapi fixes
This commit is contained in:
@@ -62,21 +62,19 @@ fn run() -> Result<()> {
|
||||
.approximate_len()
|
||||
);
|
||||
|
||||
let _ = dbg!(query.address(Address {
|
||||
address: "bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38".to_string(),
|
||||
}));
|
||||
let _ = dbg!(query.address(Address::from(
|
||||
"bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38".to_string(),
|
||||
)));
|
||||
|
||||
let _ = dbg!(query.address_txids(
|
||||
Address {
|
||||
address: "bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38".to_string(),
|
||||
},
|
||||
Address::from("bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38".to_string()),
|
||||
None,
|
||||
25
|
||||
));
|
||||
|
||||
let _ = dbg!(query.address_utxos(Address {
|
||||
address: "bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38".to_string(),
|
||||
}));
|
||||
let _ = dbg!(query.address_utxos(Address::from(
|
||||
"bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38".to_string()
|
||||
)));
|
||||
|
||||
// dbg!(query.search_and_format(MetricSelection {
|
||||
// index: Index::Height,
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::Query;
|
||||
const MAX_MEMPOOL_TXIDS: usize = 50;
|
||||
|
||||
impl Query {
|
||||
pub fn address(&self, Address { address }: Address) -> Result<AddressStats> {
|
||||
pub fn address(&self, address: Address) -> Result<AddressStats> {
|
||||
let indexer = self.indexer();
|
||||
let computer = self.computer();
|
||||
let stores = &indexer.stores;
|
||||
@@ -74,7 +74,7 @@ impl Query {
|
||||
};
|
||||
|
||||
Ok(AddressStats {
|
||||
address: address.into(),
|
||||
address,
|
||||
chain_stats: AddressChainStats {
|
||||
type_index,
|
||||
funded_txo_count: address_data.funded_txo_count,
|
||||
@@ -204,7 +204,7 @@ impl Query {
|
||||
pub fn address_mempool_txids(&self, address: Address) -> Result<Vec<Txid>> {
|
||||
let mempool = self.mempool().ok_or(Error::Str("Mempool not available"))?;
|
||||
|
||||
let bytes = AddressBytes::from_str(&address.address)?;
|
||||
let bytes = AddressBytes::from_str(&address)?;
|
||||
let addresses = mempool.get_addresses();
|
||||
|
||||
let txids: Vec<Txid> = addresses
|
||||
@@ -219,7 +219,7 @@ impl Query {
|
||||
fn resolve_address(&self, address: &Address) -> Result<(OutputType, TypeIndex)> {
|
||||
let stores = &self.indexer().stores;
|
||||
|
||||
let bytes = AddressBytes::from_str(&address.address)?;
|
||||
let bytes = AddressBytes::from_str(address)?;
|
||||
let outputtype = OutputType::from(&bytes);
|
||||
let hash = AddressHash::from(&bytes);
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ impl ApiMetricsRoutes for ApiRouter<AppState> {
|
||||
},
|
||||
|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>>()
|
||||
|
||||
@@ -8,7 +8,7 @@ use axum::{
|
||||
use brk_types::{
|
||||
BlockCountPath, BlockFeeRatesEntry, BlockFeesEntry, BlockRewardsEntry, BlockSizesWeights,
|
||||
DifficultyAdjustment, DifficultyAdjustmentEntry, HashrateSummary, PoolDetail, PoolInfo,
|
||||
PoolSlugPath, PoolsSummary, RewardStats, TimePeriodPath,
|
||||
PoolSlugPath, PoolsSummary, RewardStats, TimePeriod,
|
||||
};
|
||||
|
||||
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(path): Path<TimePeriodPath>, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::height_with(format!("{:?}", path.time_period)), move |q| q.mining_pools(path.time_period)).await
|
||||
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
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
@@ -110,8 +110,8 @@ impl MiningRoutes for ApiRouter<AppState> {
|
||||
.api_route(
|
||||
"/api/v1/mining/hashrate/{time_period}",
|
||||
get_with(
|
||||
async |headers: HeaderMap, Path(path): Path<TimePeriodPath>, 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
|
||||
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
|
||||
},
|
||||
|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(path): Path<TimePeriodPath>, 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
|
||||
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
|
||||
},
|
||||
|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(path): Path<TimePeriodPath>, 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
|
||||
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
|
||||
},
|
||||
|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(path): Path<TimePeriodPath>, 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
|
||||
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
|
||||
},
|
||||
|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(path): Path<TimePeriodPath>, 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
|
||||
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
|
||||
},
|
||||
|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(path): Path<TimePeriodPath>, 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
|
||||
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
|
||||
},
|
||||
|op| {
|
||||
op.mining_tag()
|
||||
|
||||
@@ -51,7 +51,11 @@ impl ApiRoutes for ApiRouter<AppState> {
|
||||
"/version",
|
||||
get_with(
|
||||
async |headers: HeaderMap, State(state): State<AppState>| {
|
||||
state.cached_json(&headers, CacheStrategy::Static, |_| Ok(env!("CARGO_PKG_VERSION"))).await
|
||||
state
|
||||
.cached_json(&headers, CacheStrategy::Static, |_| {
|
||||
Ok(env!("CARGO_PKG_VERSION"))
|
||||
})
|
||||
.await
|
||||
},
|
||||
|op| {
|
||||
op.server_tag()
|
||||
|
||||
@@ -1,32 +1,49 @@
|
||||
use std::{fmt, str::FromStr};
|
||||
use std::{borrow::Cow, fmt, str::FromStr};
|
||||
|
||||
use bitcoin::ScriptBuf;
|
||||
use brk_error::Error;
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::AddressBytes;
|
||||
|
||||
use super::OutputType;
|
||||
|
||||
#[derive(Debug, Deref, Deserialize, JsonSchema)]
|
||||
pub struct Address {
|
||||
/// Bitcoin address string
|
||||
#[schemars(example = &"04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f")]
|
||||
pub address: String,
|
||||
/// Bitcoin address string
|
||||
#[derive(Debug, Deref, Deserialize)]
|
||||
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.address)
|
||||
f.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Address {
|
||||
#[inline]
|
||||
fn from(address: String) -> Self {
|
||||
Self { address }
|
||||
Self(address)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +58,7 @@ impl TryFrom<(&ScriptBuf, OutputType)> for Address {
|
||||
type Error = Error;
|
||||
fn try_from((script, outputtype): (&ScriptBuf, OutputType)) -> Result<Self, Self::Error> {
|
||||
if outputtype.is_address() {
|
||||
Ok(Self {
|
||||
address: script.to_hex_string(),
|
||||
})
|
||||
Ok(Self(script.to_hex_string()))
|
||||
} else {
|
||||
Err(Error::InvalidAddress)
|
||||
}
|
||||
@@ -78,7 +93,7 @@ impl Serialize for Address {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(&self.address)
|
||||
serializer.collect_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,15 +102,13 @@ impl FromStr for Address {
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let _ = AddressBytes::address_to_script(s)?;
|
||||
Ok(Self {
|
||||
address: s.to_string(),
|
||||
})
|
||||
Ok(Self(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Address {
|
||||
/// Get the script for this address
|
||||
pub fn script(&self) -> Result<ScriptBuf, Error> {
|
||||
AddressBytes::address_to_script(&self.address)
|
||||
AddressBytes::address_to_script(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{de_unquote_i64, de_unquote_usize};
|
||||
|
||||
/// Range parameters for slicing data
|
||||
#[derive(Default, Debug, Deserialize, JsonSchema)]
|
||||
pub struct DataRange {
|
||||
/// Inclusive starting index, if negative will be from the end
|
||||
#[serde(default, alias = "f")]
|
||||
#[serde(default, alias = "f", deserialize_with = "de_unquote_i64")]
|
||||
#[schemars(example = 0, example = -1, example = -10, example = -1000)]
|
||||
from: Option<i64>,
|
||||
|
||||
/// Exclusive ending index, if negative will be from the end, overrides 'count'
|
||||
#[serde(default, alias = "t")]
|
||||
#[serde(default, alias = "t", deserialize_with = "de_unquote_i64")]
|
||||
#[schemars(example = 1000)]
|
||||
to: Option<i64>,
|
||||
|
||||
/// Number of values requested
|
||||
#[serde(default, alias = "c")]
|
||||
#[serde(default, alias = "c", deserialize_with = "de_unquote_usize")]
|
||||
#[schemars(example = 1, example = 10, example = 100)]
|
||||
count: Option<usize>,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde_json::Value;
|
||||
|
||||
pub fn de_unquote_i64<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value: Option<Value> = Option::deserialize(deserializer)?;
|
||||
|
||||
if value.is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let value = value.unwrap();
|
||||
|
||||
if let Some(mut s) = value.as_str().map(|s| s.to_string()) {
|
||||
if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
|
||||
s = s[1..s.len() - 1].to_string();
|
||||
}
|
||||
s.parse::<i64>().map(Some).map_err(serde::de::Error::custom)
|
||||
} else if let Some(n) = value.as_i64() {
|
||||
Ok(Some(n))
|
||||
} else {
|
||||
Err(serde::de::Error::custom("expected a string or number"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_unquote_usize<'de, D>(deserializer: D) -> Result<Option<usize>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value: Option<Value> = Option::deserialize(deserializer)?;
|
||||
|
||||
if value.is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let value = value.unwrap();
|
||||
|
||||
if let Some(mut s) = value.as_str().map(|s| s.to_string()) {
|
||||
if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
|
||||
s = s[1..s.len() - 1].to_string();
|
||||
}
|
||||
s.parse::<usize>()
|
||||
.map(Some)
|
||||
.map_err(serde::de::Error::custom)
|
||||
} else if let Some(n) = value.as_u64() {
|
||||
Ok(Some(n as usize))
|
||||
} else {
|
||||
Err(serde::de::Error::custom("expected a string or number"))
|
||||
}
|
||||
}
|
||||
@@ -13,64 +13,38 @@ use super::{
|
||||
SemesterIndex, TxIndex, UnknownOutputIndex, WeekIndex, YearIndex,
|
||||
};
|
||||
|
||||
/// Aggregation dimension for querying Bitcoin blockchain data
|
||||
/// Aggregation dimension for querying metrics. Includes time-based (date, week, month, year),
|
||||
/// block-based (height, txindex), and address/output type indexes.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[schemars(example = Index::DateIndex)]
|
||||
pub enum Index {
|
||||
/// Date/day index
|
||||
DateIndex,
|
||||
/// Decade index
|
||||
DecadeIndex,
|
||||
/// Difficulty epoch index (equivalent to ~2 weeks)
|
||||
DifficultyEpoch,
|
||||
/// Empty output index
|
||||
EmptyOutputIndex,
|
||||
/// Halving epoch index (equivalent to ~4 years)
|
||||
HalvingEpoch,
|
||||
/// Height/block index
|
||||
Height,
|
||||
/// Transaction input index (based on total)
|
||||
TxInIndex,
|
||||
/// Month index
|
||||
MonthIndex,
|
||||
/// Op return index
|
||||
OpReturnIndex,
|
||||
/// Transaction output index (based on total)
|
||||
TxOutIndex,
|
||||
/// Index of P2A address
|
||||
P2AAddressIndex,
|
||||
/// Index of P2MS output
|
||||
P2MSOutputIndex,
|
||||
/// Index of P2PK (33 bytes) address
|
||||
P2PK33AddressIndex,
|
||||
/// Index of P2PK (65 bytes) address
|
||||
P2PK65AddressIndex,
|
||||
/// Index of P2PKH address
|
||||
P2PKHAddressIndex,
|
||||
/// Index of P2SH address
|
||||
P2SHAddressIndex,
|
||||
/// Index of P2TR address
|
||||
P2TRAddressIndex,
|
||||
/// Index of P2WPKH address
|
||||
P2WPKHAddressIndex,
|
||||
/// Index of P2WSH address
|
||||
P2WSHAddressIndex,
|
||||
/// Quarter index
|
||||
QuarterIndex,
|
||||
/// Semester index
|
||||
SemesterIndex,
|
||||
/// Transaction index
|
||||
TxIndex,
|
||||
/// Unknown output index
|
||||
UnknownOutputIndex,
|
||||
/// Week index
|
||||
WeekIndex,
|
||||
/// Year index
|
||||
YearIndex,
|
||||
/// Loaded Address Index
|
||||
LoadedAddressIndex,
|
||||
/// Empty Address Index
|
||||
EmptyAddressIndex,
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ mod datarangeformat;
|
||||
mod date;
|
||||
mod dateindex;
|
||||
mod decadeindex;
|
||||
mod deser;
|
||||
mod difficultyadjustment;
|
||||
mod difficultyadjustmententry;
|
||||
mod difficultyentry;
|
||||
@@ -187,6 +188,7 @@ pub use datarangeformat::*;
|
||||
pub use date::*;
|
||||
pub use dateindex::*;
|
||||
pub use decadeindex::*;
|
||||
pub use deser::*;
|
||||
pub use difficultyadjustment::*;
|
||||
pub use difficultyadjustmententry::*;
|
||||
pub use difficultyentry::*;
|
||||
|
||||
@@ -1,26 +1,39 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deref, Deserialize, JsonSchema)]
|
||||
pub struct Limit {
|
||||
/// Maximum number of results to return. Defaults to 100 if not specified.
|
||||
#[serde(default = "default_search_limit")]
|
||||
#[schemars(
|
||||
example = "1",
|
||||
example = "10",
|
||||
example = "100",
|
||||
example = "1000",
|
||||
example = "10000",
|
||||
example = "100000"
|
||||
)]
|
||||
limit: usize,
|
||||
}
|
||||
/// Maximum number of results to return. Defaults to 100 if not specified.
|
||||
#[derive(Debug, Deref, Deserialize)]
|
||||
#[serde(default = "default_search_limit")]
|
||||
pub struct Limit(usize);
|
||||
|
||||
impl Limit {
|
||||
pub const MIN: Self = Self { limit: 1 };
|
||||
pub const MIN: Self = Self(1);
|
||||
pub const DEFAULT: Self = Self(100);
|
||||
}
|
||||
|
||||
fn default_search_limit() -> usize {
|
||||
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]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,44 @@
|
||||
use std::fmt::Display;
|
||||
use std::{borrow::Cow, fmt::Display};
|
||||
|
||||
use derive_deref::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone, Deref, Deserialize, JsonSchema)]
|
||||
pub struct Metric {
|
||||
/// Metric name
|
||||
#[schemars(example = &"price_close", example = &"market_cap", example = &"realized_price")]
|
||||
metric: String,
|
||||
}
|
||||
/// Metric name
|
||||
#[derive(Debug, Clone, Deref, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Metric(String);
|
||||
|
||||
impl From<String> for Metric {
|
||||
#[inline]
|
||||
fn from(metric: String) -> Self {
|
||||
Self { metric }
|
||||
Self(metric)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Metric {
|
||||
#[inline]
|
||||
fn from(metric: &str) -> Self {
|
||||
Self {
|
||||
metric: metric.to_string(),
|
||||
}
|
||||
Self(metric.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Metric {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.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"]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,10 @@ use serde::Deserialize;
|
||||
|
||||
use super::Metric;
|
||||
|
||||
/// A list of metrics
|
||||
#[derive(Debug, Deref, JsonSchema)]
|
||||
pub struct Metrics {
|
||||
/// A list of metrics
|
||||
metrics: Vec<Metric>,
|
||||
}
|
||||
#[schemars(transparent)]
|
||||
pub struct Metrics(Vec<Metric>);
|
||||
|
||||
const MAX_VECS: usize = 32;
|
||||
const MAX_STRING_SIZE: usize = 64 * MAX_VECS;
|
||||
@@ -18,9 +17,7 @@ const MAX_STRING_SIZE: usize = 64 * MAX_VECS;
|
||||
impl From<Metric> for Metrics {
|
||||
#[inline]
|
||||
fn from(metric: Metric) -> Self {
|
||||
Self {
|
||||
metrics: vec![metric],
|
||||
}
|
||||
Self(vec![metric])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,12 +31,12 @@ impl From<String> for Metrics {
|
||||
impl<'a> From<Vec<&'a str>> for Metrics {
|
||||
#[inline]
|
||||
fn from(value: Vec<&'a str>) -> Self {
|
||||
Self {
|
||||
metrics: value
|
||||
Self(
|
||||
value
|
||||
.iter()
|
||||
.map(|s| Metric::from(s.replace("-", "_").to_lowercase()))
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,23 +49,23 @@ impl<'de> Deserialize<'de> for Metrics {
|
||||
|
||||
if let Some(str) = value.as_str() {
|
||||
if str.len() <= MAX_STRING_SIZE {
|
||||
Ok(Self {
|
||||
metrics: sanitize(str.split(",").map(|s| s.to_string()))
|
||||
Ok(Self(
|
||||
sanitize(str.split(",").map(|s| s.to_string()))
|
||||
.into_iter()
|
||||
.map(Metric::from)
|
||||
.collect(),
|
||||
})
|
||||
))
|
||||
} else {
|
||||
Err(serde::de::Error::custom("Given parameter is too long"))
|
||||
}
|
||||
} else if let Some(vec) = value.as_array() {
|
||||
if vec.len() <= MAX_VECS {
|
||||
Ok(Self {
|
||||
metrics: sanitize(vec.iter().map(|s| s.as_str().unwrap().to_string()))
|
||||
Ok(Self(
|
||||
sanitize(vec.iter().map(|s| s.as_str().unwrap().to_string()))
|
||||
.into_iter()
|
||||
.map(Metric::from)
|
||||
.collect(),
|
||||
})
|
||||
))
|
||||
} else {
|
||||
Err(serde::de::Error::custom("Given parameter is too long"))
|
||||
}
|
||||
@@ -81,7 +78,7 @@ impl<'de> Deserialize<'de> for Metrics {
|
||||
impl fmt::Display for Metrics {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = self
|
||||
.metrics
|
||||
.0
|
||||
.iter()
|
||||
.map(|m| m.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
|
||||
@@ -4,8 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||
use strum::Display;
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
// Created from the list in `pools.rs`
|
||||
// Can be used as index for said list
|
||||
// Slug of a mining pool
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(
|
||||
Debug,
|
||||
|
||||
@@ -5,34 +5,35 @@ use serde::{Deserialize, Serialize};
|
||||
///
|
||||
/// Used to specify the lookback window for pool statistics, hashrate calculations,
|
||||
/// and other time-based mining metrics.
|
||||
///
|
||||
/// - `Day` (alias: 24h) - Last 24 hours (~144 blocks)
|
||||
/// - `ThreeDays` (alias: 3d) - Last 3 days (~432 blocks)
|
||||
/// - `Week` (alias: 1w) - Last week (~1008 blocks)
|
||||
/// - `Month` (alias: 1m) - Last month (~4320 blocks)
|
||||
/// - `ThreeMonths` (alias: 3m) - Last 3 months (~12960 blocks)
|
||||
/// - `SixMonths` (alias: 6m) - Last 6 months (~25920 blocks)
|
||||
/// - `Year` (alias: 1y) - Last year (~52560 blocks)
|
||||
/// - `TwoYears` (alias: 2y) - Last 2 years (~105120 blocks)
|
||||
/// - `ThreeYears` (alias: 3y) - Last 3 years (~157680 blocks)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum TimePeriod {
|
||||
/// Last 24 hours (~144 blocks)
|
||||
#[serde(rename = "24h")]
|
||||
#[serde(alias = "24h")]
|
||||
Day,
|
||||
/// Last 3 days (~432 blocks)
|
||||
#[serde(rename = "3d")]
|
||||
#[serde(alias = "3d")]
|
||||
ThreeDays,
|
||||
/// Last week (~1008 blocks)
|
||||
#[serde(rename = "1w")]
|
||||
#[serde(alias = "1w")]
|
||||
Week,
|
||||
/// Last month (~4320 blocks)
|
||||
#[serde(rename = "1m")]
|
||||
#[serde(alias = "1m")]
|
||||
Month,
|
||||
/// Last 3 months (~12960 blocks)
|
||||
#[serde(rename = "3m")]
|
||||
#[serde(alias = "3m")]
|
||||
ThreeMonths,
|
||||
/// Last 6 months (~25920 blocks)
|
||||
#[serde(rename = "6m")]
|
||||
#[serde(alias = "6m")]
|
||||
SixMonths,
|
||||
/// Last year (~52560 blocks)
|
||||
#[serde(rename = "1y")]
|
||||
#[serde(alias = "1y")]
|
||||
Year,
|
||||
/// Last 2 years (~105120 blocks)
|
||||
#[serde(rename = "2y")]
|
||||
#[serde(alias = "2y")]
|
||||
TwoYears,
|
||||
/// Last 3 years (~157680 blocks)
|
||||
#[serde(rename = "3y")]
|
||||
#[serde(alias = "3y")]
|
||||
ThreeYears,
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use vecdb::{Bytes, Formattable};
|
||||
example = "9a0b3b8305bb30cacf9e8443a90d53a76379fb3305047fdeaa4e4b0934a2a1ba"
|
||||
)]
|
||||
#[repr(C)]
|
||||
#[schemars(transparent, with = "String")]
|
||||
pub struct Txid([u8; 32]);
|
||||
|
||||
impl Txid {
|
||||
|
||||
Reference in New Issue
Block a user