Contributing to FURSY HTTP Router
November 16, 2025 · View on GitHub
Thank you for considering contributing to FURSY! This document outlines the development workflow and guidelines.
Git Workflow (Git-Flow)
This project uses Git-Flow branching model for development.
Branch Structure
main # Production-ready code (tagged releases)
└─ develop # Integration branch for next release
├─ feature/* # New features
├─ bugfix/* # Bug fixes
└─ hotfix/* # Critical fixes from main
Branch Purposes
- main: Production-ready code. Only releases are merged here.
- develop: Active development branch. All features merge here first.
- feature/*: New features. Branch from
develop, merge back todevelop. - bugfix/*: Bug fixes. Branch from
develop, merge back todevelop. - hotfix/*: Critical production fixes. Branch from
main, merge to bothmainanddevelop.
Workflow Commands
Starting a New Feature
# Create feature branch from develop
git checkout develop
git pull origin develop
git checkout -b feature/my-new-feature
# Work on your feature...
git add .
git commit -m "feat: add my new feature"
# When done, merge back to develop
git checkout develop
git merge --no-ff feature/my-new-feature
git branch -d feature/my-new-feature
git push origin develop
Fixing a Bug
# Create bugfix branch from develop
git checkout develop
git pull origin develop
git checkout -b bugfix/fix-issue-123
# Fix the bug...
git add .
git commit -m "fix: resolve issue #123"
# Merge back to develop
git checkout develop
git merge --no-ff bugfix/fix-issue-123
git branch -d bugfix/fix-issue-123
git push origin develop
Creating a Release
# Create release branch from develop
git checkout develop
git pull origin develop
git checkout -b release/v0.4.0
# Update version numbers, CHANGELOG, etc.
git add .
git commit -m "chore: prepare release v0.4.0"
# Merge to main and tag
git checkout main
git merge --no-ff release/v0.4.0
git tag -a v0.4.0 -m "Release v0.4.0"
# Merge back to develop
git checkout develop
git merge --no-ff release/v0.4.0
# Delete release branch
git branch -d release/v0.4.0
# Push everything
git push origin main develop --tags
Hotfix (Critical Production Bug)
# Create hotfix branch from main
git checkout main
git pull origin main
git checkout -b hotfix/critical-bug
# Fix the bug...
git add .
git commit -m "fix: critical production bug"
# Merge to main and tag
git checkout main
git merge --no-ff hotfix/critical-bug
git tag -a v0.3.1 -m "Hotfix v0.3.1"
# Merge to develop
git checkout develop
git merge --no-ff hotfix/critical-bug
# Delete hotfix branch
git branch -d hotfix/critical-bug
# Push everything
git push origin main develop --tags
Semantic Versioning
FURSY follows Semantic Versioning 2.0.0:
For 0.x.y versions (pre-1.0):
- 0.y.0 - New features (minor bump)
- 0.y.z - Bug fixes, hotfixes (patch bump)
For 1.x.y+ versions (stable API):
- Major (x.0.0) - Breaking changes
- Minor (x.y.0) - New features (backwards-compatible)
- Patch (x.y.z) - Bug fixes only
Note: v1.0.0 will only be released after 6-12 months of production usage and full API stabilization. Breaking changes are allowed in 0.x versions.
Commit Message Guidelines
Follow Conventional Commits specification:
<type>(<scope>): <description>
[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 (build, dependencies, etc.)
- perf: Performance improvements
Examples
feat(router): add wildcard route support
fix(context): resolve parameter extraction edge case
docs: update README with OpenAPI examples
refactor(radix): simplify tree traversal logic
test(middleware): add benchmarks for chain execution
perf(pool): optimize context pooling strategy
chore: update golangci-lint to v1.60
Code Quality Standards
Before Committing
Run the pre-commit checks:
bash scripts/pre-release-check.sh
This script runs:
go fmt ./...- Format codegolangci-lint run- Lint codego test -race -coverprofile=coverage.txt ./...- Run tests with race detector- Coverage check (>85% for Phase 1, >90% for Phase 2+)
Pull Request Requirements
- Code is formatted (
go fmt ./...) - Linter passes (
golangci-lint run) - All tests pass with race detector (
go test -race ./...) - New code has tests (minimum 85% coverage for Phase 1, 90%+ for Phase 2+)
- Benchmarks for performance-critical code
- Documentation updated (if applicable)
- Commit messages follow Conventional Commits
- No sensitive data (credentials, tokens, etc.)
- Uses
encoding/json/v2(NOTencoding/json) - Uses
log/slogfor logging - No external dependencies in core package
Development Setup
Prerequisites
- Go 1.25 or later (required for generics and modern stdlib)
- golangci-lint (for code quality checks)
- git (for version control)
Install Dependencies
# Install golangci-lint
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# Verify installation
golangci-lint --version
Running Tests
# Run all tests
go test -v ./...
# Run with coverage
go test -v -coverprofile=coverage.txt ./...
# Run with race detector (always use before commit!)
go test -race ./...
# Run benchmarks
go test -bench=. -benchmem ./...
# Run specific benchmark
go test -bench=BenchmarkRouter_StaticRoute -benchmem ./...
Running Linter
# Run linter
golangci-lint run
# Run with verbose output
golangci-lint run -v
# Run and save report
golangci-lint run --out-format=colored-line-number > lint-report.txt
Project Structure
fursy/
├── .golangci.yml # Linter configuration
├── .github/
│ └── workflows/ # CI/CD pipelines
├── docs/ # Public documentation
├── examples/ # Usage examples
│ ├── hello-world/
│ └── rest-api/
├── internal/ # Internal implementation (not in Go docs)
│ ├── radix/ # Radix tree routing engine
│ └── pool/ # Context pooling
├── middleware/ # Built-in middleware
│ ├── logger/
│ ├── recovery/
│ ├── cors/
│ ├── ratelimit/
│ └── auth/
├── plugins/ # Optional plugins (can have dependencies)
│ ├── opentelemetry/
│ ├── validator/
│ └── openapi/
├── scripts/ # Development scripts
│ └── pre-release-check.sh
├── router.go # Public API - Router (wrapper over internal/radix)
├── context.go # Public API - Context (HTTP context)
├── box.go # Public API - Box[Req, Res] (type-safe container)
├── handler.go # Public API - Handler types
├── group.go # Public API - Route groups
├── error.go # Public API - RFC 9457 Problem Details
├── CONTRIBUTING.md # This file
├── README.md # Main documentation
└── go.mod # Go module
Architecture Principles
Clean Public API (Wrapper Pattern)
FURSY uses a wrapper architecture to keep the public API clean:
github.com/coregx/fursy/ ← Public API (in Go docs)
├── router.go ← Wrapper over internal/radix
├── context.go ← Public API
└── handler.go ← Public types
github.com/coregx/fursy/internal/ ← Implementation (NOT in Go docs)
├── radix/ ← Real routing implementation
└── pool/ ← Context pooling
Why?
internal/packages cannot be imported by external modules- Go docs show ONLY clean, simple public API
- Implementation details hidden from users
- Allows changing internals without breaking changes
Zero Dependencies (Core)
- Core package (
fursy/) must use ONLY stdlib - Plugins (
plugins/) can have dependencies - Never add external dependencies to core without discussion
Type Safety
Use Go generics for type-safe handlers:
type Handler[Req, Res any] func(*Box[Req, Res]) error
type Box[Req, Res any] struct {
*Context // Embedded base context
ReqBody *Req
ResBody *Res
}
Adding New Features
- Check if issue exists, if not create one
- Discuss approach in the issue
- Create feature branch from
develop - Write tests FIRST (TDD approach)
- Implement feature
- Add benchmarks for performance-critical code
- Update documentation
- Run quality checks (
bash scripts/pre-release-check.sh) - Create pull request to
develop - Wait for code review
- Address feedback
- Merge when approved
Code Style Guidelines
General Principles
- Follow Go conventions and idioms
- Write self-documenting code
- Add comments for complex logic (especially in radix tree)
- Keep functions small and focused (<50 lines ideal)
- Use meaningful variable names
- TDD approach - write tests first!
Naming Conventions
- Public types/functions:
PascalCase(e.g.,Router,ServeHTTP) - Private types/functions:
camelCase(e.g.,findRoute,extractParams) - Constants:
PascalCase(e.g.,StatusOK,MethodGet) - Test functions:
Test*(e.g.,TestRouter_GET) - Benchmark functions:
Benchmark*(e.g.,BenchmarkRouter_StaticRoute)
Required Standards
1. Use encoding/json/v2
// ✅ CORRECT
import "encoding/json/v2"
// ❌ WRONG - Do NOT use old version
import "encoding/json"
2. Use log/slog
import "log/slog"
// Structured logging
slog.Info("request processed",
"method", req.Method,
"path", req.URL.Path,
"duration", duration,
)
3. Error Handling with RFC 9457
// Always use RFC 9457 Problem Details
return c.Error(404, fursy.NotFound("User not found"))
return c.Error(400, fursy.BadRequest("Invalid email"))
Testing
- Use table-driven tests when appropriate
- Test both success and error cases
- Use
testing.T.Run()for subtests - Minimum coverage: 85% (Phase 1), 90%+ (Phase 2+)
- Always run with race detector:
go test -race
Benchmarking
Performance is a core goal. Always benchmark critical paths:
func BenchmarkRouter_SimpleRoute(b *testing.B) {
r := New()
r.GET("/users/:id", handler)
req := httptest.NewRequest("GET", "/users/123", nil)
w := httptest.NewRecorder()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
r.ServeHTTP(w, req)
}
}
Performance goals:
- Route lookup: <500ns (parametric), <300ns (static)
- Allocations: 1 alloc/op (routing hot path)
- Throughput: >1M req/s (with middleware)
Getting Help
- Check existing issues
- Read documentation in
docs/ - Review examples in
examples/ - Ask questions in GitHub Issues
- Check .claude/STATUS.md for current project status
Performance Benchmarking
FURSY prioritizes performance. See PERFORMANCE.md for:
- Current benchmark results
- Performance optimization techniques
- Comparison with other routers
Current metrics (Phase 3):
- Static routes: 256 ns/op, 1 alloc/op
- Parametric routes: 326 ns/op, 1 alloc/op
- Deep nesting (4 params): 561 ns/op, 1 alloc/op
- Coverage: 91.7%
License
By contributing, you agree that your contributions will be licensed under the MIT License.
Thank you for contributing to FURSY HTTP Router! 🚀