use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use super::error::ErrorDetail;
use super::log::LogEntry;
use super::metric::{MetricInfoDetail, MetricSeries};
use super::trace::{Span, TraceDetail};
use crate::provider::results::MetricResultType;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound(
serialize = "T: Serialize",
deserialize = "T: serde::de::DeserializeOwned"
))]
pub struct Response<T> {
pub status: ResponseStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<QueryMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<T>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<ErrorDetail>,
#[serde(skip_serializing_if = "Option::is_none")]
pub warnings: Option<Vec<String>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ResponseStatus {
Success,
Error,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryMetadata {
pub provider: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_language: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_range: Option<TimeRange>,
pub total_count: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub returned_series: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_complete: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct TimeRange {
pub start: i64,
pub end: i64,
}
impl<T: Serialize> Response<T> {
pub fn success(metadata: QueryMetadata, data: T) -> Self {
Self {
status: ResponseStatus::Success,
metadata: Some(metadata),
data: Some(data),
error: None,
warnings: None,
}
}
}
impl<T> Response<T> {
pub fn error(error: ErrorDetail) -> Self {
Self {
status: ResponseStatus::Error,
metadata: None,
data: None,
error: Some(error),
warnings: None,
}
}
}
pub const RESULT_TYPE_METRIC_LIST: &str = "metric_list";
pub const RESULT_TYPE_METRIC_INFO: &str = "metric_info";
pub const RESULT_TYPE_LABEL_LIST: &str = "label_list";
pub const RESULT_TYPE_LABEL_VALUES: &str = "label_values";
pub const RESULT_TYPE_SERIES: &str = "series";
pub const RESULT_TYPE_LOG_ENTRIES: &str = "log_entries";
pub const RESULT_TYPE_SPANS: &str = "spans";
pub const RESULT_TYPE_TRACE_DETAIL: &str = "trace_detail";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricQueryData {
pub result_type: MetricResultType,
pub series: Vec<MetricSeries>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScalarData {
pub result_type: MetricResultType,
pub scalar: (i64, f64),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StringListData {
pub result_type: String,
pub items: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LabelValuesData {
pub result_type: String,
pub label: String,
pub items: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricInfoData {
pub result_type: String,
pub info: Option<MetricInfoDetail>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SeriesListData {
pub result_type: String,
pub series: Vec<BTreeMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogSearchData {
pub result_type: String,
pub entries: Vec<LogEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraceSearchData {
pub result_type: String,
pub spans: Vec<Span>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TraceDetailData {
pub result_type: String,
#[serde(flatten)]
pub detail: TraceDetail,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtensionData {
pub result_type: String,
pub data: serde_json::Value,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_response_status_serialization() {
assert_eq!(
serde_json::to_string(&ResponseStatus::Success).unwrap(),
r#""success""#
);
assert_eq!(
serde_json::to_string(&ResponseStatus::Error).unwrap(),
r#""error""#
);
}
#[test]
fn test_success_response_structure() {
let metadata = QueryMetadata {
provider: "dev-vm".to_string(),
provider_type: None,
query_language: None,
query: None,
time_range: None,
total_count: 2,
returned_series: None,
is_complete: None,
cursor: None,
};
let resp = Response::success(
metadata,
serde_json::json!({"result_type": "matrix", "series": []}),
);
let json = serde_json::to_value(&resp).unwrap();
assert_eq!(json["status"], "success");
assert!(json.get("error").is_none());
assert_eq!(json["metadata"]["provider"], "dev-vm");
assert_eq!(json["metadata"]["total_count"], 2);
}
#[test]
fn test_error_response_structure() {
use super::super::error::{ErrorCategory, ErrorCode};
let detail = ErrorDetail {
category: ErrorCategory::Provider,
code: ErrorCode::QuerySyntax,
provider: Some("dev-vm".to_string()),
message: "invalid expression".to_string(),
raw_error: Some("bad_data".to_string()),
recoverable: false,
suggestion: Some("Check PromQL syntax".to_string()),
doc_url: None,
source_chain: None,
};
let resp: Response<serde_json::Value> = Response::error(detail);
let json = serde_json::to_value(&resp).unwrap();
assert_eq!(json["status"], "error");
assert!(json.get("data").is_none());
assert_eq!(json["error"]["category"], "provider");
assert_eq!(json["error"]["code"], "query_syntax");
}
#[test]
fn test_extension_data_serialization_string_list() {
let metadata = QueryMetadata {
provider: "dev-vt".to_string(),
provider_type: None,
query_language: None,
query: None,
time_range: None,
total_count: 3,
returned_series: None,
is_complete: None,
cursor: None,
};
let resp = Response::success(
metadata,
ExtensionData {
result_type: "services".to_string(),
data: serde_json::json!(["cart", "payment", "frontend"]),
},
);
let json = serde_json::to_value(&resp).unwrap();
assert_eq!(json["status"], "success");
assert_eq!(json["metadata"]["total_count"], 3);
assert_eq!(json["data"]["result_type"], "services");
let items = json["data"]["data"].as_array().unwrap();
assert_eq!(items.len(), 3);
assert_eq!(items[0], "cart");
}
#[test]
fn test_extension_data_serialization_structured() {
let metadata = QueryMetadata {
provider: "dev-vt".to_string(),
provider_type: None,
query_language: None,
query: None,
time_range: None,
total_count: 2,
returned_series: None,
is_complete: None,
cursor: None,
};
let resp = Response::success(
metadata,
ExtensionData {
result_type: "services".to_string(),
data: serde_json::json!([
{"name": "cart", "spans": 100},
{"name": "payment", "spans": 50}
]),
},
);
let json = serde_json::to_value(&resp).unwrap();
assert_eq!(json["status"], "success");
assert_eq!(json["data"]["result_type"], "services");
let data = json["data"]["data"].as_array().unwrap();
assert_eq!(data.len(), 2);
assert_eq!(data[0]["name"], "cart");
assert_eq!(data[0]["spans"], 100);
}
}