serp_sdk/lib.rs
1//! # SerpAPI Rust SDK
2//!
3//! [](https://crates.io/crates/serp-sdk)
4//! [](https://docs.rs/serp-sdk)
5//! [](#license)
6//! [](https://github.com/your-org/serp-sdk/actions)
7//!
8//! A comprehensive, production-ready Rust SDK for the [SerpAPI](https://serpapi.com) service
9//! that provides real-time search engine results through a unified, type-safe interface.
10//!
11//! > 🏆 **Developed during the [Realtime Search AI Hackathon (Hybrid)](https://www.eventbrite.com/e/realtime-search-ai-hackathon-hybrid-powered-by-serpapi-tickets)
12//! > powered by SerpAPI and organized by [AI Tinkerers Paris](https://paris.aitinkerers.org/)**
13//!
14//! ## Overview
15//!
16//! The SerpAPI Rust SDK is designed with developer experience at its core. It provides a fluent,
17//! intuitive API that makes complex search operations simple while maintaining the flexibility
18//! needed for advanced use cases. Whether you're building a search aggregator, market research
19//! tool, or AI-powered application, this SDK handles the complexity of search API interactions
20//! so you can focus on your business logic.
21//!
22//! ## Key Features
23//!
24//! - 🦀 **Type-safe by Design**: Leverage Rust's type system to catch errors at compile-time
25//! - ⚡ **Async-First Architecture**: Built on Tokio for high-performance concurrent operations
26//! - 🎯 **Intuitive Builder Pattern**: Chain methods naturally to construct complex queries
27//! - 🔄 **Intelligent Retry Logic**: Automatic retries with exponential backoff for resilience
28//! - 🌊 **Streaming Support**: Efficiently handle large result sets with async streams
29//! - 🏭 **Production-Ready**: Battle-tested error handling, comprehensive logging, and metrics
30//! - 🔍 **Multi-Engine Support**: Google, Bing, Yahoo, Yandex, and 40+ search engines
31//! - 📊 **Specialized Searches**: Images, news, videos, shopping, maps, and local results
32//!
33//! ## Installation
34//!
35//! Add the SDK to your `Cargo.toml`:
36//!
37//! ```toml
38//! [dependencies]
39//! serp-sdk = "0.2"
40//! tokio = { version = "1.0", features = ["full"] }
41//!
42//! # Optional: For streaming support
43//! futures = "0.3"
44//!
45//! # Optional: For enhanced logging
46//! tracing = "0.1"
47//! tracing-subscriber = "0.3"
48//! ```
49//!
50//! ## Quick Start
51//!
52//! ### Basic Search Example
53//!
54//! ```rust,no_run
55//! use serp_sdk::{SerpClient, SearchQuery};
56//!
57//! #[tokio::main]
58//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
59//! // Initialize client with API key from environment or explicit configuration
60//! let client = SerpClient::builder()
61//! .api_key("your-serp-api-key")
62//! .build()?;
63//!
64//! // Perform a simple search
65//! let results = client.search(
66//! SearchQuery::new("Rust programming language")
67//! .language("en")
68//! .country("us")
69//! .limit(10)?
70//! ).await?;
71//!
72//! // Process organic search results
73//! if let Some(organic) = results.organic_results {
74//! for result in organic {
75//! println!("Title: {}", result.title);
76//! println!("Link: {}", result.link);
77//! if let Some(snippet) = result.snippet {
78//! println!("Snippet: {}", snippet);
79//! }
80//! println!("---");
81//! }
82//! }
83//!
84//! Ok(())
85//! }
86//! ```
87//!
88//! ### Environment Configuration
89//!
90//! The SDK supports configuration through environment variables for production deployments:
91//!
92//! ```bash
93//! export SERP_API_KEY="your-api-key"
94//! export SERP_TIMEOUT="30" # Timeout in seconds
95//! export SERP_MAX_RETRIES="5" # Maximum retry attempts
96//! ```
97//!
98//! ## Advanced Usage
99//!
100//! ### Streaming Large Result Sets
101//!
102//! For queries that return large numbers of results, use streaming to process them efficiently:
103//!
104//! ```rust,no_run
105//! use futures::StreamExt;
106//! use serp_sdk::{SerpClient, SearchQuery, StreamConfig};
107//!
108//! # #[tokio::main]
109//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
110//! # let client = SerpClient::builder().api_key("test").build()?;
111//! // Configure streaming parameters
112//! let stream_config = StreamConfig::new()
113//! .page_size(20)? // Results per page
114//! .max_pages(5) // Maximum pages to fetch
115//! .delay(std::time::Duration::from_millis(500)); // Rate limiting
116//!
117//! // Create a search stream
118//! let mut stream = client.search_stream(
119//! SearchQuery::new("rust async programming"),
120//! stream_config
121//! );
122//!
123//! // Process results as they arrive
124//! while let Some(page_result) = stream.next().await {
125//! match page_result {
126//! Ok(results) => {
127//! println!("Processing page with {} results",
128//! results.organic_results.as_ref().map_or(0, |r| r.len()));
129//!
130//! // Process each page's results
131//! if let Some(organic) = results.organic_results {
132//! for result in organic {
133//! // Your processing logic here
134//! }
135//! }
136//! }
137//! Err(e) => eprintln!("Error fetching page: {}", e),
138//! }
139//! }
140//! # Ok(())
141//! # }
142//! ```
143//!
144//! ### Specialized Search Types
145//!
146//! The SDK provides built-in support for various search types with tailored result structures:
147//!
148//! ```rust,no_run
149//! # use serp_sdk::{SerpClient, SearchQuery};
150//! # #[tokio::main]
151//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
152//! # let client = SerpClient::builder().api_key("test").build()?;
153//! // Image search with visual results
154//! let image_results = client.search(
155//! SearchQuery::new("rust logo")
156//! .images() // Automatically sets tbm=isch parameter
157//! .limit(50)?
158//! ).await?;
159//!
160//! if let Some(images) = image_results.inline_images {
161//! for image in images {
162//! println!("Image: {:?}", image.original);
163//! println!("Thumbnail: {:?}", image.thumbnail);
164//! }
165//! }
166//!
167//! // News search with recent articles
168//! let news_results = client.search(
169//! SearchQuery::new("rust programming language")
170//! .news() // Automatically sets tbm=nws parameter
171//! .language("en")
172//! .time_filter("d") // Last 24 hours
173//! ).await?;
174//!
175//! // Video search results
176//! let video_results = client.search(
177//! SearchQuery::new("rust tutorial")
178//! .videos() // Automatically sets tbm=vid parameter
179//! ).await?;
180//!
181//! // Shopping/product search
182//! let shopping_results = client.search(
183//! SearchQuery::new("rust programming book")
184//! .shopping() // Automatically sets tbm=shop parameter
185//! .country("us")
186//! ).await?;
187//!
188//! // Local search with location
189//! let local_results = client.search(
190//! SearchQuery::new("coffee shops")
191//! .location("Austin, Texas")
192//! .limit(20)?
193//! ).await?;
194//! # Ok(())
195//! # }
196//! ```
197//!
198//! ### Advanced Query Building
199//!
200//! Leverage the fluent builder pattern for complex queries:
201//!
202//! ```rust,no_run
203//! # use serp_sdk::{SerpClient, SearchQuery};
204//! # #[tokio::main]
205//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
206//! # let client = SerpClient::builder().api_key("test").build()?;
207//! let complex_query = SearchQuery::new("site:github.com rust async")
208//! .language("en")
209//! .country("us")
210//! .device("desktop") // Desktop, tablet, or mobile
211//! .safe_search("off") // off, active, or medium
212//! .time_filter("m") // Past month
213//! .filter("0") // Include similar results
214//! .offset(10) // Start from result 10
215//! .limit(50)? // Get 50 results
216//! .custom_param("gl", "us") // Add any SerpAPI parameter
217//! .custom_param("lr", "lang_en");
218//!
219//! let results = client.search(complex_query).await?;
220//! # Ok(())
221//! # }
222//! ```
223//!
224//! ### Error Handling
225//!
226//! The SDK provides granular error types for precise error handling:
227//!
228//! ```rust,no_run
229//! use serp_sdk::{SerpClient, SearchQuery, SerpError};
230//!
231//! # #[tokio::main]
232//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
233//! # let client = SerpClient::builder().api_key("test").build()?;
234//! # let query = SearchQuery::new("test");
235//! match client.search(query).await {
236//! Ok(results) => {
237//! // Process successful results
238//! }
239//! Err(SerpError::RateLimited { retry_after }) => {
240//! // Handle rate limiting
241//! println!("Rate limited. Retry after {} seconds", retry_after);
242//! tokio::time::sleep(std::time::Duration::from_secs(retry_after)).await;
243//! // Retry the request
244//! }
245//! Err(SerpError::ApiError { code, message }) => {
246//! // Handle API errors
247//! match code {
248//! 401 => println!("Invalid API key: {}", message),
249//! 403 => println!("Access forbidden: {}", message),
250//! 404 => println!("Resource not found: {}", message),
251//! _ => println!("API error {}: {}", code, message),
252//! }
253//! }
254//! Err(SerpError::InvalidQuery(msg)) => {
255//! // Handle query validation errors
256//! println!("Invalid query parameters: {}", msg);
257//! }
258//! Err(SerpError::NetworkError(e)) => {
259//! // Handle network-level errors
260//! println!("Network error: {}", e);
261//! }
262//! Err(e) => {
263//! // Handle other errors
264//! println!("Unexpected error: {}", e);
265//! }
266//! }
267//! # Ok(())
268//! # }
269//! ```
270//!
271//! ### Custom Retry Policies
272//!
273//! Configure retry behavior for resilient applications:
274//!
275//! ```rust,no_run
276//! use serp_sdk::{SerpClient, RetryPolicy};
277//! use std::time::Duration;
278//!
279//! let retry_policy = RetryPolicy::new(5) // Max 5 retries
280//! .with_base_delay(Duration::from_millis(200)) // Start with 200ms delay
281//! .with_max_delay(Duration::from_secs(30)) // Cap at 30 seconds
282//! .with_jitter(true); // Add randomization
283//!
284//! let client = SerpClient::builder()
285//! .api_key("your-key")
286//! .timeout(Duration::from_secs(60))
287//! .retry_policy(retry_policy)
288//! .build()?;
289//! # Ok::<(), serp_sdk::SerpError>(())
290//! ```
291//!
292//! ### Working with Response Data
293//!
294//! The SDK provides strongly-typed response structures for all result types:
295//!
296//! ```rust,no_run
297//! # use serp_sdk::{SerpClient, SearchQuery};
298//! # #[tokio::main]
299//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
300//! # let client = SerpClient::builder().api_key("test").build()?;
301//! let results = client.search(SearchQuery::new("rust")).await?;
302//!
303//! // Access search metadata
304//! println!("Search ID: {}", results.search_metadata.id);
305//! if let Some(time) = results.search_metadata.total_time_taken {
306//! println!("Time taken: {:.2}s", time);
307//! }
308//!
309//! // Access knowledge graph
310//! if let Some(kg) = results.knowledge_graph {
311//! println!("Knowledge Graph: {}", kg.title);
312//! if let Some(desc) = kg.description {
313//! println!("Description: {}", desc);
314//! }
315//! }
316//!
317//! // Access answer box
318//! if let Some(answer) = results.answer_box {
319//! println!("Answer: {:?}", answer.answer);
320//! }
321//!
322//! // Access related searches
323//! if let Some(related) = results.related_searches {
324//! println!("Related searches:");
325//! for search in related {
326//! // Handle both simple and block formats
327//! match search {
328//! serp_sdk::response::RelatedSearch::Simple { query, .. } => {
329//! println!(" - {}", query);
330//! }
331//! serp_sdk::response::RelatedSearch::Block { items, .. } => {
332//! for item in items {
333//! if let Some(name) = item.name {
334//! println!(" - {}", name);
335//! }
336//! }
337//! }
338//! }
339//! }
340//! }
341//!
342//! // Access pagination information
343//! if let Some(pagination) = results.pagination {
344//! println!("Current page: {:?}", pagination.current);
345//! if let Some(next) = pagination.next {
346//! println!("Next page: {}", next);
347//! }
348//! }
349//! # Ok(())
350//! # }
351//! ```
352//!
353//! ## Performance Considerations
354//!
355//! ### Connection Pooling
356//!
357//! The SDK automatically manages connection pooling for optimal performance:
358//!
359//! ```rust,no_run
360//! # use serp_sdk::SerpClient;
361//! // The client reuses connections efficiently
362//! let client = SerpClient::builder()
363//! .api_key("your-key")
364//! .max_connections(100) // Maximum concurrent connections
365//! .connection_timeout(std::time::Duration::from_secs(10))
366//! .build()?;
367//! # Ok::<(), serp_sdk::SerpError>(())
368//! ```
369//!
370//! ### Batch Processing
371//!
372//! Process multiple queries efficiently:
373//!
374//! ```rust,no_run
375//! # use serp_sdk::{SerpClient, SearchQuery};
376//! # #[tokio::main]
377//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
378//! # let client = SerpClient::builder().api_key("test").build()?;
379//! use futures::future::join_all;
380//!
381//! let queries = vec![
382//! SearchQuery::new("rust async"),
383//! SearchQuery::new("rust web framework"),
384//! SearchQuery::new("rust database"),
385//! ];
386//!
387//! // Execute queries concurrently
388//! let futures = queries.into_iter()
389//! .map(|q| client.search(q))
390//! .collect::<Vec<_>>();
391//!
392//! let results = join_all(futures).await;
393//!
394//! for result in results {
395//! match result {
396//! Ok(data) => println!("Got {} results",
397//! data.organic_results.as_ref().map_or(0, |r| r.len())),
398//! Err(e) => eprintln!("Query failed: {}", e),
399//! }
400//! }
401//! # Ok(())
402//! # }
403//! ```
404//!
405//! ## Integration Examples
406//!
407//! ### MCP (Model Context Protocol) Integration
408//!
409//! The SDK is designed to work seamlessly with AI agents and LLM tools:
410//!
411//! ```rust,no_run
412//! # use serp_sdk::{SerpClient, SearchQuery};
413//! use serde_json::json;
414//!
415//! # #[tokio::main]
416//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
417//! # let client = SerpClient::builder().api_key("test").build()?;
418//! // Convert search results to MCP-compatible format
419//! async fn search_for_mcp(
420//! client: &SerpClient,
421//! query: String,
422//! ) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
423//! let results = client.search(SearchQuery::new(&query)).await?;
424//!
425//! Ok(json!({
426//! "results": results.organic_results.unwrap_or_default()
427//! .iter()
428//! .map(|r| json!({
429//! "title": r.title,
430//! "url": r.link,
431//! "snippet": r.snippet
432//! }))
433//! .collect::<Vec<_>>(),
434//! "metadata": {
435//! "total_time": results.search_metadata.total_time_taken,
436//! "query": query
437//! }
438//! }))
439//! }
440//! # Ok(())
441//! # }
442//! ```
443//!
444//! ## Troubleshooting
445//!
446//! ### Common Issues
447//!
448//! 1. **Rate Limiting**: Implement exponential backoff or use the built-in retry policy
449//! 2. **Timeout Errors**: Increase the timeout duration for slow queries
450//! 3. **Invalid API Key**: Ensure your API key is correctly set and has sufficient quota
451//! 4. **Deserialization Errors**: Update to the latest SDK version for API compatibility
452//!
453//! ### Debug Logging
454//!
455//! Enable detailed logging for troubleshooting:
456//!
457//! ```rust,no_run
458//! use tracing_subscriber;
459//!
460//! // Enable debug logging
461//! tracing_subscriber::fmt()
462//! .with_max_level(tracing::Level::DEBUG)
463//! .init();
464//! ```
465//!
466//! ## Contributing
467//!
468//! We welcome contributions! Please see our [GitHub repository](https://github.com/RustSandbox/SerpRS)
469//! for contribution guidelines and development setup.
470//!
471//! ## License
472//!
473//! This project is dual-licensed under MIT and Apache-2.0 licenses.
474//!
475//! ## See Also
476//!
477//! - [`client`]: HTTP client implementation and configuration
478//! - [`query`]: Query builder and search parameters
479//! - [`response`]: Response structures and deserialization
480//! - [`streaming`]: Async streaming support for pagination
481//! - [`error`]: Error types and handling
482//! - [`retry`]: Retry policies and backoff strategies
483
484#![warn(missing_docs)]
485#![warn(rustdoc::missing_crate_level_docs)]
486#![warn(clippy::all)]
487#![deny(unsafe_code)]
488
489/// HTTP client module providing the main SerpAPI client implementation.
490///
491/// This module contains the [`SerpClient`](client::SerpClient) struct which is the primary
492/// interface for interacting with the SerpAPI service. It provides methods for executing
493/// searches, handling retries, and managing HTTP connections efficiently.
494///
495/// # Examples
496///
497/// ```rust,no_run
498/// use serp_sdk::client::SerpClient;
499///
500/// let client = SerpClient::builder()
501/// .api_key("your-api-key")
502/// .build()
503/// .expect("Failed to create client");
504/// ```
505pub mod client;
506
507/// Comprehensive error types for all SDK operations.
508///
509/// This module defines the [`SerpError`] enum and related types that
510/// represent all possible error conditions in the SDK. It provides detailed error information
511/// with actionable messages for debugging and error recovery.
512pub mod error;
513
514/// Fluent query builder for constructing search requests.
515///
516/// The [`SearchQuery`](query::SearchQuery) builder provides a type-safe, ergonomic API
517/// for constructing complex search queries with compile-time validation where possible.
518pub mod query;
519
520/// Strongly-typed response structures for SerpAPI results.
521///
522/// This module contains all the response types returned by SerpAPI, including organic results,
523/// knowledge graphs, answer boxes, and specialized result types for images, videos, news, etc.
524pub mod response;
525
526/// Retry policies with configurable backoff strategies.
527///
528/// The [`RetryPolicy`](retry::RetryPolicy) struct allows fine-grained control over retry
529/// behavior, including exponential backoff, jitter, and maximum delay configuration.
530pub mod retry;
531
532/// Streaming support for paginated search results.
533///
534/// This module provides async stream implementations for efficiently processing large result
535/// sets through pagination, with built-in rate limiting and error handling.
536pub mod streaming;
537
538// Re-export main types for convenience
539pub use client::{SerpClient, SerpClientBuilder};
540pub use error::{SerpError, SerpResult};
541pub use query::{SearchQuery, SearchQueryBuilder};
542pub use response::SearchResults;
543pub use retry::RetryPolicy;
544pub use streaming::StreamConfig;