diff --git a/rust/crates/cloudsearch-index/src/lib.rs b/rust/crates/cloudsearch-index/src/lib.rs index aaef2b9..ba28f7f 100644 --- a/rust/crates/cloudsearch-index/src/lib.rs +++ b/rust/crates/cloudsearch-index/src/lib.rs @@ -33,6 +33,8 @@ use tokio::{ const MAX_FIELDS_PER_INDEX: usize = 1000; const MERGE_TRIGGER_DOCUMENT_COUNT: usize = 8; +const MAX_SEARCH_SIZE: usize = 10_000; +const MAX_SEARCH_OFFSET: usize = 1_000_000; #[derive(Debug, Clone, PartialEq, Eq)] pub struct MergePlan { @@ -825,8 +827,8 @@ impl IndexHandle { }); } - let from = request.from.unwrap_or(0); - let size = request.size.unwrap_or(total); + let from = request.from.unwrap_or(0).min(MAX_SEARCH_OFFSET); + let size = request.size.unwrap_or(total).min(MAX_SEARCH_SIZE); let hits = scored .into_iter() @@ -915,6 +917,22 @@ impl IndexHandle { ))); } + if let Some(size) = request.size + && size > MAX_SEARCH_SIZE + { + return Err(CloudSearchError::InvalidSearchRequest(format!( + "size ({size}) exceeds maximum allowed value ({MAX_SEARCH_SIZE})" + ))); + } + + if let Some(from) = request.from + && from > MAX_SEARCH_OFFSET + { + return Err(CloudSearchError::InvalidSearchRequest(format!( + "from ({from}) exceeds maximum allowed value ({MAX_SEARCH_OFFSET})" + ))); + } + if let Some(aggs) = &request.aggs { for (name, agg) in aggs { match agg { diff --git a/rust/crates/cloudsearch-index/tests/coverage.rs b/rust/crates/cloudsearch-index/tests/coverage.rs index 151facf..e67aa12 100644 --- a/rust/crates/cloudsearch-index/tests/coverage.rs +++ b/rust/crates/cloudsearch-index/tests/coverage.rs @@ -133,3 +133,61 @@ async fn validate_search_request_rejects_nested_bool_with_object_field() { "nested bool with object sort field should be rejected" ); } + +#[tokio::test] +async fn validate_search_request_rejects_size_exceeding_max() { + let temp_dir = TempDir::new().expect("temp dir"); + let catalog = Arc::new(IndexCatalog::new(temp_dir.path())); + catalog.initialize().await.expect("init catalog"); + let _metadata = catalog + .create_index( + "test", + CreateIndexRequest { + settings: IndexSettings::default(), + ..Default::default() + }, + ) + .await + .expect("create index"); + let handle = catalog.open_index("test").await.expect("open index"); + + let request = SearchRequest { + size: Some(100_000), + ..Default::default() + }; + + let result = handle.validate_search_request(&request); + assert!( + result.is_err(), + "size exceeding MAX_SEARCH_SIZE should be rejected" + ); +} + +#[tokio::test] +async fn validate_search_request_rejects_from_exceeding_max() { + let temp_dir = TempDir::new().expect("temp dir"); + let catalog = Arc::new(IndexCatalog::new(temp_dir.path())); + catalog.initialize().await.expect("init catalog"); + let _metadata = catalog + .create_index( + "test", + CreateIndexRequest { + settings: IndexSettings::default(), + ..Default::default() + }, + ) + .await + .expect("create index"); + let handle = catalog.open_index("test").await.expect("open index"); + + let request = SearchRequest { + from: Some(2_000_000), + ..Default::default() + }; + + let result = handle.validate_search_request(&request); + assert!( + result.is_err(), + "from exceeding MAX_SEARCH_OFFSET should be rejected" + ); +}