global: snapshot

This commit is contained in:
nym21
2025-10-11 18:17:36 +02:00
parent bb46481d7f
commit 5f87594ead
20 changed files with 255 additions and 396 deletions

View File

@@ -1,13 +1,15 @@
use aide::axum::{ApiRouter, routing::get_with};
use axum::{
Json,
extract::{Path, State},
http::StatusCode,
http::HeaderMap,
response::Response,
};
use brk_structs::{AddressInfo, AddressPath};
use crate::extended::{ResponseExtended, ResultExtended, TransformResponseExtended};
use crate::{
VERSION,
extended::{HeaderMapExtended, ResponseExtended, ResultExtended, TransformResponseExtended},
};
use super::AppState;
@@ -19,14 +21,19 @@ impl AddressesRoutes for ApiRouter<AppState> {
fn add_addresses_routes(self) -> Self {
self.api_route(
"/api/chain/address/{address}",
get_with(async |Path(address): Path<AddressPath>,
State(app_state): State<AppState>|
-> Result<Response, (StatusCode, Json<String>)> {
let address_info = app_state.interface.get_address_info(address).to_server_result()?;
let bytes = sonic_rs::to_vec(&address_info).unwrap();
Ok(Response::new_json_from_bytes(bytes))
get_with(async |
headers: HeaderMap,
Path(address): Path<AddressPath>,
State(state): State<AppState>
| {
let etag = format!("{VERSION}-{}", state.get_height());
if headers.has_etag(&etag) {
return Response::new_not_modified();
}
match state.get_address_info(address).with_status() {
Ok(value) => Response::new_json(&value, &etag),
Err((status, message)) => Response::new_json_with(status, &message, &etag)
}
}, |op| op
.tag("Chain")
.summary("Address information")

View File

@@ -1,13 +1,15 @@
use aide::axum::{ApiRouter, routing::get_with};
use axum::{
Json,
extract::{Path, State},
http::StatusCode,
http::HeaderMap,
response::Response,
};
use brk_structs::{TransactionInfo, TxidPath};
use crate::extended::{ResponseExtended, ResultExtended, TransformResponseExtended};
use crate::{
VERSION,
extended::{HeaderMapExtended, ResponseExtended, ResultExtended, TransformResponseExtended},
};
use super::AppState;
@@ -20,14 +22,19 @@ impl TransactionsRoutes for ApiRouter<AppState> {
self.api_route(
"/api/chain/tx/{txid}",
get_with(
async |Path(txid): Path<TxidPath>,
State(app_state): State<AppState>|
-> Result<Response, (StatusCode, Json<String>)> {
let tx_info = app_state.interface.get_transaction_info(txid).to_server_result()?;
let bytes = sonic_rs::to_vec(&tx_info).unwrap();
Ok(Response::new_json_from_bytes(bytes))
async |
headers: HeaderMap,
Path(txid): Path<TxidPath>,
State(state): State<AppState>
| {
let etag = format!("{VERSION}-{}", state.get_height());
if headers.has_etag(&etag) {
return Response::new_not_modified();
}
match state.get_transaction_info(txid).with_status() {
Ok(value) => Response::new_json(&value, &etag),
Err((status, message)) => Response::new_json_with(status, &message, &etag)
}
},
|op| op
.tag("Chain")

View File

@@ -23,9 +23,9 @@ pub async fn handler(
uri: Uri,
headers: HeaderMap,
query: Query<Params>,
State(app_state): State<AppState>,
State(state): State<AppState>,
) -> Response {
match req_to_response_res(uri, headers, query, app_state) {
match req_to_response_res(uri, headers, query, state) {
Ok(response) => response,
Err(error) => {
let mut response =

View File

@@ -1,13 +1,12 @@
use aide::axum::{ApiRouter, routing::get_with};
use axum::{
Json,
extract::{Path, Query, State},
http::{HeaderMap, StatusCode, Uri},
response::{IntoResponse, Redirect, Response},
routing::get,
};
use brk_interface::{PaginatedMetrics, PaginationParam, Params, ParamsDeprec, ParamsOpt};
use brk_structs::{Index, IndexInfo, MetricCount, MetricPath};
use brk_structs::{Index, IndexInfo, MetricCount, MetricPath, MetricSearchQuery};
use brk_traversable::TreeNode;
use crate::{
@@ -23,8 +22,6 @@ pub trait ApiMetricsRoutes {
fn add_metrics_routes(self) -> Self;
}
const TO_SEPARATOR: &str = "_to_";
impl ApiMetricsRoutes for ApiRouter<AppState> {
fn add_metrics_routes(self) -> Self {
self
@@ -32,149 +29,145 @@ impl ApiMetricsRoutes for ApiRouter<AppState> {
.api_route(
"/api/metrics/count",
get_with(
async |State(app_state): State<AppState>| {
Json(app_state.interface.metric_count())
},
|op| {
op.tag("Metrics")
.summary("Metric count")
.description("Current metric count")
.with_ok_response::<Vec<MetricCount>, _>(|res| res)
.with_not_modified()
async |
headers: HeaderMap,
State(state): State<AppState>
| {
let etag = VERSION;
if headers.has_etag(etag) {
return Response::new_not_modified();
}
Response::new_json(state.metric_count(), etag)
},
|op| op.tag("Metrics")
.summary("Metric count")
.description("Current metric count")
.with_ok_response::<Vec<MetricCount>, _>(|res| res)
.with_not_modified(),
),
)
.api_route(
"/api/metrics/indexes",
get_with(
async |State(app_state): State<AppState>| {
Json(app_state.interface.get_indexes())
},
|op| {
op.tag("Metrics")
.summary("List available indexes")
.description(
"Returns all available indexes with their accepted query aliases. Use any alias when querying metrics."
)
.with_ok_response::<Vec<IndexInfo>, _>(|res| res)
.with_not_modified()
async |
headers: HeaderMap,
State(state): State<AppState>
| {
let etag = VERSION;
if headers.has_etag(etag) {
return Response::new_not_modified();
}
Response::new_json(state.get_indexes(), etag)
},
|op| op.tag("Metrics")
.summary("List available indexes")
.description(
"Returns all available indexes with their accepted query aliases. Use any alias when querying metrics."
)
.with_ok_response::<Vec<IndexInfo>, _>(|res| res)
.with_not_modified(),
),
)
.api_route(
"/api/metrics/list",
get_with(
async |State(app_state): State<AppState>,
Query(pagination): Query<PaginationParam>| {
Json(app_state.interface.get_metrics(pagination))
},
|op| {
op.tag("Metrics")
.summary("Metrics list")
.description("Paginated list of available metrics")
.with_ok_response::<PaginatedMetrics, _>(|res| res)
.with_not_modified()
async |
headers: HeaderMap,
State(state): State<AppState>,
Query(pagination): Query<PaginationParam>
| {
let etag = VERSION;
if headers.has_etag(etag) {
return Response::new_not_modified();
}
Response::new_json(state.get_metrics(pagination), etag)
},
|op| op.tag("Metrics")
.summary("Metrics list")
.description("Paginated list of available metrics")
.with_ok_response::<PaginatedMetrics, _>(|res| res)
.with_not_modified(),
),
)
.api_route(
"/api/metrics/catalog",
get_with(
async |headers: HeaderMap, State(app_state): State<AppState>| -> Response {
async |headers: HeaderMap, State(state): State<AppState>| -> Response {
let etag = VERSION;
if headers
.get_if_none_match()
.is_some_and(|prev_etag| etag == prev_etag)
{
if headers.has_etag(etag) {
return Response::new_not_modified();
}
let bytes = sonic_rs::to_vec(&app_state.interface.get_metrics_catalog()).unwrap();
let mut response = Response::new_json_from_bytes(bytes);
let headers = response.headers_mut();
headers.insert_cors();
headers.insert_etag(etag);
response
Response::new_json(state.get_metrics_catalog(), etag)
},
|op| {
op.tag("Metrics")
|op| op.tag("Metrics")
.summary("Metrics catalog")
.description(
"Returns the complete hierarchical catalog of available metrics organized as a tree structure. Metrics are grouped by categories and subcategories. Best viewed in an interactive JSON viewer (e.g., Firefox's built-in JSON viewer) for easy navigation of the nested structure."
)
.with_ok_response::<TreeNode, _>(|res| res)
.with_not_modified()
},
.with_not_modified(),
),
)
.api_route(
"/api/search/{metric}",
"/api/metrics/search",
get_with(
async |
headers: HeaderMap,
State(app_state): State<AppState>,
Path(MetricPath { metric }): Path<MetricPath>
State(state): State<AppState>,
Query(query): Query<MetricSearchQuery>
| {
let etag = VERSION;
if headers
.get_if_none_match()
.is_some_and(|prev_etag| etag == prev_etag)
{
if headers.has_etag(etag) {
return Response::new_not_modified();
}
let bytes = sonic_rs::to_vec(&app_state.interface.search_metric(&metric, usize::MAX)).unwrap();
let mut response = Response::new_json_from_bytes(bytes);
let headers = response.headers_mut();
headers.insert_cors();
headers.insert_etag(etag);
response
},
|op| {
op.tag("Metrics")
.summary("Metric search")
.description(
"Search metrics based on a query"
)
.with_ok_response::<Vec<String>, _>(|res| res)
.with_not_modified()
Response::new_json(state.match_metric(query), etag)
},
|op| op.tag("Metrics")
.summary("Search metrics")
.description("Fuzzy search for metrics by name. Supports partial matches and typos.")
.with_ok_response::<Vec<String>, _>(|res| res)
.with_not_modified(),
),
)
.api_route(
"/api/metrics/{metric}",
get_with(
async |
State(app_state): State<AppState>,
headers: HeaderMap,
State(state): State<AppState>,
Path(MetricPath { metric }): Path<MetricPath>
| {
match app_state.interface.metric_to_indexes(metric) {
Some(indexes) => Json(indexes).into_response(),
None => StatusCode::NOT_FOUND.into_response()
let etag = VERSION;
if headers.has_etag(etag) {
return Response::new_not_modified();
}
if let Some(indexes) = state.metric_to_indexes(metric.clone()) {
return Response::new_json(indexes, etag)
}
let value = if let Some(first) = state.match_metric(MetricSearchQuery {
q: metric.clone(),
limit: 1,
}).first() {
format!("Could not find '{metric}', did you mean '{first}' ?")
} else {
format!("Could not find '{metric}'.")
};
Response::new_json_with(StatusCode::NOT_FOUND, value, etag)
},
|op| {
op.tag("Metrics")
.summary("Get supported indexes for a metric")
.description(
"Returns the list of indexes are supported by the specified metric. \
For example, `realized_price` might be available on dateindex, weekindex, and monthindex."
)
.with_ok_response::<Vec<Index>, _>(|res| res)
.with_not_modified()
.with_not_found()
},
|op| op.tag("Metrics")
.summary("Get supported indexes for a metric")
.description(
"Returns the list of indexes are supported by the specified metric. \
For example, `realized_price` might be available on dateindex, weekindex, and monthindex."
)
.with_ok_response::<Vec<Index>, _>(|res| res)
.with_not_modified()
.with_not_found(),
),
)
// WIP
.route("/api/metrics/bulk", get(data::handler))
// WIP
.route(
"/api/metrics/{metric}/{index}",
get(
@@ -221,8 +214,9 @@ impl ApiMetricsRoutes for ApiRouter<AppState> {
Query(params_opt): Query<ParamsOpt>,
state: State<AppState>|
-> Response {
let separator = "_to_";
let variant = variant.replace("-", "_");
let mut split = variant.split(TO_SEPARATOR);
let mut split = variant.split(separator);
let ser_index = split.next().unwrap();
let Ok(index) = Index::try_from(ser_index) else {
@@ -230,7 +224,7 @@ impl ApiMetricsRoutes for ApiRouter<AppState> {
};
let params = Params::from((
(index, split.collect::<Vec<_>>().join(TO_SEPARATOR)),
(index, split.collect::<Vec<_>>().join(separator)),
params_opt,
));
data::handler(uri, headers, Query(params), state).await

View File

@@ -52,8 +52,8 @@ impl ApiRoutes for ApiRouter<AppState> {
get_with(
async || -> Json<Health> {
Json(Health {
status: "healthy".to_string(),
service: "brk".to_string(),
status: "healthy",
service: "brk",
timestamp: jiff::Timestamp::now().to_string(),
})
},
@@ -73,21 +73,11 @@ impl ApiRoutes for ApiRouter<AppState> {
-> Response {
let etag = VERSION;
if headers
.get_if_none_match()
.is_some_and(|prev_etag| etag == prev_etag)
{
if headers.has_etag(etag) {
return Response::new_not_modified();
}
let mut response =
Response::new_json_from_bytes(sonic_rs::to_vec(&api).unwrap());
let headers = response.headers_mut();
headers.insert_cors();
headers.insert_etag(etag);
response
Response::new_json(&api, etag)
},
),
)