We welcome contributions to the Serper SDK! This document provides guidelines for contributing to the project.
- Code of Conduct
- Getting Started
- Development Setup
- Making Changes
- Testing
- Documentation
- Submitting Changes
- Code Style
This project adheres to a code of conduct adapted from the Contributor Covenant. By participating, you are expected to uphold this code.
- Be respectful and inclusive of differing viewpoints and experiences
- Be collaborative and constructive in discussions
- Focus on what is best for the community and the project
- Show empathy towards other community members
- Fork the repository on GitHub
- Clone your fork locally
- Create a topic branch from
main - Make your changes
- Test your changes
- Submit a pull request
- Rust 1.83+ (we use Rust Edition 2024)
- Git
- A Serper API key for testing (optional, required only for integration tests)
# Clone your fork
git clone https://github.com/YOUR_USERNAME/serper.git
cd serper
# Install dependencies
cargo build
# Run tests
cargo test
# Run clippy for code quality
cargo clippy -- -D warnings
# Format code
cargo fmt
# Check documentation
cargo doc --openFor testing with the real API (optional):
export SERPER_API_KEY="your-api-key-here"Use descriptive branch names:
feature/add-new-endpoint- for new featuresfix/http-timeout-issue- for bug fixesdocs/improve-readme- for documentationrefactor/simplify-error-handling- for refactoring
Follow conventional commit format:
type(scope): description
[optional body]
[optional footer]
Examples:
feat(search): add concurrent search supportfix(http): resolve timeout configuration bugdocs(readme): update installation instructionsrefactor(core): simplify error type hierarchy
# Run all tests
cargo test
# Run specific module tests
cargo test core::
cargo test search::
cargo test http::
# Run with output
cargo test -- --nocapture
# Run integration tests (requires API key)
SERPER_API_KEY="your-key" cargo test --test integration- Unit tests are required for all new functionality
- Integration tests should be added for API interactions
- Documentation tests must pass (
cargo test --doc) - All tests must pass on CI
The project automatically generates documentation on every push to main:
# Generate documentation locally
cargo doc --open
# Generate with private items (for development)
cargo doc --document-private-items --open
# Test documentation examples
cargo test --doc- All public APIs must have comprehensive documentation
- Include usage examples in doc comments
- Document error conditions and edge cases
- Keep examples up-to-date with the current API
The documentation is automatically deployed to GitHub Pages at https://rustsandbox.github.io/serper/
- Place unit tests in the same file as the code being tested (in
#[cfg(test)]modules) - Use descriptive test names that explain what is being tested
- Test both success and failure cases
- Mock external dependencies where appropriate
Example:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_search_query_validation_empty_string() {
let result = SearchQuery::new("".to_string());
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), SerperError::Validation { .. }));
}
#[test]
fn test_search_query_valid_construction() {
let query = SearchQuery::new("rust programming".to_string()).unwrap();
assert_eq!(query.q, "rust programming");
assert_eq!(query.location, None);
}
}- Public APIs must be documented with
///comments - Examples should be provided for complex functionality
- Module-level documentation should explain the module's purpose
- README updates are required for significant changes
/// Searches for the given query using the Serper API
///
/// # Arguments
///
/// * `query` - The search query to execute
///
/// # Returns
///
/// Returns a `Result` containing the search response or an error
///
/// # Examples
///
/// ```rust
/// use serper_sdk::{SearchService, SearchQuery};
///
/// # tokio_test::block_on(async {
/// let service = SearchService::new("api-key".to_string())?;
/// let query = SearchQuery::new("rust programming".to_string())?;
/// let response = service.search(&query).await?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// # });
/// ```
///
/// # Errors
///
/// Returns `SerperError::InvalidApiKey` if the API key is invalid
/// Returns `SerperError::Request` for network-related errors
pub async fn search(&self, query: &SearchQuery) -> Result<SearchResponse> {
// implementation
}- Ensure tests pass: Run
cargo testandcargo clippy - Update documentation: Update relevant docs and examples
- Update CHANGELOG: Add your changes to the unreleased section
- Create pull request: Use the provided template
- Address feedback: Respond to review comments promptly
## Description
Brief description of what this PR does.
## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation update
## Testing
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] I have tested this manually
## Checklist
- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have updated the documentation accordingly
- [ ] My changes generate no new warnings
- [ ] I have updated CHANGELOG.mdWe follow standard Rust conventions:
- Use
cargo fmtfor formatting - Follow Clippy suggestions (
cargo clippy -- -D warnings) - Use meaningful variable names
- Prefer explicit types when it improves clarity
- Write self-documenting code
/// Module documentation
///
/// Explanation of what this module does and how it fits into the overall architecture.
// Imports
use std::collections::HashMap;
use crate::core::Result;
// Constants
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
// Types
#[derive(Debug, Clone)]
pub struct MyStruct {
// fields
}
// Implementations
impl MyStruct {
/// Constructor
pub fn new() -> Self { }
/// Public methods
pub fn do_something(&self) -> Result<()> { }
/// Private methods
fn internal_helper(&self) { }
}
// Trait implementations
impl Default for MyStruct { }
// Tests
#[cfg(test)]
mod tests { }- Use
Result<T, SerperError>for fallible operations - Provide context in error messages
- Convert errors at module boundaries
- Document error conditions in function docs
- Use
async/awaitconsistently - Avoid blocking in async code
- Use
tokioprimitives for concurrency - Handle cancellation appropriately
- Single responsibility: Each module should have a clear, focused purpose
- Clean interfaces: Modules should interact through well-defined APIs
- Dependency direction: Higher-level modules depend on lower-level ones
- Loose coupling: Minimize dependencies between modules
- Centralized errors: Use the
core::SerperErrorenum for all errors - Error propagation: Use
?operator for clean error propagation - Context preservation: Include relevant context in error messages
- Recovery strategies: Implement retry logic where appropriate
- Async first: Use async/await for I/O operations
- Connection reuse: Reuse HTTP connections when possible
- Memory efficiency: Avoid unnecessary allocations
- Benchmark changes: Profile performance-critical code
We follow Semantic Versioning:
- MAJOR: Breaking changes
- MINOR: New features (backward compatible)
- PATCH: Bug fixes (backward compatible)
- Update version in
Cargo.toml - Update CHANGELOG.md with release notes
- Run full test suite (
cargo test --all-features) - Check documentation (
cargo doc --no-deps) - Create release tag (
git tag v0.x.y) - Publish to crates.io (
cargo publish) - Create GitHub release with changelog
- Issues: Use GitHub issues for bug reports and feature requests
- Discussions: Use GitHub discussions for questions and ideas
- Security: Email security@remolab.fr for security-related issues
Thank you for contributing to the Serper SDK! 🚀