use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct Filter {
pub field: String,
pub value: FilterValue,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum FilterValue {
String(String),
Integer(i64),
Float(f64),
Boolean(bool),
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
pub struct RequestParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filters: Option<Vec<Filter>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
}
impl RequestParams {
pub fn new() -> Self {
Self::default()
}
pub fn field(mut self, field: impl Into<String>) -> Self {
self.fields.get_or_insert_with(Vec::new).push(field.into());
self
}
pub fn fields(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.fields
.get_or_insert_with(Vec::new)
.extend(fields.into_iter().map(Into::into));
self
}
pub fn filter(mut self, field: impl Into<String>, value: impl Into<FilterValue>) -> Self {
self.filters.get_or_insert_with(Vec::new).push(Filter {
field: field.into(),
value: value.into(),
});
self
}
pub fn limit(mut self, limit: u32) -> Self {
self.limit = Some(limit.min(10));
self
}
}
impl From<String> for FilterValue {
fn from(s: String) -> Self {
FilterValue::String(s)
}
}
impl From<&str> for FilterValue {
fn from(s: &str) -> Self {
FilterValue::String(s.to_string())
}
}
impl From<i64> for FilterValue {
fn from(i: i64) -> Self {
FilterValue::Integer(i)
}
}
impl From<i32> for FilterValue {
fn from(i: i32) -> Self {
FilterValue::Integer(i as i64)
}
}
impl From<u64> for FilterValue {
fn from(u: u64) -> Self {
FilterValue::Integer(u as i64)
}
}
impl From<u32> for FilterValue {
fn from(u: u32) -> Self {
FilterValue::Integer(u as i64)
}
}
impl From<f64> for FilterValue {
fn from(f: f64) -> Self {
FilterValue::Float(f)
}
}
impl From<bool> for FilterValue {
fn from(b: bool) -> Self {
FilterValue::Boolean(b)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_request_params_builder() {
let params = RequestParams::new()
.field("name")
.field("url")
.filter("in_language.identifier", "en")
.filter("is_part_of.identifier", "enwiki")
.limit(5);
assert_eq!(params.fields.as_ref().unwrap().len(), 2);
assert_eq!(params.filters.as_ref().unwrap().len(), 2);
assert_eq!(params.limit, Some(5));
}
#[test]
fn test_filter_value_conversions() {
let string_val: FilterValue = "test".into();
let int_val: FilterValue = 42i64.into();
let bool_val: FilterValue = true.into();
assert!(matches!(string_val, FilterValue::String(_)));
assert!(matches!(int_val, FilterValue::Integer(42)));
assert!(matches!(bool_val, FilterValue::Boolean(true)));
}
#[test]
fn test_filter_serialization() {
let filter = Filter {
field: "in_language.identifier".to_string(),
value: FilterValue::String("en".to_string()),
};
let json = serde_json::to_string(&filter).unwrap();
assert!(json.contains("in_language.identifier"));
assert!(json.contains("en"));
}
#[test]
fn test_request_params_serialization() {
let params = RequestParams::new()
.field("name")
.field("url")
.filter("in_language.identifier", "en")
.limit(5);
let json = serde_json::to_string(¶ms).unwrap();
assert!(json.contains("fields"));
assert!(json.contains("name"));
assert!(json.contains("url"));
assert!(json.contains("filters"));
assert!(json.contains("limit"));
}
#[test]
fn test_limit_clamping() {
let params = RequestParams::new().limit(15);
assert_eq!(params.limit, Some(10)); }
}