|
16 | 16 | use hashbrown::HashMap; |
17 | 17 | use proto::prometheus_rpc; |
18 | 18 | use regex::Regex; |
19 | | -use serde::{Deserialize,Serialize}; |
| 19 | +use serde::{Deserialize,Deserializer,Serialize}; |
20 | 20 | use strum::Display; |
21 | 21 | use utoipa::ToSchema; |
22 | 22 |
|
| 23 | +usecrate::meta::search::SearchEventType; |
| 24 | + |
| 25 | +/// Custom deserializer that accepts either a comma-separated string or a string array |
| 26 | +fndeserialize_string_or_vec<'de,D>(deserializer:D) ->Result<Vec<String>,D::Error> |
| 27 | +where |
| 28 | +D:Deserializer<'de>, |
| 29 | +{ |
| 30 | +use serde::de::{self,SeqAccess,Visitor}; |
| 31 | + |
| 32 | +structStringOrVec; |
| 33 | + |
| 34 | +impl<'de>Visitor<'de>forStringOrVec{ |
| 35 | +typeValue =Vec<String>; |
| 36 | + |
| 37 | +fnexpecting(&self,formatter:&mut std::fmt::Formatter) -> std::fmt::Result{ |
| 38 | + formatter.write_str("a string or array of strings") |
| 39 | +} |
| 40 | + |
| 41 | +fnvisit_str<E>(self,value:&str) ->Result<Self::Value,E> |
| 42 | +where |
| 43 | +E: de::Error, |
| 44 | +{ |
| 45 | +if value.is_empty(){ |
| 46 | +Ok(Vec::new()) |
| 47 | +}else{ |
| 48 | +Ok(value.split(',').map(|s| s.trim().to_string()).collect()) |
| 49 | +} |
| 50 | +} |
| 51 | + |
| 52 | +fnvisit_seq<A>(self,mutseq:A) ->Result<Self::Value,A::Error> |
| 53 | +where |
| 54 | +A:SeqAccess<'de>, |
| 55 | +{ |
| 56 | +letmut vec =Vec::new(); |
| 57 | +whileletSome(item) = seq.next_element::<String>()?{ |
| 58 | + vec.push(item); |
| 59 | +} |
| 60 | +Ok(vec) |
| 61 | +} |
| 62 | +} |
| 63 | + |
| 64 | + deserializer.deserialize_any(StringOrVec) |
| 65 | +} |
| 66 | + |
23 | 67 | pubmod grpc; |
24 | 68 | pubmod value; |
25 | 69 |
|
@@ -152,6 +196,12 @@ pub struct RequestRangeQuery { |
152 | 196 | pubuse_cache:Option<bool>, |
153 | 197 | /// Use streaming output. |
154 | 198 | pubuse_streaming:Option<bool>, |
| 199 | +#[serde(skip_serializing_if ="Option::is_none",default)] |
| 200 | +pubsearch_type:Option<SearchEventType>, |
| 201 | +#[serde(default, deserialize_with ="deserialize_string_or_vec")] |
| 202 | +pubregions:Vec<String>,// default query all regions, local: only query local region clusters |
| 203 | +#[serde(default, deserialize_with ="deserialize_string_or_vec")] |
| 204 | +pubclusters:Vec<String>,// default query all clusters, local: only query local cluster |
155 | 205 | } |
156 | 206 |
|
157 | 207 | #[derive(Debug,Deserialize)] |
@@ -376,6 +426,33 @@ mod tests { |
376 | 426 | assert_eq!(MetricType::Unknown.to_string(),"unknown"); |
377 | 427 | } |
378 | 428 |
|
| 429 | +#[test] |
| 430 | +fntest_deserialize_string_or_vec(){ |
| 431 | +// Test with comma-separated string |
| 432 | +let json =r#"{"regions": "region1,region2,region3", "clusters": "cluster1"}"#; |
| 433 | +let result:RequestRangeQuery = serde_json::from_str(json).unwrap(); |
| 434 | +assert_eq!(result.regions, vec!["region1","region2","region3"]); |
| 435 | +assert_eq!(result.clusters, vec!["cluster1"]); |
| 436 | + |
| 437 | +// Test with array |
| 438 | +let json =r#"{"regions": ["region1", "region2"], "clusters": ["cluster1", "cluster2"]}"#; |
| 439 | +let result:RequestRangeQuery = serde_json::from_str(json).unwrap(); |
| 440 | +assert_eq!(result.regions, vec!["region1","region2"]); |
| 441 | +assert_eq!(result.clusters, vec!["cluster1","cluster2"]); |
| 442 | + |
| 443 | +// Test with empty string |
| 444 | +let json =r#"{"regions": "", "clusters": []}"#; |
| 445 | +let result:RequestRangeQuery = serde_json::from_str(json).unwrap(); |
| 446 | +assert!(result.regions.is_empty()); |
| 447 | +assert!(result.clusters.is_empty()); |
| 448 | + |
| 449 | +// Test with default (missing fields) |
| 450 | +let json =r#"{}"#; |
| 451 | +let result:RequestRangeQuery = serde_json::from_str(json).unwrap(); |
| 452 | +assert!(result.regions.is_empty()); |
| 453 | +assert!(result.clusters.is_empty()); |
| 454 | +} |
| 455 | + |
379 | 456 | #[test] |
380 | 457 | fntest_api_func_response_serialize(){ |
381 | 458 | let ok =ApiFuncResponse::ok("hello".to_owned(),None); |
|