The http module provides HTTP transport layer abstractions and high-level client functionality for interacting with the Serper API. It separates transport concerns from business logic, enabling easy testing, middleware insertion, and transport layer customization. The module is designed with clean abstractions that make it easy to swap underlying HTTP implementations or add cross-cutting concerns like retry logic, rate limiting, or request logging.
Configuration for HTTP transport behavior.
pub struct TransportConfig {
pub timeout: Duration,
pub default_headers: HashMap<String, String>,
pub user_agent: String,
}Methods:
-
new() -> Self- Creates a new transport configuration with default values
- Returns:
TransportConfigwith 30s timeout, JSON content-type header - Default timeout: 30 seconds
- Default headers:
Content-Type: application/json - Default user agent:
serper-sdk/{version}
-
with_timeout(self, timeout: Duration) -> Self- Sets the request timeout (builder pattern)
- Parameters:
timeout- Request timeout duration - Returns: Self for method chaining
-
with_header(self, key: String, value: String) -> Self- Adds a default header (builder pattern)
- Parameters:
key- Header namevalue- Header value
- Returns: Self for method chaining
-
with_user_agent(self, user_agent: String) -> Self- Sets the user agent (builder pattern)
- Parameters:
user_agent- User agent string - Returns: Self for method chaining
Low-level HTTP transport implementation handling request/response operations.
pub struct HttpTransport { /* private fields */ }Methods:
-
new() -> Result<Self>- Creates a new HTTP transport with default configuration
- Returns:
Result<HttpTransport, SerperError> - Errors: Returns error if HTTP client creation fails
-
with_config(config: TransportConfig) -> Result<Self>- Creates a new HTTP transport with custom configuration
- Parameters:
config- Transport configuration - Returns:
Result<HttpTransport, SerperError> - Errors: Returns error if HTTP client creation fails
-
post_json<T: Serialize>(&self, url: &str, api_key: &ApiKey, body: &T) -> Result<Response>- Makes a POST request with JSON body
- Parameters:
url- The request URLapi_key- API key for authenticationbody- Request body (must implement Serialize)
- Returns:
Result<reqwest::Response, SerperError> - Errors: Returns Request error for HTTP failures, Api error for non-success status codes
-
get(&self, url: &str, api_key: &ApiKey) -> Result<Response>- Makes a GET request
- Parameters:
url- The request URLapi_key- API key for authentication
- Returns:
Result<reqwest::Response, SerperError> - Errors: Returns Request error for HTTP failures, Api error for non-success status codes
-
parse_json<T>(&self, response: Response) -> Result<T>- Parses a response as JSON
- Parameters:
response- HTTP response to parse - Returns:
Result<T, SerperError>where T implements DeserializeOwned - Errors: Returns Request error if JSON parsing fails
-
config(&self) -> &TransportConfig- Gets the current transport configuration
- Returns: Reference to the transport configuration
Builder for creating HTTP transports with custom configuration.
pub struct HttpTransportBuilder { /* private fields */ }Methods:
-
new() -> Self- Creates a new transport builder
- Returns:
HttpTransportBuilderinstance
-
timeout(self, timeout: Duration) -> Self- Sets the request timeout (builder pattern)
- Parameters:
timeout- Request timeout duration - Returns: Self for method chaining
-
header(self, key: impl Into<String>, value: impl Into<String>) -> Self- Adds a default header (builder pattern)
- Parameters:
key- Header namevalue- Header value
- Returns: Self for method chaining
-
user_agent(self, user_agent: impl Into<String>) -> Self- Sets the user agent (builder pattern)
- Parameters:
user_agent- User agent string - Returns: Self for method chaining
-
build(self) -> Result<HttpTransport>- Builds the HTTP transport
- Returns:
Result<HttpTransport, SerperError> - Errors: Returns error if transport creation fails
High-level HTTP client combining transport with Serper API-specific operations.
pub struct SerperHttpClient { /* private fields */ }Methods:
-
new(api_key: ApiKey) -> Result<Self>- Creates a new HTTP client with the specified API key
- Parameters:
api_key- The Serper API key - Returns:
Result<SerperHttpClient, SerperError> - Errors: Returns error if client creation fails
-
with_config(api_key: ApiKey, base_url: BaseUrl, config: TransportConfig) -> Result<Self>- Creates a new HTTP client with custom configuration
- Parameters:
api_key- The Serper API keybase_url- Custom base URL for the APIconfig- Transport configuration
- Returns:
Result<SerperHttpClient, SerperError> - Errors: Returns error if client creation fails
-
search(&self, query: &SearchQuery) -> Result<SearchResponse>- Executes a search query
- Parameters:
query- The search query to execute - Returns:
Result<SearchResponse, SerperError> - Errors: Returns validation error for invalid queries, network errors, or API errors
-
search_multiple(&self, queries: &[SearchQuery]) -> Result<Vec<SearchResponse>>- Executes multiple search queries in sequence
- Parameters:
queries- Array of search queries to execute - Returns:
Result<Vec<SearchResponse>, SerperError> - Errors: Returns error on first failed request
-
search_concurrent(&self, queries: &[SearchQuery], max_concurrent: usize) -> Result<Vec<SearchResponse>>- Executes multiple search queries concurrently
- Parameters:
queries- Array of search queries to executemax_concurrent- Maximum number of concurrent requests
- Returns:
Result<Vec<SearchResponse>, SerperError> - Errors: Returns error if any request fails
-
api_key(&self) -> &ApiKey- Gets the API key (for debugging/logging purposes)
- Returns: Reference to the API key
-
base_url(&self) -> &BaseUrl- Gets the base URL
- Returns: Reference to the base URL
-
transport_config(&self) -> &TransportConfig- Gets the transport configuration
- Returns: Reference to the transport configuration
Builder for creating HTTP clients with custom configuration.
pub struct SerperHttpClientBuilder { /* private fields */ }Methods:
-
new() -> Self- Creates a new HTTP client builder
- Returns:
SerperHttpClientBuilderinstance
-
api_key(self, api_key: ApiKey) -> Self- Sets the API key (builder pattern)
- Parameters:
api_key- The API key - Returns: Self for method chaining
-
base_url(self, base_url: BaseUrl) -> Self- Sets the base URL (builder pattern)
- Parameters:
base_url- The base URL - Returns: Self for method chaining
-
timeout(self, timeout: Duration) -> Self- Sets the request timeout (builder pattern)
- Parameters:
timeout- Timeout duration - Returns: Self for method chaining
-
header(self, key: impl Into<String>, value: impl Into<String>) -> Self- Adds a default header (builder pattern)
- Parameters:
key- Header namevalue- Header value
- Returns: Self for method chaining
-
build(self) -> Result<SerperHttpClient>- Builds the HTTP client
- Returns:
Result<SerperHttpClient, SerperError> - Errors: Returns error if API key is missing or client creation fails
use serper_sdk::http::transport::{HttpTransport, TransportConfig};
use serper_sdk::core::types::ApiKey;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create transport with custom configuration
let config = TransportConfig::new()
.with_timeout(Duration::from_secs(60))
.with_header("Custom-Header".to_string(), "value".to_string())
.with_user_agent("my-app/1.0".to_string());
let transport = HttpTransport::with_config(config)?;
let api_key = ApiKey::new("your-api-key".to_string())?;
// Make a request
let response = transport.post_json(
"https://google.serper.dev/search",
&api_key,
&serde_json::json!({"q": "test query"})
).await?;
// Parse response
let search_response: SearchResponse = transport.parse_json(response).await?;
Ok(())
}use serper_sdk::http::client::SerperHttpClientBuilder;
use serper_sdk::core::types::{ApiKey, BaseUrl};
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = ApiKey::new("your-api-key".to_string())?;
let base_url = BaseUrl::new("https://custom-proxy.com".to_string());
let client = SerperHttpClientBuilder::new()
.api_key(api_key)
.base_url(base_url)
.timeout(Duration::from_secs(90))
.header("X-Custom", "value")
.build()?;
// Use the client
let query = SearchQuery::new("test".to_string())?;
let response = client.search(&query).await?;
Ok(())
}use serper_sdk::http::client::SerperHttpClient;
use serper_sdk::search::query::SearchQuery;
use serper_sdk::core::types::ApiKey;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = ApiKey::new("your-api-key".to_string())?;
let client = SerperHttpClient::new(api_key)?;
let queries = vec![
SearchQuery::new("Rust programming".to_string())?,
SearchQuery::new("Python programming".to_string())?,
SearchQuery::new("JavaScript programming".to_string())?,
];
// Execute up to 2 searches concurrently
let results = client.search_concurrent(&queries, 2).await?;
for (i, response) in results.iter().enumerate() {
println!("Query {}: {} results", i + 1, response.organic_count());
}
Ok(())
}use serper_sdk::http::transport::HttpTransportBuilder;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let transport = HttpTransportBuilder::new()
.timeout(Duration::from_secs(120))
.header("X-Request-ID", "12345")
.header("X-Client-Version", "1.2.3")
.user_agent("MyApp/1.0 (contact@example.com)")
.build()?;
// Transport is ready to use
let config = transport.config();
println!("Timeout: {:?}", config.timeout);
println!("User agent: {}", config.user_agent);
Ok(())
}use serper_sdk::http::client::SerperHttpClient;
use serper_sdk::core::{SerperError, types::ApiKey};
#[tokio::main]
async fn main() {
let api_key = ApiKey::new("your-api-key".to_string()).unwrap();
let client = SerperHttpClient::new(api_key).unwrap();
let query = SearchQuery::new("test".to_string()).unwrap();
match client.search(&query).await {
Ok(response) => {
println!("Success: {} results", response.organic_count());
},
Err(SerperError::Request(e)) => {
println!("Network error: {}", e);
},
Err(SerperError::Api { message }) => {
println!("API error: {}", message);
},
Err(SerperError::Json(e)) => {
println!("JSON parsing error: {}", e);
},
Err(e) => {
println!("Other error: {}", e);
}
}
}core::types- For ApiKey, BaseUrl typescore::error- For SerperError and Result typessearch::query- For SearchQuery typesearch::response- For SearchResponse type and ResponseParser
reqwest- HTTP client implementationserde- For serialization traitstokio- For async runtime and synchronization primitivesstd::time- For Duration typestd::collections::HashMap- For headers storage
- Separation of Concerns: Transport layer separated from business logic
- Configurability: Comprehensive configuration options with sane defaults
- Error Transparency: Clear error propagation and categorization
- Async First: All operations are async with proper cancellation support
- Testability: Clean interfaces for mocking and testing
- Performance: Connection reuse and concurrent execution support
All HTTP module types are designed for concurrent use:
HttpTransport:Send + Sync- Can be shared across tasksSerperHttpClient:Send + Sync- Internal cloning for concurrent operationsTransportConfig:Send + Sync- Immutable configuration- All builder types:
Send- Can be moved between tasks
The HTTP module provides comprehensive error handling:
SerperError::Request- Network/transport failuresSerperError::Api- HTTP error status codes (4xx, 5xx)SerperError::Json- Response parsing failuresSerperError::Validation- Request validation failures
- API errors include HTTP status codes and descriptions
- Network errors preserve underlying reqwest error information
- JSON errors provide parsing context
- No automatic retries at the HTTP layer (handled at higher levels)
- Transient errors are distinguished from permanent failures
- Proper error classification enables retry logic in consumers
- HTTP clients reuse connections automatically
- Connection pools are managed by underlying reqwest
- Keep-alive is enabled by default
- Built-in support for concurrent operations
- Semaphore-based concurrency limiting
- Shared transport instances for efficiency
- Streaming JSON parsing where possible
- Efficient header management with HashMap storage
- Minimal copying of request/response data
The HTTP module includes comprehensive tests covering:
- Transport configuration and creation
- HTTP request/response handling
- Error scenarios and status codes
- Concurrent operation behavior
- Builder pattern functionality
Run tests with:
cargo test http::The module design enables easy testing with mock servers:
use mockito::Server;
#[tokio::test]
async fn test_api_error_handling() {
let mut server = Server::new_async().await;
let mock = server.mock("POST", "/search")
.with_status(429) // Rate limited
.with_body("Rate limit exceeded")
.create_async()
.await;
let api_key = ApiKey::new("test-key".to_string()).unwrap();
let base_url = BaseUrl::new(server.url());
let client = SerperHttpClient::with_config(
api_key,
base_url,
TransportConfig::new()
).unwrap();
let query = SearchQuery::new("test".to_string()).unwrap();
let result = client.search(&query).await;
assert!(matches!(result, Err(SerperError::Api { .. })));
mock.assert_async().await;
}