1#![allow(clippy::disallowed_types)]
10
11use rustc_hash::{FxHashMap, FxHashSet};
12use serde::Deserialize;
13
14use crate::error::ClientError;
15use crate::types::{MetricsBucket, MetricsGroup, QueryResponse, ResultType};
16
17#[derive(Debug, Deserialize)]
19pub struct PrometheusResponse {
20 pub status: String,
21 #[serde(default)]
22 pub error: Option<String>,
23 #[serde(default)]
24 pub error_type: Option<String>,
25 pub data: Option<PrometheusData>,
26}
27
28#[derive(Debug, Deserialize)]
30pub struct PrometheusLabelsResponse {
31 pub status: String,
32 #[serde(default)]
33 pub error: Option<String>,
34 #[serde(default)]
35 pub error_type: Option<String>,
36 #[serde(default)]
37 pub data: Vec<String>,
38}
39
40#[derive(Debug, Deserialize)]
42pub struct PrometheusSeriesResponse {
43 pub status: String,
44 #[serde(default)]
45 pub error: Option<String>,
46 #[serde(default)]
47 pub error_type: Option<String>,
48 #[serde(default)]
49 pub data: Vec<std::collections::HashMap<String, String>>,
50}
51
52#[derive(Debug, Deserialize)]
54pub struct PrometheusBuildInfoResponse {
55 pub status: String,
56 #[serde(default)]
57 pub error: Option<String>,
58 #[serde(default)]
59 pub error_type: Option<String>,
60 pub data: Option<PrometheusBuildInfo>,
61}
62
63#[derive(Debug, Clone, Deserialize)]
65pub struct PrometheusBuildInfo {
66 pub version: String,
67 #[serde(default)]
68 pub revision: String,
69 #[serde(default)]
70 pub branch: String,
71 #[serde(default, rename = "buildUser")]
72 pub build_user: String,
73 #[serde(default, rename = "buildDate")]
74 pub build_date: String,
75 #[serde(default, rename = "goVersion")]
76 pub go_version: String,
77}
78
79#[derive(Debug, Deserialize)]
81#[serde(rename_all = "camelCase")]
82pub struct PrometheusData {
83 pub result_type: String,
84 pub result: Vec<PrometheusResult>,
85}
86
87#[derive(Debug, Deserialize)]
89pub struct PrometheusResult {
90 pub metric: std::collections::HashMap<String, String>,
92 pub values: Vec<(f64, String)>,
95}
96
97pub fn parse_response(
111 json: &[u8],
112 metric: &str,
113 query: &str,
114 granularity_ns: u128,
115) -> Result<QueryResponse, ClientError> {
116 let response: PrometheusResponse =
117 serde_json::from_slice(json).map_err(|e| ClientError::ParseError(e.to_string()))?;
118
119 if response.status != "success" {
121 let message = response
122 .error
123 .unwrap_or_else(|| "unknown error".to_string());
124 return Err(ClientError::BackendError {
125 status: 400,
126 message,
127 });
128 }
129
130 let data = response
131 .data
132 .ok_or_else(|| ClientError::ParseError("missing data field".to_string()))?;
133
134 let result_type = match data.result_type.as_str() {
136 "matrix" => ResultType::Matrix,
137 "vector" => ResultType::Vector,
138 "scalar" => ResultType::Scalar,
139 "string" => ResultType::String,
140 _ => ResultType::Matrix, };
142
143 let groups = data.result.into_iter().map(convert_result).collect();
145
146 Ok(QueryResponse {
147 metric: metric.to_string(),
148 query: query.to_string(),
149 parsed_agg: None,
150 parsed_filter: String::new(),
151 parsed_grouping: None,
152 parsed_time_range: None,
153 start: None,
154 end: None,
155 granularity_ns,
156 groups,
157 result_type,
158 })
159}
160
161pub fn parse_labels_response(json: &[u8]) -> Result<Vec<String>, ClientError> {
170 let response: PrometheusLabelsResponse =
171 serde_json::from_slice(json).map_err(|e| ClientError::ParseError(e.to_string()))?;
172
173 if response.status != "success" {
174 let message = response
175 .error
176 .unwrap_or_else(|| "unknown error".to_string());
177 return Err(ClientError::BackendError {
178 status: 400,
179 message,
180 });
181 }
182
183 Ok(response.data)
184}
185
186#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
190pub struct MetricLabels {
191 pub labels: std::collections::HashMap<String, Vec<String>>,
193}
194
195pub fn parse_series_response(json: &[u8]) -> Result<MetricLabels, ClientError> {
205 let response: PrometheusSeriesResponse =
206 serde_json::from_slice(json).map_err(|e| ClientError::ParseError(e.to_string()))?;
207
208 if response.status != "success" {
209 let message = response
210 .error
211 .unwrap_or_else(|| "unknown error".to_string());
212 return Err(ClientError::BackendError {
213 status: 400,
214 message,
215 });
216 }
217
218 let mut labels: FxHashMap<String, FxHashSet<String>> = FxHashMap::default();
220
221 for series in response.data {
222 for (key, value) in series {
223 if key == "__name__" {
225 continue;
226 }
227 labels.entry(key).or_default().insert(value);
228 }
229 }
230
231 let labels = labels
233 .into_iter()
234 .map(|(k, v)| {
235 let mut values: Vec<_> = v.into_iter().collect();
236 values.sort();
237 (k, values)
238 })
239 .collect();
240
241 Ok(MetricLabels { labels })
242}
243
244pub fn parse_buildinfo_response(json: &[u8]) -> Result<PrometheusBuildInfo, ClientError> {
253 let response: PrometheusBuildInfoResponse =
254 serde_json::from_slice(json).map_err(|e| ClientError::ParseError(e.to_string()))?;
255
256 if response.status != "success" {
257 let message = response
258 .error
259 .unwrap_or_else(|| "unknown error".to_string());
260 return Err(ClientError::BackendError {
261 status: 400,
262 message,
263 });
264 }
265
266 response
267 .data
268 .ok_or_else(|| ClientError::ParseError("missing data field".to_string()))
269}
270
271fn convert_result(result: PrometheusResult) -> MetricsGroup {
273 let group = result
275 .metric
276 .iter()
277 .filter(|(k, _)| *k != "__name__")
278 .map(|(k, v)| format!("{k}:{v}"))
279 .collect::<Vec<_>>()
280 .join(",");
281
282 let buckets = result
284 .values
285 .iter()
286 .filter_map(|(ts, val)| {
287 let value: f64 = val.parse().ok()?;
288 let ts_ns = (*ts * 1_000_000_000.0) as u128;
289 Some(MetricsBucket {
290 start: ts_ns,
291 end: ts_ns, value,
293 count: 1,
294 })
295 })
296 .collect();
297
298 MetricsGroup { group, buckets }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 #[test]
306 fn test_parse_success_response() {
307 let json = r#"{
308 "status": "success",
309 "data": {
310 "resultType": "matrix",
311 "result": [
312 {
313 "metric": {"__name__": "cpu_usage", "env": "prod", "host": "server1"},
314 "values": [[1700000000, "0.5"], [1700000060, "0.6"]]
315 }
316 ]
317 }
318 }"#;
319
320 let response = parse_response(json.as_bytes(), "cpu_usage", "env:prod", 60_000_000_000)
321 .expect("should parse");
322
323 assert_eq!(response.metric, "cpu_usage");
324 assert_eq!(response.result_type, ResultType::Matrix);
325 assert_eq!(response.groups.len(), 1);
326
327 let group = &response.groups[0];
328 assert!(group.group.contains("env:prod"));
330 assert!(group.group.contains("host:server1"));
331 assert!(!group.group.contains("__name__"));
332
333 assert_eq!(group.buckets.len(), 2);
334 assert!((group.buckets[0].value - 0.5).abs() < f64::EPSILON);
335 assert!((group.buckets[1].value - 0.6).abs() < f64::EPSILON);
336 }
337
338 #[test]
339 fn test_parse_multiple_series() {
340 let json = r#"{
341 "status": "success",
342 "data": {
343 "resultType": "matrix",
344 "result": [
345 {
346 "metric": {"env": "prod"},
347 "values": [[1700000000, "10"]]
348 },
349 {
350 "metric": {"env": "staging"},
351 "values": [[1700000000, "5"]]
352 }
353 ]
354 }
355 }"#;
356
357 let response =
358 parse_response(json.as_bytes(), "metric", "*", 60_000_000_000).expect("should parse");
359
360 assert_eq!(response.groups.len(), 2);
361 }
362
363 #[test]
364 fn test_parse_error_response() {
365 let json = r#"{
366 "status": "error",
367 "errorType": "bad_data",
368 "error": "invalid query syntax"
369 }"#;
370
371 let result = parse_response(json.as_bytes(), "metric", "bad", 60_000_000_000);
372 assert!(result.is_err());
373
374 match result.unwrap_err() {
375 ClientError::BackendError { message, .. } => {
376 assert!(message.contains("invalid query syntax"));
377 }
378 _ => panic!("expected BackendError"),
379 }
380 }
381
382 #[test]
383 fn test_parse_empty_result() {
384 let json = r#"{
385 "status": "success",
386 "data": {
387 "resultType": "matrix",
388 "result": []
389 }
390 }"#;
391
392 let response =
393 parse_response(json.as_bytes(), "metric", "*", 60_000_000_000).expect("should parse");
394
395 assert!(response.groups.is_empty());
396 }
397
398 #[test]
399 fn test_parse_invalid_json() {
400 let json = b"not json";
401 let result = parse_response(json, "metric", "*", 60_000_000_000);
402 assert!(matches!(result, Err(ClientError::ParseError(_))));
403 }
404
405 #[test]
408 fn test_parse_labels_response_success() {
409 let json = r#"{
410 "status": "success",
411 "data": ["env", "host", "service", "region"]
412 }"#;
413
414 let labels = parse_labels_response(json.as_bytes()).expect("should parse");
415 assert_eq!(labels, vec!["env", "host", "service", "region"]);
416 }
417
418 #[test]
419 fn test_parse_labels_response_empty() {
420 let json = r#"{
421 "status": "success",
422 "data": []
423 }"#;
424
425 let labels = parse_labels_response(json.as_bytes()).expect("should parse");
426 assert!(labels.is_empty());
427 }
428
429 #[test]
430 fn test_parse_labels_response_error() {
431 let json = r#"{
432 "status": "error",
433 "errorType": "bad_data",
434 "error": "invalid label name"
435 }"#;
436
437 let result = parse_labels_response(json.as_bytes());
438 assert!(result.is_err());
439
440 match result.unwrap_err() {
441 ClientError::BackendError { message, .. } => {
442 assert!(message.contains("invalid label name"));
443 }
444 _ => panic!("expected BackendError"),
445 }
446 }
447
448 #[test]
449 fn test_parse_labels_response_invalid_json() {
450 let json = b"not json";
451 let result = parse_labels_response(json);
452 assert!(matches!(result, Err(ClientError::ParseError(_))));
453 }
454
455 #[test]
456 fn test_parse_label_values_response() {
457 let json = r#"{
459 "status": "success",
460 "data": ["prod", "staging", "dev"]
461 }"#;
462
463 let values = parse_labels_response(json.as_bytes()).expect("should parse");
464 assert_eq!(values, vec!["prod", "staging", "dev"]);
465 }
466
467 #[test]
468 fn test_parse_metric_names_response() {
469 let json = r#"{
471 "status": "success",
472 "data": ["cpu_usage", "memory_usage", "http_requests_total"]
473 }"#;
474
475 let metrics = parse_labels_response(json.as_bytes()).expect("should parse");
476 assert_eq!(
477 metrics,
478 vec!["cpu_usage", "memory_usage", "http_requests_total"]
479 );
480 }
481
482 #[test]
485 fn test_parse_series_response_success() {
486 let json = r#"{
487 "status": "success",
488 "data": [
489 {"__name__": "cpu_usage", "cpu": "0", "mode": "idle"},
490 {"__name__": "cpu_usage", "cpu": "0", "mode": "system"},
491 {"__name__": "cpu_usage", "cpu": "1", "mode": "idle"},
492 {"__name__": "cpu_usage", "cpu": "1", "mode": "user"}
493 ]
494 }"#;
495
496 let result = parse_series_response(json.as_bytes()).expect("should parse");
497
498 assert_eq!(result.labels.len(), 2);
500
501 let cpu_values = result.labels.get("cpu").expect("should have cpu label");
503 assert_eq!(cpu_values, &vec!["0".to_string(), "1".to_string()]);
504
505 let mode_values = result.labels.get("mode").expect("should have mode label");
507 assert_eq!(
508 mode_values,
509 &vec!["idle".to_string(), "system".to_string(), "user".to_string()]
510 );
511 }
512
513 #[test]
514 fn test_parse_series_response_empty() {
515 let json = r#"{
516 "status": "success",
517 "data": []
518 }"#;
519
520 let result = parse_series_response(json.as_bytes()).expect("should parse");
521 assert!(result.labels.is_empty());
522 }
523
524 #[test]
525 fn test_parse_series_response_error() {
526 let json = r#"{
527 "status": "error",
528 "errorType": "bad_data",
529 "error": "invalid match selector"
530 }"#;
531
532 let result = parse_series_response(json.as_bytes());
533 assert!(result.is_err());
534
535 match result.unwrap_err() {
536 ClientError::BackendError { message, .. } => {
537 assert!(message.contains("invalid match selector"));
538 }
539 _ => panic!("expected BackendError"),
540 }
541 }
542
543 #[test]
546 fn test_parse_result_type_vector() {
547 let json = r#"{
548 "status": "success",
549 "data": {
550 "resultType": "vector",
551 "result": [
552 {
553 "metric": {"env": "prod"},
554 "values": [[1700000000, "42"]]
555 }
556 ]
557 }
558 }"#;
559
560 let response =
561 parse_response(json.as_bytes(), "metric", "*", 60_000_000_000).expect("should parse");
562 assert_eq!(response.result_type, ResultType::Vector);
563 }
564
565 #[test]
566 fn test_parse_result_type_scalar() {
567 let json = r#"{
568 "status": "success",
569 "data": {
570 "resultType": "scalar",
571 "result": []
572 }
573 }"#;
574
575 let response =
576 parse_response(json.as_bytes(), "metric", "*", 60_000_000_000).expect("should parse");
577 assert_eq!(response.result_type, ResultType::Scalar);
578 }
579
580 #[test]
581 fn test_parse_result_type_string() {
582 let json = r#"{
583 "status": "success",
584 "data": {
585 "resultType": "string",
586 "result": []
587 }
588 }"#;
589
590 let response =
591 parse_response(json.as_bytes(), "metric", "*", 60_000_000_000).expect("should parse");
592 assert_eq!(response.result_type, ResultType::String);
593 }
594}