serper_sdk/search/
query.rs1use crate::core::{
2 error::{Result, SerperError},
3 types::{Location, Pagination},
4};
5use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct SearchQuery {
17 pub q: String,
19
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub location: Option<String>,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub gl: Option<String>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub hl: Option<String>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub page: Option<u32>,
35
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub num: Option<u32>,
39}
40
41impl SearchQuery {
42 pub fn new(query: String) -> Result<Self> {
52 if query.trim().is_empty() {
53 return Err(SerperError::validation_error(
54 "Query string cannot be empty",
55 ));
56 }
57
58 Ok(Self {
59 q: query,
60 location: None,
61 gl: None,
62 hl: None,
63 page: None,
64 num: None,
65 })
66 }
67
68 pub fn with_location(mut self, location: String) -> Self {
74 self.location = Some(location);
75 self
76 }
77
78 pub fn with_country(mut self, country: String) -> Self {
84 self.gl = Some(country);
85 self
86 }
87
88 pub fn with_language(mut self, language: String) -> Self {
94 self.hl = Some(language);
95 self
96 }
97
98 pub fn with_page(mut self, page: u32) -> Self {
104 self.page = Some(page);
105 self
106 }
107
108 pub fn with_num_results(mut self, num: u32) -> Self {
114 self.num = Some(num);
115 self
116 }
117
118 pub fn with_location_config(mut self, location: Location) -> Self {
124 if let Some(loc) = location.location {
125 self.location = Some(loc);
126 }
127 if let Some(country) = location.country_code {
128 self.gl = Some(country);
129 }
130 if let Some(language) = location.language_code {
131 self.hl = Some(language);
132 }
133 self
134 }
135
136 pub fn with_pagination(mut self, pagination: Pagination) -> Self {
142 if let Some(page) = pagination.page {
143 self.page = Some(page);
144 }
145 if let Some(num) = pagination.num_results {
146 self.num = Some(num);
147 }
148 self
149 }
150
151 pub fn validate(&self) -> Result<()> {
157 if self.q.trim().is_empty() {
158 return Err(SerperError::validation_error(
159 "Query string cannot be empty",
160 ));
161 }
162
163 if let Some(page) = self.page
164 && page == 0
165 {
166 return Err(SerperError::validation_error(
167 "Page number must be greater than 0",
168 ));
169 }
170
171 if let Some(num) = self.num
172 && (num == 0 || num > 100)
173 {
174 return Err(SerperError::validation_error(
175 "Number of results must be between 1 and 100",
176 ));
177 }
178
179 Ok(())
180 }
181
182 pub fn query(&self) -> &str {
184 &self.q
185 }
186
187 pub fn has_location_params(&self) -> bool {
189 self.location.is_some() || self.gl.is_some() || self.hl.is_some()
190 }
191
192 pub fn has_pagination_params(&self) -> bool {
194 self.page.is_some() || self.num.is_some()
195 }
196}
197
198pub struct SearchQueryBuilder {
200 query: Option<String>,
201 location: Option<String>,
202 country: Option<String>,
203 language: Option<String>,
204 page: Option<u32>,
205 num_results: Option<u32>,
206}
207
208impl SearchQueryBuilder {
209 pub fn new() -> Self {
211 Self {
212 query: None,
213 location: None,
214 country: None,
215 language: None,
216 page: None,
217 num_results: None,
218 }
219 }
220
221 pub fn query(mut self, query: impl Into<String>) -> Self {
223 self.query = Some(query.into());
224 self
225 }
226
227 pub fn location(mut self, location: impl Into<String>) -> Self {
229 self.location = Some(location.into());
230 self
231 }
232
233 pub fn country(mut self, country: impl Into<String>) -> Self {
235 self.country = Some(country.into());
236 self
237 }
238
239 pub fn language(mut self, language: impl Into<String>) -> Self {
241 self.language = Some(language.into());
242 self
243 }
244
245 pub fn page(mut self, page: u32) -> Self {
247 self.page = Some(page);
248 self
249 }
250
251 pub fn num_results(mut self, num: u32) -> Self {
253 self.num_results = Some(num);
254 self
255 }
256
257 pub fn build(self) -> Result<SearchQuery> {
259 let query = self
260 .query
261 .ok_or_else(|| SerperError::validation_error("Query string is required"))?;
262
263 let mut search_query = SearchQuery::new(query)?;
264
265 if let Some(location) = self.location {
266 search_query = search_query.with_location(location);
267 }
268 if let Some(country) = self.country {
269 search_query = search_query.with_country(country);
270 }
271 if let Some(language) = self.language {
272 search_query = search_query.with_language(language);
273 }
274 if let Some(page) = self.page {
275 search_query = search_query.with_page(page);
276 }
277 if let Some(num) = self.num_results {
278 search_query = search_query.with_num_results(num);
279 }
280
281 search_query.validate()?;
282 Ok(search_query)
283 }
284}
285
286impl Default for SearchQueryBuilder {
287 fn default() -> Self {
288 Self::new()
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295
296 #[test]
297 fn test_search_query_new() {
298 let query = SearchQuery::new("test query".to_string()).unwrap();
299 assert_eq!(query.q, "test query");
300 assert_eq!(query.location, None);
301 assert_eq!(query.gl, None);
302 assert_eq!(query.hl, None);
303 assert_eq!(query.page, None);
304 assert_eq!(query.num, None);
305 }
306
307 #[test]
308 fn test_search_query_empty_fails() {
309 let result = SearchQuery::new("".to_string());
310 assert!(result.is_err());
311 }
312
313 #[test]
314 fn test_search_query_with_location() {
315 let query = SearchQuery::new("test".to_string())
316 .unwrap()
317 .with_location("Paris".to_string());
318 assert_eq!(query.location, Some("Paris".to_string()));
319 }
320
321 #[test]
322 fn test_search_query_builder() {
323 let query = SearchQueryBuilder::new()
324 .query("test query")
325 .location("Paris")
326 .country("fr")
327 .language("en")
328 .page(1)
329 .num_results(10)
330 .build()
331 .unwrap();
332
333 assert_eq!(query.q, "test query");
334 assert_eq!(query.location, Some("Paris".to_string()));
335 assert_eq!(query.gl, Some("fr".to_string()));
336 assert_eq!(query.hl, Some("en".to_string()));
337 assert_eq!(query.page, Some(1));
338 assert_eq!(query.num, Some(10));
339 }
340
341 #[test]
342 fn test_search_query_validation() {
343 let query = SearchQuery::new("test".to_string()).unwrap().with_page(0);
344
345 assert!(query.validate().is_err());
346
347 let query = SearchQuery::new("test".to_string())
348 .unwrap()
349 .with_num_results(101);
350
351 assert!(query.validate().is_err());
352 }
353
354 #[test]
355 fn test_search_query_helper_methods() {
356 let query = SearchQuery::new("test".to_string())
357 .unwrap()
358 .with_location("Paris".to_string())
359 .with_page(1);
360
361 assert!(query.has_location_params());
362 assert!(query.has_pagination_params());
363 assert_eq!(query.query(), "test");
364 }
365}