use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ListQuery {
pub limit: i64,
pub offset: i64,
pub sort_by: Option<String>,
pub search: Option<String>,
pub filter: Option<String>,
}
impl Default for ListQuery {
fn default() -> Self {
Self {
limit: -1,
offset: 0,
sort_by: None,
search: None,
filter: None,
}
}
}
impl ListQuery {
pub fn to_query_pairs(&self) -> Vec<(String, String)> {
let mut pairs = Vec::with_capacity(5);
pairs.push(("limit".to_string(), self.limit.to_string()));
pairs.push(("offset".to_string(), self.offset.to_string()));
if let Some(sort_by) = &self.sort_by {
pairs.push(("sortBy".to_string(), sort_by.clone()));
}
if let Some(search) = &self.search {
pairs.push(("search".to_string(), search.clone()));
}
if let Some(filter) = &self.filter {
pairs.push(("filter".to_string(), filter.clone()));
}
pairs
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListEnvelope<T> {
pub items: Vec<T>,
pub metadata: ListMetadata,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ListMetadata {
#[serde(default)]
pub total: i64,
#[serde(default)]
pub matching: i64,
#[serde(default)]
pub limit: i64,
#[serde(default)]
pub offset: i64,
}
#[cfg(test)]
mod tests {
use super::{ListEnvelope, ListMetadata, ListQuery};
#[test]
fn query_pairs_skip_absent_optionals() {
let pairs = ListQuery::default().to_query_pairs();
assert_eq!(
pairs,
vec![
("limit".to_string(), "-1".to_string()),
("offset".to_string(), "0".to_string()),
],
"default carries only limit=-1 and offset=0"
);
let full = ListQuery {
limit: 200,
offset: 40,
sort_by: Some("timestamp".into()),
search: Some("GatewayManager".into()),
filter: None,
};
let pairs = full.to_query_pairs();
assert_eq!(
pairs,
vec![
("limit".to_string(), "200".to_string()),
("offset".to_string(), "40".to_string()),
("sortBy".to_string(), "timestamp".to_string()),
("search".to_string(), "GatewayManager".to_string()),
],
"present optionals serialize under their gateway-native names; filter skipped"
);
}
#[test]
fn envelope_is_serde_tolerant() {
let body = serde_json::json!({
"items": [{"id": "mod-1"}],
"metadata": {
"total": 368,
"matching": 368,
"limit": -1,
"offset": 0,
"metrics": {"elapsedMs": 12}
}
});
let page: ListEnvelope<serde_json::Value> =
serde_json::from_value(body).expect("extra `metrics` key is tolerated");
assert_eq!(page.items.len(), 1);
assert_eq!(
page.metadata,
ListMetadata {
total: 368,
matching: 368,
limit: -1,
offset: 0,
}
);
let sparse = serde_json::json!({"items": [], "metadata": {}});
let page: ListEnvelope<serde_json::Value> =
serde_json::from_value(sparse).expect("absent metadata fields default");
assert_eq!(page.metadata.total, 0);
}
}