server: openapi fixes

This commit is contained in:
nym21
2025-12-16 20:23:01 +01:00
parent f7f065c6e0
commit 2ccf0ef856
23 changed files with 350 additions and 188 deletions

View File

@@ -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(&params);
let vecs = self.search(&params)?;
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(&params);
let vecs = self.search(&params)?;
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"

View File

@@ -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(&params);
let vecs = self.search(&params)?;
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"

View File

@@ -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(),
// ),
// })
// }))
}
}

View File

@@ -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();

View File

@@ -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);