ignition_core/client/
query.rs1use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
19pub struct ListQuery {
20 pub limit: i64,
22 pub offset: i64,
24 pub sort_by: Option<String>,
26 pub search: Option<String>,
28 pub filter: Option<String>,
30}
31
32impl Default for ListQuery {
33 fn default() -> Self {
34 Self {
35 limit: -1,
36 offset: 0,
37 sort_by: None,
38 search: None,
39 filter: None,
40 }
41 }
42}
43
44impl ListQuery {
45 pub fn to_query_pairs(&self) -> Vec<(String, String)> {
50 let mut pairs = Vec::with_capacity(5);
51 pairs.push(("limit".to_string(), self.limit.to_string()));
52 pairs.push(("offset".to_string(), self.offset.to_string()));
53 if let Some(sort_by) = &self.sort_by {
54 pairs.push(("sortBy".to_string(), sort_by.clone()));
55 }
56 if let Some(search) = &self.search {
57 pairs.push(("search".to_string(), search.clone()));
58 }
59 if let Some(filter) = &self.filter {
60 pairs.push(("filter".to_string(), filter.clone()));
61 }
62 pairs
63 }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct ListEnvelope<T> {
69 pub items: Vec<T>,
71 pub metadata: ListMetadata,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
78pub struct ListMetadata {
79 #[serde(default)]
81 pub total: i64,
82 #[serde(default)]
84 pub matching: i64,
85 #[serde(default)]
87 pub limit: i64,
88 #[serde(default)]
90 pub offset: i64,
91}
92
93#[cfg(test)]
94mod tests {
95 use super::{ListEnvelope, ListMetadata, ListQuery};
96
97 #[test]
100 fn query_pairs_skip_absent_optionals() {
101 let pairs = ListQuery::default().to_query_pairs();
102 assert_eq!(
103 pairs,
104 vec![
105 ("limit".to_string(), "-1".to_string()),
106 ("offset".to_string(), "0".to_string()),
107 ],
108 "default carries only limit=-1 and offset=0"
109 );
110
111 let full = ListQuery {
112 limit: 200,
113 offset: 40,
114 sort_by: Some("timestamp".into()),
115 search: Some("GatewayManager".into()),
116 filter: None,
117 };
118 let pairs = full.to_query_pairs();
119 assert_eq!(
120 pairs,
121 vec![
122 ("limit".to_string(), "200".to_string()),
123 ("offset".to_string(), "40".to_string()),
124 ("sortBy".to_string(), "timestamp".to_string()),
125 ("search".to_string(), "GatewayManager".to_string()),
126 ],
127 "present optionals serialize under their gateway-native names; filter skipped"
128 );
129 }
130
131 #[test]
134 fn envelope_is_serde_tolerant() {
135 let body = serde_json::json!({
136 "items": [{"id": "mod-1"}],
137 "metadata": {
138 "total": 368,
139 "matching": 368,
140 "limit": -1,
141 "offset": 0,
142 "metrics": {"elapsedMs": 12}
143 }
144 });
145 let page: ListEnvelope<serde_json::Value> =
146 serde_json::from_value(body).expect("extra `metrics` key is tolerated");
147 assert_eq!(page.items.len(), 1);
148 assert_eq!(
149 page.metadata,
150 ListMetadata {
151 total: 368,
152 matching: 368,
153 limit: -1,
154 offset: 0,
155 }
156 );
157
158 let sparse = serde_json::json!({"items": [], "metadata": {}});
159 let page: ListEnvelope<serde_json::Value> =
160 serde_json::from_value(sparse).expect("absent metadata fields default");
161 assert_eq!(page.metadata.total, 0);
162 }
163}