Thank you for your interest in contributing to the project! This guide will help you get started with development, understand our workflow, and make meaningful contributions.
- Code of Conduct
- Getting Started
- Development Environment
- Project Structure
- Development Workflow
- Coding Standards
- Testing Guidelines
- Documentation
- Pull Request Process
- Release Process
- Getting Help
We are committed to providing a welcoming and inclusive environment. All contributors are expected to:
- Be respectful and considerate
- Welcome newcomers and help them get started
- Focus on constructive feedback
- Assume good intentions
- Respect differing viewpoints and experiences
- Rust: 1.85 or later (2024 edition)
- Git: For version control
- IDE: VS Code with rust-analyzer recommended
- OS: Windows, Linux, or macOS
- cargo-nextest: For running tests (
cargo install cargo-nextest)
-
Fork and Clone
git clone https://github.com/yourusername/bdrpc.git cd bdrpc -
Build the Project
cargo build
-
Run Tests
cargo nextest run
-
Run Benchmarks
cargo bench
# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install additional components
rustup component add rustfmt clippy
# Install cargo tools
cargo install cargo-nextest cargo-watch cargo-audit cargo-outdated- rust-analyzer: Rust language support
- CodeLLDB: Debugging support
- Even Better TOML: TOML file support
- Error Lens: Inline error display
- GitLens: Git integration
Create a .env file in the project root (optional):
RUST_LOG=debug
RUST_BACKTRACE=1bdrpc/
├── bdrpc/ # Core library
│ ├── src/
│ │ ├── lib.rs # Library entry point
│ │ ├── error.rs # Top-level error types
│ │ ├── observability.rs # Tracing and metrics
│ │ ├── channel/ # Channel management
│ │ ├── endpoint/ # Endpoint and handshake
│ │ ├── reconnection/ # Reconnection strategies
│ │ ├── serialization/ # Serialization formats
│ │ └── transport/ # Transport implementations
│ ├── tests/ # Integration tests
│ ├── benches/ # Benchmarks
│ ├── examples/ # Example code
│ └── Cargo.toml
├── bdrpc-macros/ # Procedural macros
│ ├── src/
│ │ ├── lib.rs # Macro entry point
│ │ ├── parse.rs # Parsing logic
│ │ └── generate.rs # Code generation
│ ├── tests/
│ └── Cargo.toml
├── docs/ # Documentation
│ ├── ADR/ # Architecture Decision Records
│ ├── dev/ # Development guides
│ └── README.md
├── Cargo.toml # Workspace configuration
├── CONTRIBUTING.md # This file
└── README.md
Key Principles:
- Each crate has a single, well-defined responsibility
- Dependencies flow downward (no circular dependencies)
- Core transport and serialization layers are independent
- Public APIs are clearly separated from internal implementation
- No
mod.rsfiles - use modern Rust module structure
- main: Stable, production-ready code
- develop: Integration branch for features
- feature/*: Feature development branches
- fix/*: Bug fix branches
- docs/*: Documentation updates
git checkout develop
git pull origin develop
git checkout -b feature/your-feature-nameFollow the Conventional Commits specification:
<type>(<scope>): <subject>
<body>
<footer>
Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, etc.)refactor: Code refactoringperf: Performance improvementstest: Adding or updating testschore: Maintenance tasks
Examples:
feat(transport): implement TCP transport with reconnection
Add TCP transport implementation with exponential backoff
reconnection strategy and configurable timeouts.
Closes #123
fix(serialization): correct frame length validation
The previous implementation didn't handle edge cases for
maximum frame size. This fix ensures proper validation.
Fixes #456
-
Pull latest changes
git checkout develop git pull origin develop
-
Create/update feature branch
git checkout -b feature/my-feature
-
Make changes and test
# Make your changes cargo fmt cargo clippy cargo nextest run -
Commit changes
git add . git commit -m "feat(module): description"
-
Push and create PR
git push origin feature/my-feature # Create PR on GitHub
We follow the Rust API Guidelines and enforce style with rustfmt.
Key Conventions:
-
Naming:
- Types:
PascalCase - Functions/variables:
snake_case - Constants:
SCREAMING_SNAKE_CASE - Lifetimes:
'a,'b, etc.
- Types:
-
Error Handling:
- Use
Result<T, E>for recoverable errors - Use
panic!only for unrecoverable errors - Provide context with error types
- Use
thiserrorfor error definitions
- Use
-
Documentation:
- All public items must have doc comments
- Include examples in doc comments
- Document panics, errors, and safety requirements
-
Safety:
- Minimize
unsafecode - Document all
unsafeblocks with safety invariants - Prefer safe abstractions
- Minimize
-
Modern Rust:
- Use Rust 2024 edition features
- No
mod.rsfiles - use modern module structure - Leverage async/await for asynchronous operations
# Format all code
cargo fmt
# Check formatting without modifying
cargo fmt -- --check# Run clippy
cargo clippy -- -D warnings
# Run clippy with all features
cargo clippy --all-features -- -D warnings/// Sends a message through the channel.
///
/// # Arguments
///
/// * `message` - The message to send (must be serializable)
///
/// # Returns
///
/// Returns `Ok(())` on success, or an error if the send fails.
///
/// # Errors
///
/// Returns `ChannelError::Closed` if the channel is closed.
/// Returns `ChannelError::SerializationError` if serialization fails.
///
/// # Examples
///
/// ```
/// use bdrpc::Channel;
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let channel = Channel::new()?;
/// channel.send(&"Hello, world!").await?;
/// # Ok(())
/// # }
/// ```
pub async fn send<T: Serialize>(&self, message: &T) -> Result<(), ChannelError> {
if self.is_closed() {
return Err(ChannelError::Closed);
}
let serialized = self.serializer.serialize(message)?;
self.transport.send(serialized).await?;
Ok(())
}bdrpc/
├── src/
│ ├── lib.rs
│ ├── module.rs
│ └── module/
│ ├── implementation.rs
│ └── tests.rs # Unit tests
├── tests/
│ ├── integration_test.rs # Integration tests
│ └── common/
│ └── mod.rs # Test utilities
└── benches/
└── benchmark.rs # Benchmarks
- Test each function in isolation
- Cover edge cases and error paths
- Use descriptive test names
- Use
cargo nextest runfor running tests
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_send_and_receive_success() {
let (tx, rx) = create_channel().await.unwrap();
tx.send(&"test message").await.unwrap();
let received: String = rx.receive().await.unwrap();
assert_eq!(received, "test message");
}
#[tokio::test]
async fn test_send_on_closed_channel_returns_error() {
let (tx, _rx) = create_channel().await.unwrap();
drop(_rx);
assert!(tx.send(&"test").await.is_err());
}
#[tokio::test]
async fn test_receive_on_empty_channel_waits() {
let (_tx, rx) = create_channel().await.unwrap();
let timeout = tokio::time::timeout(
Duration::from_millis(100),
rx.receive::<String>()
);
assert!(timeout.await.is_err());
}
}- Test multi-component interactions
- Test end-to-end workflows
- Use realistic scenarios
// tests/integration_test.rs
use bdrpc::{Endpoint, TcpTransport, JsonSerializer};
#[tokio::test]
async fn test_bidirectional_communication() {
let server = Endpoint::bind("127.0.0.1:0")
.with_transport(TcpTransport::new())
.with_serializer(JsonSerializer::new())
.build()
.await
.unwrap();
let client = Endpoint::connect(server.local_addr())
.with_transport(TcpTransport::new())
.with_serializer(JsonSerializer::new())
.build()
.await
.unwrap();
// Test bidirectional communication
client.send(&"ping").await.unwrap();
let msg: String = server.receive().await.unwrap();
assert_eq!(msg, "ping");
server.send(&"pong").await.unwrap();
let msg: String = client.receive().await.unwrap();
assert_eq!(msg, "pong");
}Use proptest for property-based testing:
use proptest::prelude::*;
proptest! {
#[test]
fn test_serialization_roundtrip(
data in prop::collection::vec(any::<u8>(), 0..1000)
) {
let serializer = PostcardSerializer::new();
let serialized = serializer.serialize(&data).unwrap();
let deserialized: Vec<u8> = serializer.deserialize(&serialized).unwrap();
prop_assert_eq!(data, deserialized);
}
}Use criterion for benchmarks:
use criterion::{criterion_group, criterion_main, Criterion};
fn benchmark_serialization(c: &mut Criterion) {
let serializer = PostcardSerializer::new();
let data = vec![0u8; 1024];
c.bench_function("serialize 1KB", |b| {
b.iter(|| {
serializer.serialize(std::hint::black_box(&data)).unwrap();
});
});
}
criterion_group!(benches, benchmark_serialization);
criterion_main!(benches);# Run all tests with nextest
cargo nextest run
# Run specific test
cargo nextest run test_name
# Run with output
cargo nextest run -- --nocapture
# Run benchmarks
cargo bench
# Run with coverage (requires tarpaulin)
cargo tarpaulin --out Html- Unit tests: 90%+ coverage
- Integration tests: All major workflows
- Property tests: Core algorithms
- Benchmarks: Performance-critical paths
- Code Documentation: Inline doc comments
- ADRs: Architecture Decision Records (docs/ADR/)
- Guides: User and developer guides (docs/)
- API Reference: Generated from doc comments
/// Brief one-line summary.
///
/// More detailed description with multiple paragraphs if needed.
/// Explain the purpose, behavior, and any important details.
///
/// # Arguments
///
/// * `param1` - Description of first parameter
/// * `param2` - Description of second parameter
///
/// # Returns
///
/// Description of return value.
///
/// # Errors
///
/// List possible error conditions.
///
/// # Panics
///
/// Describe panic conditions if any.
///
/// # Safety
///
/// Document safety requirements for unsafe functions.
///
/// # Examples
///
/// ```
/// use bdrpc::example;
///
/// # async fn test() -> Result<(), Box<dyn std::error::Error>> {
/// let result = example(42).await?;
/// assert_eq!(result, 84);
/// # Ok(())
/// # }
/// ```
pub async fn example(param: i32) -> Result<i32, Error> {
// Implementation
}# Generate and open documentation
cargo doc --open
# Generate with private items
cargo doc --document-private-itemsWhen making significant architectural decisions:
- Copy an existing ADR template from
docs/ADR/ - Number sequentially (ADR-XXX)
- Fill in all sections
- Submit as part of your PR
- Code compiles without warnings
- All tests pass (
cargo nextest run) - Code is formatted (
cargo fmt) - Clippy passes (
cargo clippy) - Documentation is updated
- Examples are updated if API changed
- Commit messages follow conventions
- Branch is up to date with develop
## Description
Brief description of changes.
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Related Issues
Closes #123
## Testing
Describe testing performed:
- Unit tests added/updated
- Integration tests added/updated
- Manual testing performed
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] Examples updated
- [ ] Tests added/updated
- [ ] All tests pass
- [ ] No new warnings- Automated Checks: CI must pass
- Code Review: At least one approval required
- Testing: Reviewer verifies tests are adequate
- Documentation: Reviewer checks docs are updated
- Merge: Squash and merge to develop
For Authors:
- Keep PRs focused and reasonably sized
- Respond to feedback promptly
- Be open to suggestions
- Update PR based on feedback
For Reviewers:
- Review within 2 business days
- Be constructive and specific
- Ask questions if unclear
- Approve when satisfied
We follow Semantic Versioning:
- MAJOR: Breaking changes
- MINOR: New features (backward compatible)
- PATCH: Bug fixes (backward compatible)
- Update version in
Cargo.tomlfiles - Update
CHANGELOG.md - Run full test suite
- Create release branch
- Tag release
- Publish to crates.io
- Create GitHub release
- Update documentation site
- Documentation: docs/
- ADRs: docs/ADR/
- Post-v0.1.0 Roadmap: docs/dev/post-v0.1.0-roadmap.md
- v0.1.0 Implementation Plan (Archived): docs/dev/archive/implementation-plan-v0.1.0.md
- Open Questions: docs/dev/open-questions.md
- GitHub Issues: Bug reports and feature requests
- GitHub Discussions: Questions and general discussion
- Pull Requests: Code review and collaboration
When asking for help:
- Search existing issues/discussions first
- Provide context and details
- Include code examples if relevant
- Describe what you've tried
- Be patient and respectful
When creating channels in BDRPC, follow these guidelines:
Use Channel::new_in_memory() for:
- Unit tests - Testing channel logic in isolation
- Examples - Demonstrating channel API without network complexity
- In-process communication - When you need typed message passing within a single process
use bdrpc::channel::{Channel, ChannelId};
// Create in-memory channel
let (sender, receiver) = Channel::<MyProtocol>::new_in_memory(ChannelId::new(), 100);Important: In-memory channels are NOT connected to any transport and cannot communicate across network boundaries.
Use the Endpoint API for:
- Production applications - Real network communication
- Client-server systems - TCP/UDP communication
- Distributed systems - Multi-node communication
- Integration tests - Testing full network stack
use bdrpc::endpoint::{Endpoint, EndpointConfig};
use bdrpc::serialization::JsonSerializer;
// Create endpoint
let mut endpoint = Endpoint::new(JsonSerializer::default(), EndpointConfig::default());
// Register protocol
endpoint.register_bidirectional("MyProtocol", 1).await?;
// Connect and create channel
let connection = endpoint.connect("127.0.0.1:8080").await?;
let sender = endpoint.channel_manager()
.create_channel::<MyProtocol>(ChannelId::new(), 100)
.await?;channel_basics.rs- In-memory channels onlyadvanced_channels.rs- In-memory channel managementchat_server.rs- In-memory multi-client patterncalculator.rs- TCP transport + in-memory channels (demo)network_chat.rs- Full Endpoint API with network channels (recommended pattern)
If you have code using the deprecated Channel::new():
- For tests/examples: Change to
Channel::new_in_memory() - For production: Migrate to Endpoint API (see
network_chat.rs) - See
MIGRATION.mdfor detailed migration guide
- Profile before optimizing
- Document performance-critical code
- Add benchmarks for hot paths
- Consider memory allocation patterns
- Use appropriate data structures
- Leverage async/await efficiently
- Validate all inputs
- Handle errors securely
- Avoid information leaks
- Document security assumptions
- Report security issues privately
- Maintain API stability
- Deprecate before removing
- Provide migration guides
- Version protocol formats
- Test upgrade paths
Contributors are recognized in:
- Git commit history
- Release notes
- Project README
- Annual contributor list
Thank you for contributing to BDRPC! 🚀
By contributing to BDRPC, you agree that your contributions will be licensed under the same license as the project (see LICENSE.md).