mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-17 13:24:49 -07:00
global: snapshot
This commit is contained in:
257
crates/brk_query/src/impl/metrics.rs
Normal file
257
crates/brk_query/src/impl/metrics.rs
Normal file
@@ -0,0 +1,257 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use brk_error::{Error, Result};
|
||||
use brk_traversable::TreeNode;
|
||||
use brk_types::{
|
||||
Format, Index, IndexInfo, Limit, Metric, MetricCount, MetricData, PaginatedMetrics, Pagination,
|
||||
PaginationIndex,
|
||||
};
|
||||
use vecdb::AnyExportableVec;
|
||||
|
||||
use crate::vecs::{IndexToVec, MetricToVec};
|
||||
use crate::{DataRangeFormat, MetricSelection, Output, Query};
|
||||
|
||||
/// Estimated bytes per column header
|
||||
const CSV_HEADER_BYTES_PER_COL: usize = 10;
|
||||
/// Estimated bytes per cell value
|
||||
const CSV_CELL_BYTES: usize = 15;
|
||||
|
||||
impl Query {
|
||||
pub fn match_metric(&self, metric: &Metric, limit: Limit) -> Vec<&'static str> {
|
||||
self.vecs().matches(metric, limit)
|
||||
}
|
||||
|
||||
pub fn metric_not_found_error(&self, metric: &Metric) -> Error {
|
||||
if let Some(first) = self.match_metric(metric, Limit::MIN).first() {
|
||||
Error::String(format!("Could not find '{metric}', did you mean '{first}'?"))
|
||||
} else {
|
||||
Error::String(format!("Could not find '{metric}'."))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn columns_to_csv(
|
||||
columns: &[&dyn AnyExportableVec],
|
||||
from: Option<i64>,
|
||||
to: Option<i64>,
|
||||
) -> Result<String> {
|
||||
if columns.is_empty() {
|
||||
return Ok(String::new());
|
||||
}
|
||||
|
||||
let num_rows = columns[0].range_count(from, to);
|
||||
let num_cols = columns.len();
|
||||
|
||||
let estimated_size =
|
||||
num_cols * CSV_HEADER_BYTES_PER_COL + num_rows * num_cols * CSV_CELL_BYTES;
|
||||
let mut csv = String::with_capacity(estimated_size);
|
||||
|
||||
for (i, col) in columns.iter().enumerate() {
|
||||
if i > 0 {
|
||||
csv.push(',');
|
||||
}
|
||||
csv.push_str(col.name());
|
||||
}
|
||||
csv.push('\n');
|
||||
|
||||
let mut writers: Vec<_> = columns
|
||||
.iter()
|
||||
.map(|col| col.create_writer(from, to))
|
||||
.collect();
|
||||
|
||||
for _ in 0..num_rows {
|
||||
for (i, writer) in writers.iter_mut().enumerate() {
|
||||
if i > 0 {
|
||||
csv.push(',');
|
||||
}
|
||||
writer.write_next(&mut csv)?;
|
||||
}
|
||||
csv.push('\n');
|
||||
}
|
||||
|
||||
Ok(csv)
|
||||
}
|
||||
|
||||
/// Format single metric - returns `MetricData`
|
||||
pub fn format(
|
||||
&self,
|
||||
metric: &dyn AnyExportableVec,
|
||||
params: &DataRangeFormat,
|
||||
) -> Result<Output> {
|
||||
let from = params.from().map(|from| metric.i64_to_usize(from));
|
||||
let to = params.to().map(|to| metric.i64_to_usize(to));
|
||||
|
||||
Ok(match params.format() {
|
||||
Format::CSV => Output::CSV(Self::columns_to_csv(
|
||||
&[metric],
|
||||
from.map(|v| v as i64),
|
||||
to.map(|v| v as i64),
|
||||
)?),
|
||||
Format::JSON => {
|
||||
let mut buf = Vec::new();
|
||||
MetricData::serialize(metric, from, to, &mut buf)?;
|
||||
Output::Json(buf)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Format multiple metrics - returns `Vec<MetricData>`
|
||||
pub fn format_bulk(
|
||||
&self,
|
||||
metrics: &[&dyn AnyExportableVec],
|
||||
params: &DataRangeFormat,
|
||||
) -> Result<Output> {
|
||||
let from = params.from().map(|from| {
|
||||
metrics
|
||||
.iter()
|
||||
.map(|v| v.i64_to_usize(from))
|
||||
.min()
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
let to = params.to().map(|to| {
|
||||
metrics
|
||||
.iter()
|
||||
.map(|v| v.i64_to_usize(to))
|
||||
.min()
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
let format = params.format();
|
||||
|
||||
Ok(match format {
|
||||
Format::CSV => Output::CSV(Self::columns_to_csv(
|
||||
metrics,
|
||||
from.map(|v| v as i64),
|
||||
to.map(|v| v as i64),
|
||||
)?),
|
||||
Format::JSON => {
|
||||
if metrics.is_empty() {
|
||||
return Ok(Output::default(format));
|
||||
}
|
||||
|
||||
let mut buf = Vec::new();
|
||||
buf.push(b'[');
|
||||
for (i, vec) in metrics.iter().enumerate() {
|
||||
if i > 0 {
|
||||
buf.push(b',');
|
||||
}
|
||||
MetricData::serialize(*vec, from, to, &mut buf)?;
|
||||
}
|
||||
buf.push(b']');
|
||||
Output::Json(buf)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// Calculate total weight of the vecs for the given range
|
||||
pub fn weight(vecs: &[&dyn AnyExportableVec], from: Option<i64>, to: Option<i64>) -> usize {
|
||||
vecs.iter().map(|v| v.range_weight(from, to)).sum()
|
||||
}
|
||||
|
||||
/// Search and format single metric
|
||||
pub fn search_and_format(&self, params: MetricSelection) -> Result<Output> {
|
||||
self.search_and_format_checked(params, usize::MAX)
|
||||
}
|
||||
|
||||
/// Search and format single metric with weight limit
|
||||
pub fn search_and_format_checked(
|
||||
&self,
|
||||
params: MetricSelection,
|
||||
max_weight: usize,
|
||||
) -> Result<Output> {
|
||||
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 weight = Self::weight(&vecs, params.from(), params.to());
|
||||
if weight > max_weight {
|
||||
return Err(Error::String(format!(
|
||||
"Request too heavy: {weight} bytes exceeds limit of {max_weight} bytes"
|
||||
)));
|
||||
}
|
||||
|
||||
self.format(*metric, ¶ms.range)
|
||||
}
|
||||
|
||||
/// Search and format bulk metrics
|
||||
pub fn search_and_format_bulk(&self, params: MetricSelection) -> Result<Output> {
|
||||
self.search_and_format_bulk_checked(params, usize::MAX)
|
||||
}
|
||||
|
||||
/// Search and format bulk metrics with weight limit (for DDoS prevention)
|
||||
pub fn search_and_format_bulk_checked(
|
||||
&self,
|
||||
params: MetricSelection,
|
||||
max_weight: usize,
|
||||
) -> Result<Output> {
|
||||
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());
|
||||
if weight > max_weight {
|
||||
return Err(Error::String(format!(
|
||||
"Request too heavy: {weight} bytes exceeds limit of {max_weight} bytes"
|
||||
)));
|
||||
}
|
||||
|
||||
self.format_bulk(&vecs, ¶ms.range)
|
||||
}
|
||||
|
||||
pub fn metric_to_index_to_vec(&self) -> &BTreeMap<&str, IndexToVec<'_>> {
|
||||
&self.vecs().metric_to_index_to_vec
|
||||
}
|
||||
|
||||
pub fn index_to_metric_to_vec(&self) -> &BTreeMap<Index, MetricToVec<'_>> {
|
||||
&self.vecs().index_to_metric_to_vec
|
||||
}
|
||||
|
||||
pub fn metric_count(&self) -> MetricCount {
|
||||
MetricCount {
|
||||
distinct_metrics: self.distinct_metric_count(),
|
||||
total_endpoints: self.total_metric_count(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn distinct_metric_count(&self) -> usize {
|
||||
self.vecs().distinct_metric_count
|
||||
}
|
||||
|
||||
pub fn total_metric_count(&self) -> usize {
|
||||
self.vecs().total_metric_count
|
||||
}
|
||||
|
||||
pub fn indexes(&self) -> &[IndexInfo] {
|
||||
&self.vecs().indexes
|
||||
}
|
||||
|
||||
pub fn metrics(&self, pagination: Pagination) -> PaginatedMetrics {
|
||||
self.vecs().metrics(pagination)
|
||||
}
|
||||
|
||||
pub fn metrics_catalog(&self) -> &TreeNode {
|
||||
self.vecs().catalog()
|
||||
}
|
||||
|
||||
pub fn index_to_vecids(&self, paginated_index: PaginationIndex) -> Option<&[&str]> {
|
||||
self.vecs().index_to_ids(paginated_index)
|
||||
}
|
||||
|
||||
pub fn metric_to_indexes(&self, metric: Metric) -> Option<&Vec<Index>> {
|
||||
self.vecs().metric_to_indexes(metric)
|
||||
}
|
||||
}
|
||||
75
crates/brk_query/src/impl/metrics_legacy.rs
Normal file
75
crates/brk_query/src/impl/metrics_legacy.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
//! Deprecated metrics formatting without MetricData wrapper.
|
||||
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::Format;
|
||||
use vecdb::AnyExportableVec;
|
||||
|
||||
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 from = params
|
||||
.from()
|
||||
.map(|from| metrics.iter().map(|v| v.i64_to_usize(from)).min().unwrap_or_default());
|
||||
|
||||
let to = params
|
||||
.to()
|
||||
.map(|to| metrics.iter().map(|v| v.i64_to_usize(to)).min().unwrap_or_default());
|
||||
|
||||
let format = params.format();
|
||||
|
||||
Ok(match format {
|
||||
Format::CSV => OutputLegacy::CSV(Self::columns_to_csv(metrics, from.map(|v| v as i64), to.map(|v| v as i64))?),
|
||||
Format::JSON => {
|
||||
if metrics.is_empty() {
|
||||
return Ok(OutputLegacy::default(format));
|
||||
}
|
||||
|
||||
if metrics.len() == 1 {
|
||||
let metric = metrics[0];
|
||||
let count = metric.range_count(from.map(|v| v as i64), to.map(|v| v as i64));
|
||||
let mut buf = Vec::new();
|
||||
if count == 1 {
|
||||
metric.write_json_value(from, &mut buf)?;
|
||||
OutputLegacy::Json(LegacyValue::Value(buf))
|
||||
} else {
|
||||
metric.write_json(from, to, &mut buf)?;
|
||||
OutputLegacy::Json(LegacyValue::List(buf))
|
||||
}
|
||||
} else {
|
||||
let mut values = Vec::with_capacity(metrics.len());
|
||||
for vec in metrics {
|
||||
let mut buf = Vec::new();
|
||||
vec.write_json(from, to, &mut buf)?;
|
||||
values.push(buf);
|
||||
}
|
||||
OutputLegacy::Json(LegacyValue::Matrix(values))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Deprecated - use search_and_format instead
|
||||
pub fn search_and_format_legacy(&self, params: MetricSelection) -> Result<OutputLegacy> {
|
||||
self.search_and_format_legacy_checked(params, usize::MAX)
|
||||
}
|
||||
|
||||
/// 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);
|
||||
|
||||
if vecs.is_empty() {
|
||||
return Ok(OutputLegacy::default(params.range.format()));
|
||||
}
|
||||
|
||||
let weight = Self::weight(&vecs, params.from(), params.to());
|
||||
if weight > max_weight {
|
||||
return Err(Error::String(format!(
|
||||
"Request too heavy: {weight} bytes exceeds limit of {max_weight} bytes"
|
||||
)));
|
||||
}
|
||||
|
||||
self.format_legacy(&vecs, ¶ms.range)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
mod address;
|
||||
mod block;
|
||||
mod mempool;
|
||||
mod metrics;
|
||||
mod metrics_legacy;
|
||||
mod mining;
|
||||
mod transaction;
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![allow(clippy::module_inception)]
|
||||
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use brk_computer::Computer;
|
||||
use brk_error::{Error, Result};
|
||||
use brk_indexer::Indexer;
|
||||
use brk_mempool::Mempool;
|
||||
use brk_reader::Reader;
|
||||
use brk_traversable::TreeNode;
|
||||
use brk_types::{Format, Height, Index, IndexInfo, Limit, Metric, MetricCount};
|
||||
use vecdb::{AnyExportableVec, AnyStoredVec};
|
||||
use brk_types::Height;
|
||||
use vecdb::AnyStoredVec;
|
||||
|
||||
// Infrastructure modules
|
||||
#[cfg(feature = "tokio")]
|
||||
@@ -29,9 +27,8 @@ pub use brk_types::{
|
||||
Pagination, PaginationIndex,
|
||||
};
|
||||
pub use r#impl::BLOCK_TXS_PAGE_SIZE;
|
||||
pub use output::{Output, Value};
|
||||
pub use output::{LegacyValue, Output, OutputLegacy};
|
||||
|
||||
use crate::vecs::{IndexToVec, MetricToVec};
|
||||
use vecs::Vecs;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -70,194 +67,6 @@ impl Query {
|
||||
Height::from(self.indexer().vecs.block.height_to_blockhash.stamp())
|
||||
}
|
||||
|
||||
// === Metrics methods ===
|
||||
|
||||
pub fn match_metric(&self, metric: &Metric, limit: Limit) -> Vec<&'static str> {
|
||||
self.vecs().matches(metric, limit)
|
||||
}
|
||||
|
||||
fn columns_to_csv(
|
||||
columns: &[&&dyn AnyExportableVec],
|
||||
from: Option<i64>,
|
||||
to: Option<i64>,
|
||||
) -> Result<String> {
|
||||
if columns.is_empty() {
|
||||
return Ok(String::new());
|
||||
}
|
||||
|
||||
let num_rows = columns[0].range_count(from, to);
|
||||
let num_cols = columns.len();
|
||||
|
||||
let estimated_size = num_cols * 10 + num_rows * num_cols * 15;
|
||||
let mut csv = String::with_capacity(estimated_size);
|
||||
|
||||
// Write headers from column names
|
||||
for (idx, col) in columns.iter().enumerate() {
|
||||
if idx > 0 {
|
||||
csv.push(',');
|
||||
}
|
||||
csv.push_str(col.name());
|
||||
}
|
||||
csv.push('\n');
|
||||
|
||||
// Create one writer per column
|
||||
let mut writers: Vec<_> = columns
|
||||
.iter()
|
||||
.map(|col| col.create_writer(from, to))
|
||||
.collect();
|
||||
|
||||
for _ in 0..num_rows {
|
||||
for (index, writer) in writers.iter_mut().enumerate() {
|
||||
if index > 0 {
|
||||
csv.push(',');
|
||||
}
|
||||
writer.write_next(&mut csv)?;
|
||||
}
|
||||
csv.push('\n');
|
||||
}
|
||||
|
||||
Ok(csv)
|
||||
}
|
||||
|
||||
pub fn format(
|
||||
&self,
|
||||
metrics: Vec<&&dyn AnyExportableVec>,
|
||||
params: &DataRangeFormat,
|
||||
) -> Result<Output> {
|
||||
let from = params.from().map(|from| {
|
||||
metrics
|
||||
.iter()
|
||||
.map(|v| v.i64_to_usize(from))
|
||||
.min()
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
let to = params.to().map(|to| {
|
||||
metrics
|
||||
.iter()
|
||||
.map(|v| v.i64_to_usize(to))
|
||||
.min()
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
let format = params.format();
|
||||
|
||||
Ok(match format {
|
||||
Format::CSV => Output::CSV(Self::columns_to_csv(
|
||||
&metrics,
|
||||
from.map(|v| v as i64),
|
||||
to.map(|v| v as i64),
|
||||
)?),
|
||||
Format::JSON => {
|
||||
let mut values = metrics
|
||||
.iter()
|
||||
.map(|vec| vec.collect_range_json_bytes(from, to).map_err(Error::from))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
if values.is_empty() {
|
||||
return Ok(Output::default(format));
|
||||
}
|
||||
|
||||
if values.len() == 1 {
|
||||
Output::Json(Value::List(values.pop().unwrap()))
|
||||
} else {
|
||||
Output::Json(Value::Matrix(values))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// Calculate total weight of the vecs for the given range
|
||||
pub fn weight(vecs: &[&dyn AnyExportableVec], from: Option<i64>, to: Option<i64>) -> usize {
|
||||
vecs.iter().map(|v| v.range_weight(from, to)).sum()
|
||||
}
|
||||
|
||||
pub fn search_and_format(&self, params: MetricSelection) -> Result<Output> {
|
||||
let vecs = self.search(¶ms);
|
||||
|
||||
if vecs.is_empty() {
|
||||
return Ok(Output::default(params.range.format()));
|
||||
}
|
||||
|
||||
self.format(vecs.iter().collect(), ¶ms.range)
|
||||
}
|
||||
|
||||
/// Search and format with weight limit (for DDoS prevention)
|
||||
pub fn search_and_format_checked(
|
||||
&self,
|
||||
params: MetricSelection,
|
||||
max_weight: usize,
|
||||
) -> Result<Output> {
|
||||
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());
|
||||
if weight > max_weight {
|
||||
return Err(Error::String(format!(
|
||||
"Request too heavy: {weight} bytes exceeds limit of {max_weight} bytes"
|
||||
)));
|
||||
}
|
||||
|
||||
self.format(vecs.iter().collect(), ¶ms.range)
|
||||
}
|
||||
|
||||
pub fn metric_to_index_to_vec(&self) -> &BTreeMap<&str, IndexToVec<'_>> {
|
||||
&self.vecs().metric_to_index_to_vec
|
||||
}
|
||||
|
||||
pub fn index_to_metric_to_vec(&self) -> &BTreeMap<Index, MetricToVec<'_>> {
|
||||
&self.vecs().index_to_metric_to_vec
|
||||
}
|
||||
|
||||
pub fn metric_count(&self) -> MetricCount {
|
||||
MetricCount {
|
||||
distinct_metrics: self.distinct_metric_count(),
|
||||
total_endpoints: self.total_metric_count(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn distinct_metric_count(&self) -> usize {
|
||||
self.vecs().distinct_metric_count
|
||||
}
|
||||
|
||||
pub fn total_metric_count(&self) -> usize {
|
||||
self.vecs().total_metric_count
|
||||
}
|
||||
|
||||
pub fn get_indexes(&self) -> &[IndexInfo] {
|
||||
&self.vecs().indexes
|
||||
}
|
||||
|
||||
pub fn get_metrics(&self, pagination: Pagination) -> PaginatedMetrics {
|
||||
self.vecs().metrics(pagination)
|
||||
}
|
||||
|
||||
pub fn get_metrics_catalog(&self) -> &TreeNode {
|
||||
self.vecs().catalog()
|
||||
}
|
||||
|
||||
pub fn get_index_to_vecids(&self, paginated_index: PaginationIndex) -> Option<&[&str]> {
|
||||
self.vecs().index_to_ids(paginated_index)
|
||||
}
|
||||
|
||||
pub fn metric_to_indexes(&self, metric: Metric) -> Option<&Vec<Index>> {
|
||||
self.vecs().metric_to_indexes(metric)
|
||||
}
|
||||
|
||||
// === Core accessors ===
|
||||
|
||||
#[inline]
|
||||
pub fn reader(&self) -> &Reader {
|
||||
&self.0.reader
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use brk_types::Format;
|
||||
|
||||
/// New format with MetricData metadata wrapper
|
||||
#[derive(Debug)]
|
||||
pub enum Output {
|
||||
Json(Value),
|
||||
Json(Vec<u8>),
|
||||
CSV(String),
|
||||
}
|
||||
|
||||
@@ -11,44 +12,67 @@ impl Output {
|
||||
pub fn to_string(self) -> String {
|
||||
match self {
|
||||
Output::CSV(s) => s,
|
||||
Output::Json(v) => unsafe { String::from_utf8_unchecked(v.to_vec()) },
|
||||
Output::Json(v) => unsafe { String::from_utf8_unchecked(v) },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default(format: Format) -> Self {
|
||||
match format {
|
||||
Format::CSV => Output::CSV(String::new()),
|
||||
Format::JSON => Output::Json(br#"{"len":0,"from":0,"to":0,"data":[]}"#.to_vec()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deprecated: Raw JSON without metadata wrapper
|
||||
#[derive(Debug)]
|
||||
pub enum Value {
|
||||
Matrix(Vec<Vec<u8>>),
|
||||
List(Vec<u8>),
|
||||
pub enum OutputLegacy {
|
||||
Json(LegacyValue),
|
||||
CSV(String),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
impl OutputLegacy {
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(self) -> String {
|
||||
match self {
|
||||
OutputLegacy::CSV(s) => s,
|
||||
OutputLegacy::Json(v) => unsafe { String::from_utf8_unchecked(v.to_vec()) },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default(format: Format) -> Self {
|
||||
match format {
|
||||
Format::CSV => OutputLegacy::CSV(String::new()),
|
||||
Format::JSON => OutputLegacy::Json(LegacyValue::List(b"[]".to_vec())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deprecated: Raw JSON without metadata wrapper.
|
||||
#[derive(Debug)]
|
||||
pub enum LegacyValue {
|
||||
Matrix(Vec<Vec<u8>>),
|
||||
List(Vec<u8>),
|
||||
Value(Vec<u8>),
|
||||
}
|
||||
|
||||
impl LegacyValue {
|
||||
pub fn to_vec(self) -> Vec<u8> {
|
||||
match self {
|
||||
Value::List(v) => v,
|
||||
Self::Matrix(m) => {
|
||||
let total_size = m.iter().map(|v| v.len()).sum::<usize>() + m.len() - 1 + 2;
|
||||
let mut matrix = Vec::with_capacity(total_size);
|
||||
matrix.push(b'[');
|
||||
|
||||
LegacyValue::Value(v) | LegacyValue::List(v) => v,
|
||||
LegacyValue::Matrix(m) => {
|
||||
let total_size = m.iter().map(|v| v.len()).sum::<usize>() + m.len() + 1;
|
||||
let mut buf = Vec::with_capacity(total_size);
|
||||
buf.push(b'[');
|
||||
for (i, vec) in m.into_iter().enumerate() {
|
||||
if i > 0 {
|
||||
matrix.push(b',');
|
||||
buf.push(b',');
|
||||
}
|
||||
matrix.extend(vec);
|
||||
buf.extend(vec);
|
||||
}
|
||||
matrix.push(b']');
|
||||
matrix
|
||||
buf.push(b']');
|
||||
buf
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn default(format: Format) -> Self {
|
||||
match format {
|
||||
Format::CSV => Output::CSV("".to_string()),
|
||||
Format::JSON => Output::Json(Value::List(b"[]".to_vec())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user