mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-02 10:30:00 -07:00
server: openapi fixes
This commit is contained in:
@@ -22,6 +22,16 @@ impl Query {
|
||||
}
|
||||
|
||||
pub fn metric_not_found_error(&self, metric: &Metric) -> Error {
|
||||
// Check if metric exists but with different indexes
|
||||
if let Some(indexes) = self.vecs().metric_to_indexes(metric.clone()) {
|
||||
let index_list: Vec<_> = indexes.iter().map(|i| i.to_string()).collect();
|
||||
return Error::String(format!(
|
||||
"'{metric}' doesn't support the requested index. Supported indexes: {}",
|
||||
index_list.join(", ")
|
||||
));
|
||||
}
|
||||
|
||||
// Metric doesn't exist, suggest alternatives
|
||||
if let Some(first) = self.match_metric(metric, Limit::MIN).first() {
|
||||
Error::String(format!("Could not find '{metric}', did you mean '{first}'?"))
|
||||
} else {
|
||||
@@ -77,8 +87,9 @@ impl Query {
|
||||
metric: &dyn AnyExportableVec,
|
||||
params: &DataRangeFormat,
|
||||
) -> Result<Output> {
|
||||
let len = metric.len();
|
||||
let from = params.from().map(|from| metric.i64_to_usize(from));
|
||||
let to = params.to().map(|to| metric.i64_to_usize(to));
|
||||
let to = params.to_for_len(len).map(|to| metric.i64_to_usize(to));
|
||||
|
||||
Ok(match params.format() {
|
||||
Format::CSV => Output::CSV(Self::columns_to_csv(
|
||||
@@ -100,6 +111,9 @@ impl Query {
|
||||
metrics: &[&dyn AnyExportableVec],
|
||||
params: &DataRangeFormat,
|
||||
) -> Result<Output> {
|
||||
// Use min length across metrics for consistent count resolution
|
||||
let min_len = metrics.iter().map(|v| v.len()).min().unwrap_or(0);
|
||||
|
||||
let from = params.from().map(|from| {
|
||||
metrics
|
||||
.iter()
|
||||
@@ -108,7 +122,7 @@ impl Query {
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
let to = params.to().map(|to| {
|
||||
let to = params.to_for_len(min_len).map(|to| {
|
||||
metrics
|
||||
.iter()
|
||||
.map(|v| v.i64_to_usize(to))
|
||||
@@ -143,13 +157,20 @@ impl Query {
|
||||
})
|
||||
}
|
||||
|
||||
/// Search for vecs matching the given metrics and index
|
||||
pub fn search(&self, params: &MetricSelection) -> Vec<&'static dyn AnyExportableVec> {
|
||||
params
|
||||
.metrics
|
||||
.iter()
|
||||
.filter_map(|metric| self.vecs().get(metric, params.index))
|
||||
.collect()
|
||||
/// Search for vecs matching the given metrics and index.
|
||||
/// Returns error if no metrics requested or any requested metric is not found.
|
||||
pub fn search(&self, params: &MetricSelection) -> Result<Vec<&'static dyn AnyExportableVec>> {
|
||||
if params.metrics.is_empty() {
|
||||
return Err(Error::String("No metrics specified".to_string()));
|
||||
}
|
||||
let mut vecs = Vec::with_capacity(params.metrics.len());
|
||||
for metric in params.metrics.iter() {
|
||||
match self.vecs().get(metric, params.index) {
|
||||
Some(vec) => vecs.push(vec),
|
||||
None => return Err(self.metric_not_found_error(metric)),
|
||||
}
|
||||
}
|
||||
Ok(vecs)
|
||||
}
|
||||
|
||||
/// Calculate total weight of the vecs for the given range
|
||||
@@ -168,14 +189,11 @@ impl Query {
|
||||
params: MetricSelection,
|
||||
max_weight: usize,
|
||||
) -> Result<Output> {
|
||||
let vecs = self.search(¶ms);
|
||||
let vecs = self.search(¶ms)?;
|
||||
|
||||
let Some(metric) = vecs.first() else {
|
||||
let metric = params.metrics.first().cloned().unwrap_or_else(|| Metric::from(""));
|
||||
return Err(self.metric_not_found_error(&metric));
|
||||
};
|
||||
let metric = vecs.first().expect("search guarantees non-empty on success");
|
||||
|
||||
let weight = Self::weight(&vecs, params.from(), params.to());
|
||||
let weight = Self::weight(&vecs, params.from(), params.to_for_len(metric.len()));
|
||||
if weight > max_weight {
|
||||
return Err(Error::String(format!(
|
||||
"Request too heavy: {weight} bytes exceeds limit of {max_weight} bytes"
|
||||
@@ -196,13 +214,10 @@ impl Query {
|
||||
params: MetricSelection,
|
||||
max_weight: usize,
|
||||
) -> Result<Output> {
|
||||
let vecs = self.search(¶ms);
|
||||
let vecs = self.search(¶ms)?;
|
||||
|
||||
if vecs.is_empty() {
|
||||
return Ok(Output::default(params.range.format()));
|
||||
}
|
||||
|
||||
let weight = Self::weight(&vecs, params.from(), params.to());
|
||||
let min_len = vecs.iter().map(|v| v.len()).min().expect("search guarantees non-empty");
|
||||
let weight = Self::weight(&vecs, params.from(), params.to_for_len(min_len));
|
||||
if weight > max_weight {
|
||||
return Err(Error::String(format!(
|
||||
"Request too heavy: {weight} bytes exceeds limit of {max_weight} bytes"
|
||||
|
||||
@@ -9,12 +9,14 @@ use crate::{DataRangeFormat, LegacyValue, MetricSelection, OutputLegacy, Query};
|
||||
impl Query {
|
||||
/// Deprecated - raw data without MetricData wrapper
|
||||
pub fn format_legacy(&self, metrics: &[&dyn AnyExportableVec], params: &DataRangeFormat) -> Result<OutputLegacy> {
|
||||
let min_len = metrics.iter().map(|v| v.len()).min().unwrap_or(0);
|
||||
|
||||
let from = params
|
||||
.from()
|
||||
.map(|from| metrics.iter().map(|v| v.i64_to_usize(from)).min().unwrap_or_default());
|
||||
|
||||
let to = params
|
||||
.to()
|
||||
.to_for_len(min_len)
|
||||
.map(|to| metrics.iter().map(|v| v.i64_to_usize(to)).min().unwrap_or_default());
|
||||
|
||||
let format = params.format();
|
||||
@@ -57,13 +59,10 @@ impl Query {
|
||||
|
||||
/// Deprecated - use search_and_format_checked instead
|
||||
pub fn search_and_format_legacy_checked(&self, params: MetricSelection, max_weight: usize) -> Result<OutputLegacy> {
|
||||
let vecs = self.search(¶ms);
|
||||
let vecs = self.search(¶ms)?;
|
||||
|
||||
if vecs.is_empty() {
|
||||
return Ok(OutputLegacy::default(params.range.format()));
|
||||
}
|
||||
|
||||
let weight = Self::weight(&vecs, params.from(), params.to());
|
||||
let min_len = vecs.iter().map(|v| v.len()).min().expect("search guarantees non-empty");
|
||||
let weight = Self::weight(&vecs, params.from(), params.to_for_len(min_len));
|
||||
if weight > max_weight {
|
||||
return Err(Error::String(format!(
|
||||
"Request too heavy: {weight} bytes exceeds limit of {max_weight} bytes"
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
// TODO: INCOMPLETE - indexes_to_fee_rate.dateindex doesn't have percentile fields
|
||||
// because from_txindex.rs calls remove_percentiles() before creating dateindex.
|
||||
// Need to either:
|
||||
// 1. Use .height instead and convert height to dateindex for iteration
|
||||
// 2. Fix from_txindex.rs to preserve percentiles for dateindex
|
||||
// 3. Create a separate dateindex computation path with percentiles
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_types::{BlockFeeRatesEntry, FeeRatePercentiles, TimePeriod};
|
||||
use vecdb::{IterableVec, VecIndex};
|
||||
@@ -6,38 +15,42 @@ use super::dateindex_iter::DateIndexIter;
|
||||
use crate::Query;
|
||||
|
||||
impl Query {
|
||||
pub fn block_fee_rates(&self, time_period: TimePeriod) -> Result<Vec<BlockFeeRatesEntry>> {
|
||||
let computer = self.computer();
|
||||
let current_height = self.height();
|
||||
let start = current_height
|
||||
.to_usize()
|
||||
.saturating_sub(time_period.block_count());
|
||||
pub fn block_fee_rates(&self, _time_period: TimePeriod) -> Result<Vec<BlockFeeRatesEntry>> {
|
||||
// Disabled until percentile data is available at dateindex level
|
||||
Ok(Vec::new())
|
||||
|
||||
let iter = DateIndexIter::new(computer, start, current_height.to_usize());
|
||||
|
||||
let vecs = &computer.chain.indexes_to_fee_rate.dateindex;
|
||||
let mut min = vecs.unwrap_min().iter();
|
||||
let mut pct10 = vecs.unwrap_pct10().iter();
|
||||
let mut pct25 = vecs.unwrap_pct25().iter();
|
||||
let mut median = vecs.unwrap_median().iter();
|
||||
let mut pct75 = vecs.unwrap_pct75().iter();
|
||||
let mut pct90 = vecs.unwrap_pct90().iter();
|
||||
let mut max = vecs.unwrap_max().iter();
|
||||
|
||||
Ok(iter.collect(|di, ts, h| {
|
||||
Some(BlockFeeRatesEntry {
|
||||
avg_height: h,
|
||||
timestamp: ts,
|
||||
percentiles: FeeRatePercentiles::new(
|
||||
min.get(di).unwrap_or_default(),
|
||||
pct10.get(di).unwrap_or_default(),
|
||||
pct25.get(di).unwrap_or_default(),
|
||||
median.get(di).unwrap_or_default(),
|
||||
pct75.get(di).unwrap_or_default(),
|
||||
pct90.get(di).unwrap_or_default(),
|
||||
max.get(di).unwrap_or_default(),
|
||||
),
|
||||
})
|
||||
}))
|
||||
// Original implementation:
|
||||
// let computer = self.computer();
|
||||
// let current_height = self.height();
|
||||
// let start = current_height
|
||||
// .to_usize()
|
||||
// .saturating_sub(time_period.block_count());
|
||||
//
|
||||
// let iter = DateIndexIter::new(computer, start, current_height.to_usize());
|
||||
//
|
||||
// let vecs = &computer.chain.indexes_to_fee_rate.dateindex;
|
||||
// let mut min = vecs.unwrap_min().iter();
|
||||
// let mut pct10 = vecs.unwrap_pct10().iter();
|
||||
// let mut pct25 = vecs.unwrap_pct25().iter();
|
||||
// let mut median = vecs.unwrap_median().iter();
|
||||
// let mut pct75 = vecs.unwrap_pct75().iter();
|
||||
// let mut pct90 = vecs.unwrap_pct90().iter();
|
||||
// let mut max = vecs.unwrap_max().iter();
|
||||
//
|
||||
// Ok(iter.collect(|di, ts, h| {
|
||||
// Some(BlockFeeRatesEntry {
|
||||
// avg_height: h,
|
||||
// timestamp: ts,
|
||||
// percentiles: FeeRatePercentiles::new(
|
||||
// min.get(di).unwrap_or_default(),
|
||||
// pct10.get(di).unwrap_or_default(),
|
||||
// pct25.get(di).unwrap_or_default(),
|
||||
// median.get(di).unwrap_or_default(),
|
||||
// pct75.get(di).unwrap_or_default(),
|
||||
// pct90.get(di).unwrap_or_default(),
|
||||
// max.get(di).unwrap_or_default(),
|
||||
// ),
|
||||
// })
|
||||
// }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,9 @@ impl<'a> DateIndexIter<'a> {
|
||||
.computer
|
||||
.chain
|
||||
.timeindexes_to_timestamp
|
||||
.dateindex_extra
|
||||
.unwrap_first()
|
||||
.dateindex
|
||||
.as_ref()
|
||||
.expect("timeindexes_to_timestamp.dateindex should exist")
|
||||
.iter();
|
||||
let mut heights = self.computer.indexes.dateindex_to_first_height.iter();
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ impl Query {
|
||||
.indexes
|
||||
.height_to_dateindex
|
||||
.read_once(current_height)?;
|
||||
|
||||
let current_hashrate = *computer
|
||||
.chain
|
||||
.indexes_to_hash_rate
|
||||
@@ -58,11 +59,13 @@ impl Query {
|
||||
.dateindex
|
||||
.unwrap_last()
|
||||
.iter();
|
||||
|
||||
let mut timestamp_iter = computer
|
||||
.chain
|
||||
.timeindexes_to_timestamp
|
||||
.dateindex_extra
|
||||
.unwrap_first()
|
||||
.dateindex
|
||||
.as_ref()
|
||||
.expect("timeindexes_to_timestamp.dateindex should exist")
|
||||
.iter();
|
||||
|
||||
let mut hashrates = Vec::with_capacity(total_days / step + 1);
|
||||
|
||||
Reference in New Issue
Block a user