Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Development setup

This guide helps you set up a development environment for contributing to Barbacane.

Prerequisites

  • Rust 1.94+ - Install via rustup. This is the project MSRV (enforced by CI); the dependency floor (wasmtime/cranelift) requires it.
  • Git - For version control
  • Node.js 20+ - For the UI (if working on the web interface)
  • PostgreSQL 14+ - For the control plane (or use Docker)
  • Docker - For running PostgreSQL locally (optional)

Optional:

  • cargo-watch - For auto-rebuild on file changes
  • wasm32-unknown-unknown target - For building WASM plugins (rustup target add wasm32-unknown-unknown)
  • tmux - For running multiple services in one terminal

Quick Start with Makefile

The easiest way to get started is using the Makefile:

# Start PostgreSQL in Docker
make db-up

# Build all WASM plugins and seed them into the database
make seed-plugins

# Start the control plane (port 9090)
make control-plane

# In another terminal, start the UI (port 5173)
make ui

Then open http://localhost:5173 in your browser.

Makefile Targets

TargetDescription
Build & Test
makeRun check + test (default)
make testRun all workspace tests
make test-verboseRun tests with output
make test-one TEST=nameRun specific test
make clippyRun clippy lints
make fmtFormat all code
make checkRun fmt-check + clippy
make buildBuild debug
make releaseBuild release
make pluginsBuild all WASM plugins
make compileCompile spec to artifact.bca
make seed-pluginsBuild plugins and seed registry
make cleanClean all build artifacts
make coverageLine coverage of the unit tests
make coverage-integrationCoverage including the integration suite
make coverage-htmlCoverage as a browsable HTML report
make coverage-lcovCoverage as lcov.info
Development
make control-planeStart control plane server (port 9090)
make uiStart UI dev server (port 5173)
make devShow instructions to start both
make dev-tmuxStart both in tmux session
Docker
make docker-buildBuild both Docker images
make docker-build-gatewayBuild data plane image
make docker-build-controlBuild control plane image
make docker-upStart full stack (compose)
make docker-downStop full stack
make docker-runRun data plane standalone
make docker-run-controlRun control plane only
Database
make db-upStart PostgreSQL container
make db-downStop PostgreSQL container
make db-resetReset database (removes all data)

Override the database URL:

make control-plane DATABASE_URL=postgres://user:pass@host/db

Getting Started

Clone the Repository

git clone https://github.com/barbacane/barbacane.git
cd barbacane

Build

# Build all crates
cargo build --workspace

# Build in release mode
cargo build --workspace --release

Test

# Run all tests
cargo test --workspace

# Run tests for a specific crate
cargo test -p barbacane-compiler

# Run tests with output
cargo test --workspace -- --nocapture

# Run a specific test (routing trie lives in the barbacane crate)
cargo test -p barbacane router::trie::tests::static_takes_precedence_over_param

Coverage

Coverage uses cargo-llvm-cov, which instruments the build rather than sampling, so a line is covered only if it actually executed.

cargo install cargo-llvm-cov
rustup component add llvm-tools-preview

# Unit tests only: fast, needs no services
make coverage

# Adds the integration suite, which boots the gateway as a child process
make coverage-integration

# Browsable report at target/llvm-cov/html/index.html
make coverage-html

make coverage-integration measures the gateway process too. cargo llvm-cov show-env puts the instrumented build under CARGO_TARGET_DIR, and the test harness resolves the gateway binary from there, so the process the tests drive is the instrumented one. Set BARBACANE_TEST_BINARY to point the harness at a specific binary instead.

The targets wrap scripts/coverage.sh, which CI calls directly in the Coverage job, so local and CI runs measure the same thing. The job publishes the summary to the run page, uploads lcov.info as an artifact and fails if line coverage drops below the floor, currently 60%. Raise that floor when the real number rises; do not lower it to turn a red build green.

./scripts/coverage.sh --help

Run

# Dev server (auto-compiles and hot-reloads on save)
cargo run --bin barbacane -- dev --manifest tests/fixtures/barbacane.yaml --spec tests/fixtures/minimal.yaml

# Or the manual workflow:
# Validate a spec
cargo run --bin barbacane -- validate --spec tests/fixtures/minimal.yaml

# Compile a spec
cargo run --bin barbacane -- compile --spec tests/fixtures/minimal.yaml \
  --manifest tests/fixtures/barbacane.yaml --output test.bca

# Run the gateway
cargo run --bin barbacane -- serve --artifact test.bca --listen 127.0.0.1:8080 --dev

Project Structure

barbacane/
├── Cargo.toml              # Workspace definition
├── Makefile                # Development shortcuts
├── Dockerfile              # Data plane container image
├── Dockerfile.control      # Control plane container image
├── docker-compose.yml      # PostgreSQL for local dev
├── docker/                 # Docker support files
│   ├── nginx-control.conf  # Nginx config for control plane UI
│   └── control-entrypoint.sh
├── LICENSE
├── CONTRIBUTING.md
├── README.md
│
├── crates/
│   ├── barbacane/          # Data-plane binary + CLI (serve/compile/validate/dev)
│   │   ├── Cargo.toml
│   │   └── src/
│   │       ├── main.rs
│   │       ├── router/     # Prefix-trie routing (trie.rs)
│   │       ├── validator.rs
│   │       └── mcp/        # MCP server
│   │
│   ├── barbacane-control/  # Control plane server (REST API, PostgreSQL)
│   │   ├── Cargo.toml
│   │   └── src/
│   │       ├── main.rs
│   │       ├── server.rs
│   │       └── db/
│   │
│   ├── barbacane-compiler/ # Spec compilation + parser + .bca format
│   │   ├── Cargo.toml
│   │   └── src/
│   │       ├── lib.rs
│   │       ├── artifact.rs
│   │       └── spec_parser/   # OpenAPI/AsyncAPI parsing
│   │
│   ├── barbacane-wasm/     # WASM plugin runtime (wasmtime), host functions
│   ├── barbacane-telemetry/# OpenTelemetry tracing + Prometheus metrics
│   ├── barbacane-plugin-sdk/  # Plugin SDK (types + log/http/errors/jwt helpers)
│   ├── barbacane-plugin-macros/ # #[barbacane_middleware] / #[barbacane_dispatcher]
│   ├── barbacane-sigv4/    # AWS SigV4 signing (s3 / lambda dispatchers)
│   └── barbacane-test/     # Integration tests (+ adversarial security suite)
│
├── plugins/                # Built-in WASM plugins
│   ├── http-upstream/      # HTTP reverse proxy dispatcher
│   ├── mock/               # Mock response dispatcher
│   ├── lambda/             # AWS Lambda dispatcher
│   ├── kafka/              # Kafka dispatcher (AsyncAPI)
│   ├── nats/               # NATS dispatcher (AsyncAPI)
│   ├── rate-limit/         # Rate limiting middleware
│   ├── cors/               # CORS middleware
│   ├── cache/              # Caching middleware
│   ├── jwt-auth/           # JWT authentication
│   ├── apikey-auth/        # API key authentication
│   └── oauth2-auth/        # OAuth2 token introspection
│
├── ui/                     # React web interface
│   ├── package.json
│   ├── vite.config.ts
│   └── src/
│       ├── pages/          # Page components
│       ├── components/     # UI components
│       ├── hooks/          # Custom React hooks
│       └── lib/            # API client, utilities
│
├── tests/
│   └── fixtures/           # Test spec files
│       ├── minimal.yaml
│       ├── train-travel-3.0.yaml
│       └── ...
│
├── docs/                   # Documentation
│   ├── index.md
│   ├── guide/
│   ├── reference/
│   └── contributing/
│
└── adr/                    # Architecture Decision Records
    ├── 0001-*.md
    └── ...

Development Workflow

Making Changes

  1. Create a branch

    git checkout -b feature/my-feature
    
  2. Make changes and test

    cargo test --workspace
    
  3. Format code

    cargo fmt --all
    
  4. Check lints

    cargo clippy --workspace -- -D warnings
    
  5. Commit

    git commit -m "feat: add my feature"
    

Code Duplication

CI fails when copy-paste duplication across crates/ and plugins/ exceeds the threshold in the duplication job of .github/workflows/ci.yml. Reproduce it locally with the same settings:

cargo install jscpd --locked
jscpd crates plugins \
  --format rust \
  --ignore "**/target/**,crates/barbacane-test/**" \
  --min-lines 10 --min-tokens 70 \
  --threshold 3.5

crates/barbacane-test is excluded because repeated setup across integration tests says nothing about the shipped code. Unit tests in #[cfg(test)] modules are counted, as they cannot be separated by path.

The threshold guards against growth rather than demanding a cleanup. Lower it when a change reduces duplication, so the reduction is kept. Raising it needs a reason in the pull request.

Commit Messages

Follow Conventional Commits:

  • feat: - New feature
  • fix: - Bug fix
  • docs: - Documentation
  • refactor: - Code refactoring
  • test: - Adding tests
  • chore: - Maintenance

Examples:

feat: add cache middleware
fix: handle empty path in router
docs: add middleware configuration guide
refactor: extract trie traversal logic
test: add integration tests for 405 responses

Adding a New Crate

  1. Create the crate directory:

    mkdir -p crates/barbacane-mycrate/src
    
  2. Create Cargo.toml:

    [package]
    name = "barbacane-mycrate"
    description = "Description here"
    version.workspace = true
    edition.workspace = true
    license.workspace = true
    
    [dependencies]
    # Use workspace dependencies
    serde = { workspace = true }
    
  3. Add to workspace in root Cargo.toml:

    [workspace]
    members = [
        # ...existing crates...
        "crates/barbacane-mycrate",
    ]
    
    [workspace.dependencies]
    barbacane-mycrate = { path = "crates/barbacane-mycrate" }
    
  4. Create src/lib.rs:

    //! Brief description of the crate.
    //!
    //! More detailed explanation.

Testing

Unit Tests

Place unit tests in the same file:

// src/parser.rs

pub fn parse(input: &str) -> Result<Spec, Error> {
    // implementation
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_minimal() {
        let result = parse("openapi: 3.1.0...");
        assert!(result.is_ok());
    }
}

Integration Tests

Use barbacane-test crate for full-stack tests:

use barbacane_test::TestGateway;

#[tokio::test]
async fn test_my_feature() {
    let gateway = TestGateway::from_spec("tests/fixtures/my-fixture.yaml")
        .await
        .expect("failed to start gateway");

    let resp = gateway.get("/my-endpoint").await.unwrap();
    assert_eq!(resp.status(), 200);
}

Test Fixtures

Add test spec files to tests/fixtures/:

# tests/fixtures/my-feature.yaml
openapi: "3.1.0"
info:
  title: Test
  version: "1.0.0"
paths:
  /test:
    get:
      x-barbacane-dispatch:
        name: mock
        config:
          status: 200

UI Development

The web interface is a React application in the ui/ directory.

Setup

cd ui
npm install

Development Server

# Using Makefile (from project root)
make ui

# Or manually
cd ui && npm run dev

The UI runs at http://localhost:5173 and proxies API requests to the control plane at http://localhost:9090.

Testing

cd ui
npm run test        # Run tests once
npm run test:watch  # Watch mode

Key Directories

ui/src/
├── pages/         # Page components (ProjectPluginsPage, etc.)
├── components/    # Reusable UI components
│   └── ui/        # Base components (Button, Card, Badge)
├── hooks/         # Custom hooks (useJsonSchema, usePlugins)
├── lib/
│   ├── api/       # API client (types, requests)
│   └── utils.ts   # Utilities (cn, formatters)
└── App.tsx        # Main app with routing

Adding a New Page

  1. Create page component in src/pages/:

    // src/pages/my-feature.tsx
    export function MyFeaturePage() {
      return <div>...</div>
    }
    
  2. Add route in src/App.tsx:

    <Route path="/my-feature" element={<MyFeaturePage />} />
    

API Client

Use the typed API client from @/lib/api:

import { useQuery } from '@tanstack/react-query'
import { listPlugins } from '@/lib/api'

function MyComponent() {
  const { data: plugins, isLoading } = useQuery({
    queryKey: ['plugins'],
    queryFn: () => listPlugins(),
  })
  // ...
}

Debugging

Logging

Use eprintln! for development logging:

if cfg!(debug_assertions) {
    eprintln!("debug: processing request to {}", path);
}

Running with Verbose Output

# Gateway with dev mode
cargo run --bin barbacane -- serve --artifact test.bca --dev

# Compile with output
cargo run --bin barbacane -- compile --spec api.yaml --output api.bca

Integration Test Debugging

# Run single test with output
cargo test -p barbacane-test test_gateway_health -- --nocapture

Performance Profiling

Benchmarks

Criterion benchmarks are available for performance-critical components:

# Run all benchmarks
cargo bench --workspace

# Run router + validator benchmarks (both live in the barbacane crate)
cargo bench -p barbacane --bench routing
cargo bench -p barbacane --bench validation

Router benchmarks (crates/barbacane/benches/routing.rs):

  • router_lookup - Measures lookup performance for static paths, parameterized paths, and not-found cases
  • router_insert - Measures route insertion performance at various route counts (10-1000 routes)

Validator benchmarks (crates/barbacane/benches/validation.rs):

  • validator_creation - Measures schema compilation time
  • path_param_validation - Validates path parameters against schemas
  • query_param_validation - Validates query parameters
  • body_validation - Validates JSON request bodies
  • full_request_validation - End-to-end request validation

Benchmark results are saved to target/criterion/ with HTML reports.

Flamegraph

cargo install flamegraph
cargo flamegraph -p barbacane -- --artifact test.bca

Documentation

Doc Comments

All public APIs should have doc comments:

/// Parse an OpenAPI specification from a string.
///
/// # Arguments
///
/// * `input` - YAML or JSON string containing the spec
///
/// # Returns
///
/// Parsed `ApiSpec` or error if parsing fails.
///
/// # Example
///
/// ```
/// let spec = parse_spec("openapi: 3.1.0...")?;
/// println!("Found {} operations", spec.operations.len());
/// ```
pub fn parse_spec(input: &str) -> Result<ApiSpec, ParseError> {
    // ...
}

Generate Docs

cargo doc --workspace --open

Release Process

  1. Update version in workspace Cargo.toml
  2. Update CHANGELOG.md
  3. Create git tag: git tag v0.1.0
  4. Push: git push origin main --tags
  5. CI builds and publishes

Getting Help

  • Open an issue on GitHub
  • Check existing ADRs for design decisions
  • Read the architecture docs