#[cfg(feature = "node")]
use napi_derive::napi;
#[cfg(feature = "python")]
use pyo3::pyclass;
#[cfg(feature = "python")]
use pyo3_stub_gen::derive::gen_stub_pyclass;
use serde::{Deserialize, Deserializer, Serialize};
fn tag_as_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
match serde_json::Value::deserialize(deserializer)? {
serde_json::Value::String(s) => Ok(vec![s]),
serde_json::Value::Array(items) => items
.into_iter()
.map(|v| match v {
serde_json::Value::String(s) => Ok(s),
other => Err(D::Error::custom(format!(
"expected string in tag array, got {other}"
))),
})
.collect(),
serde_json::Value::Null => Ok(Vec::new()),
other => Err(D::Error::custom(format!(
"expected string or array of strings for tag, got {other}"
))),
}
}
#[cfg_attr(feature = "python", gen_stub_pyclass)]
#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
#[cfg_attr(feature = "node", napi(object))]
#[derive(Debug, Clone, Default, Serialize)]
pub struct GetEndpointMetricsRequest {
pub period: String,
pub metric: String,
}
#[cfg_attr(feature = "python", gen_stub_pyclass)]
#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
#[cfg_attr(feature = "node", napi(object))]
#[derive(Debug, Clone, Default, Serialize)]
pub struct GetAccountMetricsRequest {
pub period: String,
pub metric: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub percentile: Option<String>,
}
#[cfg_attr(feature = "python", gen_stub_pyclass)]
#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
#[cfg_attr(feature = "node", napi(object))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EndpointMetric {
pub data: Vec<Vec<i64>>,
#[serde(deserialize_with = "tag_as_vec")]
pub tag: Vec<String>,
}
#[cfg_attr(feature = "python", gen_stub_pyclass)]
#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
#[cfg_attr(feature = "node", napi(object))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetEndpointMetricsResponse {
#[serde(default)]
pub data: Vec<EndpointMetric>,
pub error: Option<String>,
}
#[cfg_attr(feature = "python", gen_stub_pyclass)]
#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
#[cfg_attr(feature = "node", napi(object))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetAccountMetricsResponse {
#[serde(default)]
pub data: Vec<EndpointMetric>,
pub error: Option<String>,
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::EndpointMetric;
#[test]
fn tag_deserializes_from_string() {
let m: EndpointMetric =
serde_json::from_str(r#"{"data": [[1, 2]], "tag": "total"}"#).unwrap();
assert_eq!(m.tag, vec!["total".to_string()]);
}
#[test]
fn tag_deserializes_from_tuple() {
let m: EndpointMetric =
serde_json::from_str(r#"{"data": [[1, 2]], "tag": ["network", "arbitrum-mainnet"]}"#)
.unwrap();
assert_eq!(
m.tag,
vec!["network".to_string(), "arbitrum-mainnet".to_string()]
);
}
#[test]
fn tag_deserializes_from_null() {
let m: EndpointMetric = serde_json::from_str(r#"{"data": [[1, 2]], "tag": null}"#).unwrap();
assert!(m.tag.is_empty());
}
#[test]
fn tag_rejects_mixed_array() {
let err =
serde_json::from_str::<EndpointMetric>(r#"{"data": [], "tag": ["x", 5]}"#).unwrap_err();
assert!(err.to_string().contains("expected string in tag array"));
}
#[test]
fn tag_rejects_object() {
let err = serde_json::from_str::<EndpointMetric>(r#"{"data": [], "tag": {"k": "v"}}"#)
.unwrap_err();
assert!(err
.to_string()
.contains("expected string or array of strings for tag"));
}
}