# Contributing to Caddy-RS Thank you for your interest in contributing to Caddy-RS! This document provides guidelines and information for contributors. ## Table of Contents - [Code of Conduct](#code-of-conduct) - [How to Contribute](#how-to-contribute) - [Development Setup](#development-setup) - [Making Changes](#making-changes) - [Pull Request Process](#pull-request-process) - [Coding Standards](#coding-standards) - [Testing Guidelines](#testing-guidelines) - [Documentation](#documentation) - [Issue Reporting](#issue-reporting) ## Code of Conduct This project adheres to a code of conduct adapted from the [Contributor Covenant](https://www.contributor-covenant.org/). By participating, you are expected to uphold this code. ### Our Pledge - **Be welcoming and inclusive** to all contributors regardless of experience level - **Be respectful** in all communications and code reviews - **Be constructive** when providing feedback - **Focus on what is best** for the community and the project ## How to Contribute There are many ways to contribute to Caddy-RS: ### Types of Contributions 1. **Bug Reports**: Help identify and fix issues 2. **Feature Requests**: Suggest new functionality 3. **Code Contributions**: Implement features, fix bugs, improve performance 4. **Documentation**: Improve or add documentation 5. **Testing**: Write tests, perform manual testing 6. **Performance**: Optimize code, identify bottlenecks 7. **Security**: Identify and fix security issues ### Getting Started 1. **Look for good first issues**: Check issues labeled `good-first-issue` or `help-wanted` 2. **Check existing issues**: Avoid duplicate work by checking existing issues and PRs 3. **Join discussions**: Participate in issue discussions to understand requirements 4. **Start small**: Begin with small contributions to understand the codebase ## Development Setup ### Prerequisites - Rust 1.75+ with 2024 edition support - Git - A code editor (VS Code with rust-analyzer recommended) ### Setup Steps 1. **Fork the repository** on GitHub 2. **Clone your fork**: ```bash git clone https://github.com/your-username/caddy-rs.git cd caddy-rs ``` 3. **Add upstream remote**: ```bash git remote add upstream https://github.com/original-owner/caddy-rs.git ``` 4. **Install dependencies and build**: ```bash cargo build cargo test ``` ### Development Tools Install these tools for better development experience: ```bash cargo install cargo-watch # Auto-reload during development cargo install cargo-edit # Easy dependency management cargo install cargo-audit # Security vulnerability scanning cargo install cargo-tarpaulin # Code coverage ``` ## Making Changes ### Before You Start 1. **Create an issue** if one doesn't exist for your change 2. **Discuss the approach** in the issue before implementing 3. **Check for existing work** to avoid duplication ### Development Workflow 1. **Create a feature branch**: ```bash git checkout -b feature/your-feature-name ``` 2. **Make your changes**: - Follow the [coding standards](#coding-standards) - Write tests for new functionality - Update documentation as needed 3. **Test your changes**: ```bash cargo test # Run all tests cargo clippy -- -D warnings # Check for linting issues cargo fmt # Format code ``` 4. **Commit your changes**: ```bash git add . git commit -m "feat: add new feature description" ``` ### Commit Message Convention We use conventional commits for clear, semantic commit messages: ``` (): [optional body] [optional footer] ``` **Types:** - `feat`: New feature - `fix`: Bug fix - `docs`: Documentation changes - `style`: Code style changes (formatting, etc.) - `refactor`: Code refactoring - `test`: Adding or updating tests - `chore`: Maintenance tasks **Examples:** ``` feat(proxy): add health check support fix(config): handle missing configuration file gracefully docs(api): update reverse proxy configuration examples test(middleware): add unit tests for CORS middleware ``` ## Pull Request Process ### Before Submitting Ensure your PR meets these criteria: - [ ] **Tests pass**: `cargo test` - [ ] **Code is formatted**: `cargo fmt` - [ ] **No linting warnings**: `cargo clippy -- -D warnings` - [ ] **Documentation updated**: If you changed APIs or added features - [ ] **Changelog updated**: Add entry to `CHANGELOG.md` if needed - [ ] **Performance impact considered**: No significant performance regression ### Submitting the PR 1. **Push to your fork**: ```bash git push origin feature/your-feature-name ``` 2. **Create pull request** on GitHub with: - **Clear title** describing the change - **Detailed description** explaining what and why - **Reference to related issues** using `#issue-number` - **Testing instructions** for reviewers - **Screenshots or examples** if UI-related ### PR Review Process 1. **Automated checks** will run (tests, linting, etc.) 2. **Code review** by maintainers and other contributors 3. **Address feedback** by making additional commits 4. **Final approval** and merge by maintainers ### Review Guidelines for Contributors When reviewing others' PRs: - **Be kind and constructive** in feedback - **Focus on the code**, not the person - **Explain your suggestions** with reasoning - **Approve when ready** or request changes with specific feedback - **Test the changes** if possible ## Coding Standards ### Rust Style Guidelines Follow standard Rust conventions: ```rust // Use snake_case for functions and variables fn handle_request() -> Result<()> { } let response_body = String::new(); // Use PascalCase for types and traits struct ProxyService; trait Middleware; enum HandlerType; // Use SCREAMING_SNAKE_CASE for constants const MAX_RETRY_COUNT: u32 = 3; const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30); ``` ### Code Organization ```rust // Order imports logically use std::collections::HashMap; use std::time::Duration; use anyhow::Result; use serde::{Deserialize, Serialize}; use tokio::net::TcpListener; use crate::config::Config; use crate::proxy::ProxyService; // Group related functionality impl ProxyService { // Public methods first pub async fn new(config: Config) -> Result { } pub async fn handle_request(&self) -> Result<()> { } // Private methods last async fn select_upstream(&self) -> Result<()> { } } ``` ### Error Handling Use `Result` types consistently: ```rust use anyhow::{Context, Result}; // Good: Propagate errors with context pub async fn load_config(path: &str) -> Result { let content = tokio::fs::read_to_string(path) .await .context("Failed to read configuration file")?; let config = serde_json::from_str(&content) .context("Failed to parse configuration")?; Ok(config) } // Avoid: Unwrapping or ignoring errors let config = serde_json::from_str(&content).unwrap(); // Don't do this ``` ### Async Code Follow async best practices: ```rust // Good: Use async/await throughout pub async fn proxy_request(&self, req: Request) -> Result { let upstream = self.select_upstream().await?; let response = self.client.request(upstream, req).await?; Ok(response) } // Avoid: Blocking calls in async context std::thread::sleep(Duration::from_secs(1)); // Don't do this tokio::time::sleep(Duration::from_secs(1)).await; // Do this instead ``` ### Documentation Document public APIs: ```rust /// Selects an upstream server using the configured load balancing algorithm. /// /// This method applies the load balancing policy to choose from available /// upstream servers. It considers server health and current load when making /// the selection. /// /// # Arguments /// /// * `upstreams` - A slice of available upstream servers /// * `policy` - The load balancing policy to apply /// /// # Returns /// /// Returns a reference to the selected upstream server, or an error if /// no healthy upstreams are available. /// /// # Examples /// /// ```rust /// let upstream = load_balancer.select_upstream(&upstreams, &policy)?; /// println!("Selected: {}", upstream.dial); /// ``` pub fn select_upstream<'a>( &self, upstreams: &'a [Upstream], policy: &LoadBalancingPolicy, ) -> Result<&'a Upstream> { // Implementation } ``` ## Testing Guidelines ### Test Categories 1. **Unit Tests**: Test individual functions and modules 2. **Integration Tests**: Test component interactions 3. **End-to-End Tests**: Test complete workflows ### Unit Test Examples ```rust #[cfg(test)] mod tests { use super::*; use tokio_test; #[tokio::test] async fn test_round_robin_selection() { let load_balancer = LoadBalancer::new(); let upstreams = vec![ Upstream { dial: "backend1:8080".to_string() }, Upstream { dial: "backend2:8080".to_string() }, ]; let policy = LoadBalancingPolicy::RoundRobin; let first = load_balancer.select_upstream(&upstreams, &policy).unwrap(); let second = load_balancer.select_upstream(&upstreams, &policy).unwrap(); assert_ne!(first.dial, second.dial); } #[test] fn test_config_parsing() { let config_json = r#" { "listen": [":8080"], "routes": [] } "#; let config: ServerConfig = serde_json::from_str(config_json).unwrap(); assert_eq!(config.listen, vec![":8080"]); } } ``` ### Testing Async Code ```rust #[tokio::test] async fn test_async_function() { let service = ProxyService::new(test_config()).await.unwrap(); let request = test_request(); let response = service.handle_request(request).await; assert!(response.is_ok()); } ``` ### Test Organization - Place unit tests in the same file using `#[cfg(test)]` - Place integration tests in separate files in `tests/` directory - Use descriptive test names that explain what is being tested - Group related tests in modules ## Documentation ### Types of Documentation 1. **Code Documentation**: Rustdoc comments in source code 2. **API Documentation**: Configuration and usage reference 3. **Architecture Documentation**: System design and decisions 4. **User Documentation**: README, getting started guides ### Documentation Standards - **Keep examples up to date** with the current API - **Include error cases** in documentation - **Use clear, concise language** - **Provide complete examples** that work without modification ### Updating Documentation When making changes, update relevant documentation: - **Rustdoc comments** for new or changed APIs - **API documentation** in `docs/api.md` for configuration changes - **README.md** for new features or usage changes - **Architecture documentation** for design changes ## Issue Reporting ### Before Reporting 1. **Search existing issues** to avoid duplicates 2. **Check the documentation** to ensure it's actually a bug 3. **Try the latest version** to see if it's already fixed ### Bug Reports Include this information in bug reports: ```markdown ## Bug Description A clear description of what the bug is. ## Steps to Reproduce 1. Create config file with... 2. Run caddy-rs with... 3. Send request to... 4. See error ## Expected Behavior What you expected to happen. ## Actual Behavior What actually happened. ## Environment - OS: (e.g., Ubuntu 20.04) - Rust version: (e.g., 1.75.0) - Caddy-RS version: (e.g., 0.1.0) ## Configuration ```json { "your": "configuration", "file": "here" } ``` ## Logs ``` Relevant log output here ``` ``` ### Feature Requests Structure feature requests like this: ```markdown ## Feature Description A clear description of the feature you'd like to see. ## Use Case Describe the problem this feature would solve. ## Proposed Solution Your ideas for how this could be implemented. ## Alternatives Other ways this problem could be solved. ## Additional Context Any other context, screenshots, or examples. ``` ## Questions and Support - **Check the documentation** first (README, docs/, etc.) - **Search existing issues** for similar questions - **Create a new issue** with the "question" label - **Be specific** about what you're trying to achieve ## License By contributing to Caddy-RS, you agree that your contributions will be licensed under the same license as the project (Apache License 2.0). ## Recognition Contributors will be recognized in the project README and release notes. Significant contributors may be invited to become project maintainers. Thank you for contributing to Caddy-RS! 🦀