serp_sdk/
lib.rs

1//! # SerpAPI Rust SDK
2//!
3//! [![Crates.io](https://img.shields.io/crates/v/serp-sdk.svg)](https://crates.io/crates/serp-sdk)
4//! [![Documentation](https://docs.rs/serp-sdk/badge.svg)](https://docs.rs/serp-sdk)
5//! [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](#license)
6//! [![Build Status](https://github.com/your-org/serp-sdk/workflows/CI/badge.svg)](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;