1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::client::Client;
5use crate::error::Result;
6
7pub const TIMESERIES_PATH: &str = "api/v2/query/timeseries";
11
12#[derive(Debug, Clone, Serialize)]
15pub struct TimeseriesRequest {
16 pub data: RequestData,
17}
18
19#[derive(Debug, Clone, Serialize)]
20pub struct RequestData {
21 pub r#type: String,
22 pub attributes: RequestAttributes,
23}
24
25#[derive(Debug, Clone, Serialize)]
26pub struct RequestAttributes {
27 pub from: i64,
28 pub to: i64,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub interval: Option<i64>,
31 pub queries: Vec<MetricQuery>,
32 pub formulas: Vec<Formula>,
33}
34
35#[derive(Debug, Clone, Serialize)]
36pub struct MetricQuery {
37 pub data_source: String,
38 pub query: String,
39 pub name: String,
40}
41
42#[derive(Debug, Clone, Serialize)]
43pub struct Formula {
44 pub formula: String,
45}
46
47#[derive(Debug, Clone, Default, Deserialize, Serialize)]
50pub struct TimeseriesResponse {
51 #[serde(default)]
52 pub data: Option<ResponseData>,
53}
54
55#[derive(Debug, Clone, Default, Deserialize, Serialize)]
56pub struct ResponseData {
57 #[serde(default)]
58 pub attributes: ResponseAttributes,
59}
60
61#[derive(Debug, Clone, Default, Deserialize, Serialize)]
62pub struct ResponseAttributes {
63 #[serde(default)]
65 pub series: Vec<SeriesMeta>,
66 #[serde(default)]
68 pub times: Vec<i64>,
69 #[serde(default)]
71 pub values: Vec<Vec<Option<f64>>>,
72}
73
74#[derive(Debug, Clone, Default, Deserialize, Serialize)]
75pub struct SeriesMeta {
76 #[serde(default)]
78 pub group_tags: Vec<String>,
79 #[serde(default)]
80 pub query_index: Option<i64>,
81 #[serde(default)]
82 pub unit: Value,
83}
84
85impl Client {
86 pub async fn metrics_timeseries(
92 &self,
93 from_ms: i64,
94 to_ms: i64,
95 interval_ms: Option<i64>,
96 query: &str,
97 ) -> Result<TimeseriesResponse> {
98 let req = TimeseriesRequest {
99 data: RequestData {
100 r#type: "timeseries_request".into(),
101 attributes: RequestAttributes {
102 from: from_ms,
103 to: to_ms,
104 interval: interval_ms,
105 queries: vec![MetricQuery {
106 data_source: "metrics".into(),
107 query: query.into(),
108 name: "q1".into(),
109 }],
110 formulas: vec![Formula {
111 formula: "q1".into(),
112 }],
113 },
114 },
115 };
116 self.post_json(TIMESERIES_PATH, &req).await
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn request_serializes_to_v2_shape() {
126 let req = TimeseriesRequest {
127 data: RequestData {
128 r#type: "timeseries_request".into(),
129 attributes: RequestAttributes {
130 from: 1_747_000_000_000,
131 to: 1_747_864_000_000,
132 interval: Some(86_400_000),
133 queries: vec![MetricQuery {
134 data_source: "metrics".into(),
135 query: "sum:bridgeft.import.records{*} by {feed}".into(),
136 name: "q1".into(),
137 }],
138 formulas: vec![Formula {
139 formula: "q1".into(),
140 }],
141 },
142 },
143 };
144 let v: Value = serde_json::to_value(&req).unwrap();
145 assert_eq!(v["data"]["type"], "timeseries_request");
146 assert_eq!(v["data"]["attributes"]["from"], 1_747_000_000_000i64);
147 assert_eq!(v["data"]["attributes"]["queries"][0]["data_source"], "metrics");
148 assert_eq!(v["data"]["attributes"]["formulas"][0]["formula"], "q1");
149 }
150
151 #[test]
152 fn parses_timeseries_response() {
153 let raw = r#"{
154 "data": {
155 "type": "timeseries_response",
156 "attributes": {
157 "series": [
158 {"group_tags": ["feed:positions"], "query_index": 0},
159 {"group_tags": ["feed:account_balances"], "query_index": 0}
160 ],
161 "times": [1747000000000, 1747086400000, 1747172800000],
162 "values": [
163 [2901, 2950, null],
164 [1483, 1490, 0]
165 ]
166 }
167 }
168 }"#;
169 let resp: TimeseriesResponse = serde_json::from_str(raw).unwrap();
170 let attrs = resp.data.unwrap().attributes;
171 assert_eq!(attrs.series.len(), 2);
172 assert_eq!(attrs.series[0].group_tags, vec!["feed:positions"]);
173 assert_eq!(attrs.times.len(), 3);
174 assert_eq!(attrs.values[0][0], Some(2901.0));
175 assert_eq!(attrs.values[0][2], None); assert_eq!(attrs.values[1][2], Some(0.0)); }
178}