From 33c36e3e3e8796436ed0a02d977a48fb75fe95bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20K=C3=BChn?= Date: Sun, 22 Mar 2026 16:17:11 +0100 Subject: [PATCH] feat: complete search endpoint parameters and pagination --- examples/lookup.rs | 3 +- src/client.rs | 84 ++++++++++++++++++++++++++++++++++++++----- tests/client_tests.rs | 5 ++- 3 files changed, 78 insertions(+), 14 deletions(-) diff --git a/examples/lookup.rs b/examples/lookup.rs index a05963b..d05a18e 100644 --- a/examples/lookup.rs +++ b/examples/lookup.rs @@ -53,8 +53,7 @@ async fn main() -> euvd_rs::Result<()> { let query = args.get(2).expect("Usage: lookup search "); let params = SearchParams { text: Some(query.clone()), - from_score: None, - to_score: None, + ..Default::default() }; let result = client.search(¶ms).await?; println!( diff --git a/src/client.rs b/src/client.rs index aa95a22..3328c71 100644 --- a/src/client.rs +++ b/src/client.rs @@ -70,6 +70,7 @@ impl EuvdClient { /// text: Some("Microsoft".to_string()), /// from_score: Some(7.0), /// to_score: Some(10.0), + /// ..Default::default() /// }; /// let results = client.search(¶ms).await?; /// # Ok(()) @@ -108,6 +109,48 @@ impl EuvdClient { if let Some(to_score) = params.to_score { query_parts.push(format!("toScore={}", to_score)); } + if let Some(from_epss) = params.from_epss { + query_parts.push(format!("fromEpss={}", from_epss)); + } + if let Some(to_epss) = params.to_epss { + query_parts.push(format!("toEpss={}", to_epss)); + } + if let Some(from_date) = ¶ms.from_date { + query_parts.push(format!("fromDate={}", urlencoding::encode(from_date))); + } + if let Some(to_date) = ¶ms.to_date { + query_parts.push(format!("toDate={}", urlencoding::encode(to_date))); + } + if let Some(from_updated_date) = ¶ms.from_updated_date { + query_parts.push(format!( + "fromUpdatedDate={}", + urlencoding::encode(from_updated_date) + )); + } + if let Some(to_updated_date) = ¶ms.to_updated_date { + query_parts.push(format!( + "toUpdatedDate={}", + urlencoding::encode(to_updated_date) + )); + } + if let Some(product) = ¶ms.product { + query_parts.push(format!("product={}", urlencoding::encode(product))); + } + if let Some(vendor) = ¶ms.vendor { + query_parts.push(format!("vendor={}", urlencoding::encode(vendor))); + } + if let Some(assigner) = ¶ms.assigner { + query_parts.push(format!("assigner={}", urlencoding::encode(assigner))); + } + if let Some(exploited) = params.exploited { + query_parts.push(format!("exploited={}", exploited)); + } + if let Some(page) = params.page { + query_parts.push(format!("page={}", page)); + } + if let Some(size) = params.size { + query_parts.push(format!("size={}", size)); + } if !query_parts.is_empty() { url.push('?'); @@ -350,18 +393,19 @@ pub(crate) fn parse_csv_mapping(body: &str) -> Vec { /// ``` /// use euvd_rs::SearchParams; /// -/// // Search by text +/// // Search by text with pagination /// let params = SearchParams { /// text: Some("Microsoft".to_string()), -/// from_score: None, -/// to_score: None, +/// page: Some(0), +/// size: Some(20), +/// ..Default::default() /// }; /// /// // Search by score range /// let params = SearchParams { -/// text: None, /// from_score: Some(7.5), /// to_score: Some(10.0), +/// ..Default::default() /// }; /// ``` #[derive(Clone, Debug, Default)] @@ -372,6 +416,30 @@ pub struct SearchParams { pub from_score: Option, /// Maximum CVSS score (0.0-10.0) pub to_score: Option, + /// Minimum EPSS score (0-100) + pub from_epss: Option, + /// Maximum EPSS score (0-100) + pub to_epss: Option, + /// Filter by published date (start, YYYY-MM-DD) + pub from_date: Option, + /// Filter by published date (end, YYYY-MM-DD) + pub to_date: Option, + /// Filter by last updated date (start, YYYY-MM-DD) + pub from_updated_date: Option, + /// Filter by last updated date (end, YYYY-MM-DD) + pub to_updated_date: Option, + /// Filter by product name + pub product: Option, + /// Filter by vendor name + pub vendor: Option, + /// Filter by assigner + pub assigner: Option, + /// Filter to exploited vulnerabilities only + pub exploited: Option, + /// Page number (0-based) + pub page: Option, + /// Results per page (default 10, max 100) + pub size: Option, } #[cfg(test)] @@ -440,9 +508,8 @@ mod tests { async fn search_params_score_above_10() { let client = crate::EuvdClient::new(); let params = SearchParams { - text: None, from_score: Some(11.0), - to_score: None, + ..Default::default() }; let result = client.search(¶ms).await; assert!(matches!(result, Err(crate::EuvdError::Parse(_)))); @@ -452,9 +519,9 @@ mod tests { async fn search_params_inverted_range() { let client = crate::EuvdClient::new(); let params = SearchParams { - text: None, from_score: Some(9.0), to_score: Some(5.0), + ..Default::default() }; let result = client.search(¶ms).await; assert!(matches!(result, Err(crate::EuvdError::Parse(_)))); @@ -464,9 +531,8 @@ mod tests { async fn search_params_negative_score() { let client = crate::EuvdClient::new(); let params = SearchParams { - text: None, from_score: Some(-1.0), - to_score: None, + ..Default::default() }; let result = client.search(¶ms).await; assert!(matches!(result, Err(crate::EuvdError::Parse(_)))); diff --git a/tests/client_tests.rs b/tests/client_tests.rs index 070d48d..c2aa5dd 100644 --- a/tests/client_tests.rs +++ b/tests/client_tests.rs @@ -253,8 +253,7 @@ async fn test_search_with_text() { let params = SearchParams { text: Some("linux".to_string()), - from_score: None, - to_score: None, + ..Default::default() }; let result = client.search(¶ms).await.unwrap(); @@ -284,9 +283,9 @@ async fn test_search_with_score_range() { let client = EuvdClient::builder().base_url(server.url()).build(); let params = SearchParams { - text: None, from_score: Some(9.0), to_score: Some(10.0), + ..Default::default() }; let result = client.search(¶ms).await.unwrap();