quicknode_sdk/admin/
endpoint_metrics.rs1#[cfg(feature = "node")]
2use napi_derive::napi;
3#[cfg(feature = "python")]
4use pyo3::pyclass;
5#[cfg(feature = "python")]
6use pyo3_stub_gen::derive::gen_stub_pyclass;
7use serde::{Deserialize, Deserializer, Serialize};
8
9fn tag_as_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
14where
15 D: Deserializer<'de>,
16{
17 use serde::de::Error;
18 match serde_json::Value::deserialize(deserializer)? {
19 serde_json::Value::String(s) => Ok(vec![s]),
20 serde_json::Value::Array(items) => items
21 .into_iter()
22 .map(|v| match v {
23 serde_json::Value::String(s) => Ok(s),
24 other => Err(D::Error::custom(format!(
25 "expected string in tag array, got {other}"
26 ))),
27 })
28 .collect(),
29 serde_json::Value::Null => Ok(Vec::new()),
30 other => Err(D::Error::custom(format!(
31 "expected string or array of strings for tag, got {other}"
32 ))),
33 }
34}
35
36#[cfg_attr(feature = "python", gen_stub_pyclass)]
38#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
39#[cfg_attr(feature = "node", napi(object))]
40#[derive(Debug, Clone, Default, Serialize)]
41pub struct GetEndpointMetricsRequest {
42 pub period: String,
44 pub metric: String,
46}
47
48#[cfg_attr(feature = "python", gen_stub_pyclass)]
50#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
51#[cfg_attr(feature = "node", napi(object))]
52#[derive(Debug, Clone, Default, Serialize)]
53pub struct GetAccountMetricsRequest {
54 pub period: String,
56 pub metric: String,
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub percentile: Option<String>,
61}
62
63#[cfg_attr(feature = "python", gen_stub_pyclass)]
65#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
66#[cfg_attr(feature = "node", napi(object))]
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct EndpointMetric {
69 pub data: Vec<Vec<i64>>,
71 #[serde(deserialize_with = "tag_as_vec")]
75 pub tag: Vec<String>,
76}
77
78#[cfg_attr(feature = "python", gen_stub_pyclass)]
80#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
81#[cfg_attr(feature = "node", napi(object))]
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct GetEndpointMetricsResponse {
84 #[serde(default)]
86 pub data: Vec<EndpointMetric>,
87 pub error: Option<String>,
89}
90
91#[cfg_attr(feature = "python", gen_stub_pyclass)]
93#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
94#[cfg_attr(feature = "node", napi(object))]
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct GetAccountMetricsResponse {
97 #[serde(default)]
99 pub data: Vec<EndpointMetric>,
100 pub error: Option<String>,
102}
103
104#[cfg(test)]
105#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
106mod tests {
107 use super::EndpointMetric;
108
109 #[test]
110 fn tag_deserializes_from_string() {
111 let m: EndpointMetric =
112 serde_json::from_str(r#"{"data": [[1, 2]], "tag": "total"}"#).unwrap();
113 assert_eq!(m.tag, vec!["total".to_string()]);
114 }
115
116 #[test]
117 fn tag_deserializes_from_tuple() {
118 let m: EndpointMetric =
119 serde_json::from_str(r#"{"data": [[1, 2]], "tag": ["network", "arbitrum-mainnet"]}"#)
120 .unwrap();
121 assert_eq!(
122 m.tag,
123 vec!["network".to_string(), "arbitrum-mainnet".to_string()]
124 );
125 }
126
127 #[test]
128 fn tag_deserializes_from_null() {
129 let m: EndpointMetric = serde_json::from_str(r#"{"data": [[1, 2]], "tag": null}"#).unwrap();
130 assert!(m.tag.is_empty());
131 }
132
133 #[test]
134 fn tag_rejects_mixed_array() {
135 let err =
136 serde_json::from_str::<EndpointMetric>(r#"{"data": [], "tag": ["x", 5]}"#).unwrap_err();
137 assert!(err.to_string().contains("expected string in tag array"));
138 }
139
140 #[test]
141 fn tag_rejects_object() {
142 let err = serde_json::from_str::<EndpointMetric>(r#"{"data": [], "tag": {"k": "v"}}"#)
143 .unwrap_err();
144 assert!(err
145 .to_string()
146 .contains("expected string or array of strings for tag"));
147 }
148}