This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
All task runner commands use task (Taskfile). The key ones:
# Build / check
cargo build --workspace
cargo check --workspace
# Tests
cargo test --workspace # all unit + in-process integration tests
cargo test --package batlehub-web nuget # single package, filter by name
cargo test --package batlehub-adapters --lib rbac # lib-only tests (no integration)
cargo test -p batlehub-cli --test integration # CLI integration tests (subprocess binary)
# Linting (CI fails on any warning)
cargo clippy --workspace -- -D warnings
cargo fmt --all --check
# Format
cargo fmt --all
# Coverage (requires Podman — starts Postgres + MinIO)
task coverage # HTML report in coverage/html/
task coverage-check # fails if line coverage < 80%
# Integration tests that need real Postgres (via Podman)
task test:pg-cache
task test:pg-local-registry
# Integration test that needs real S3/MinIO
task test:s3
# Run server (requires Postgres)
cargo run -p batlehub-server -- --config config.example.toml
# Frontend
cd ui && npm run dev # Vite dev server (proxies /api → localhost:8080)
cd ui && npm run generate # regenerate TypeScript client from ui/openapi.json
task dump-spec # refresh ui/openapi.json from running server
# Fuzz (nightly)
task fuzz TARGET=fuzz_rbac_evaluate MAX_TIME=30crates/core — domain: entities, ports (traits), rules, services (no I/O)
crates/config — TOML schema (AppConfig, RegistryConfig, …) + loader
crates/adapters — infrastructure: HTTP registry clients, Postgres, Redis, S3, in-memory impls
crates/web — actix-web handlers, middleware, extractors
crates/examples — integration test helpers and smoke/real-proxy test binaries
server/ — binary: wires everything together, no domain logic
cli/ — batlehub-cli binary: clap commands + reqwest API client + ratatui TUI
ui/ — Vue 3 + Vite SPA (TypeScript, Tailwind, shadcn-inspired components)
The dependency direction is strict: core ← adapters ← web ← server. The config crate is read only by server and web.
-
Auth middleware (
crates/web/src/middleware/auth.rs) — iteratesVec<Arc<dyn AuthProvider>>, resolves to anIdentity, stores it in request extensions.X-NuGet-ApiKeyis normalised toAuthorization: Bearerinextractors::raw_auth_from_requestbefore providers see it. -
Handler (
crates/web/src/handlers/proxy/<registry>.rs) — callsrequire_registry_type+require_local_modeguards, then either:- Local/hybrid mode: reads from
LocalRegistryService(database-backed) - Proxy/hybrid fallthrough: delegates to
ProxyService::handle
- Local/hybrid mode: reads from
-
ProxyService (
crates/core/src/services/proxy.rs) — acquires a short-lived read lock onHotConfigto clone theArc<RegistryClient>andArc<RegistryPolicy>, then:- Resolves metadata (cache-first, stale-on-error optional)
- Evaluates rules (
RbacRule,DenyLatestRule,BlockListRule,ReleaseAgeGateRule) - Streams artifact from upstream (or serves from storage cache)
HotConfig is wrapped in Arc<RwLock<HotConfig>> (HotConfigLock). Handlers snapshot the parts they need by cloning Arc<> before any await. Config reload replaces the entire inner value atomically. In-flight requests finish with the old snapshot.
crates/adapters/src/registry/<name>.rs— implementRegistryClient(registry_type,resolve_metadata,fetch_artifact; optionallylist_versions,search_packages). UseNugetRegistryClientas a reference.crates/adapters/src/registry/mod.rs— add#[cfg(feature = "registry-<name>")] pub mod <name>entry.crates/web/src/handlers/proxy/<name>.rs— actix-web handlers; useproxy_stream,require_registry_type,require_local_mode,content_type_forhelpers fromcommon.rs.crates/web/src/lib.rs— register routes withcfg.service(...)and add theutoipatag.crates/config/src/schema.rs— allow"<name>"in theRegistryConfigtype field.server/src/main.rs— instantiate the client and wire it intoHotConfig.ui/src/config/registryTypes.ts— add aRegistryTypeDefentry with setup snippets.
For local/hybrid mode, additionally implement get_<name>_versions (and related helpers) in crates/core/src/services/local_registry.rs, following the existing get_nuget_versions / get_maven_versions patterns.
- Unit tests:
#[cfg(test)] mod testsinside the same file. Registry adapter tests usemockito::Serverto mock HTTP upstreams. - Integration tests (in-process):
crates/web/tests/integration.rs— spins up a full actix-web app withInMemoryPackageRepository,InMemoryStorageBackend,InMemoryCacheStore, andFixedRegistry. Each registry type has amake_local_<type>_app(mode: RegistryMode)factory and a helper to build publish payloads. - CLI integration tests:
cli/tests/integration.rs— builds the CLI binary then invokes it as a subprocess against an in-memory actix-web server (same pattern as the web tests). Usesenv!("CARGO_BIN_EXE_batlehub-cli")so cargo builds the binary automatically before running. See architecture note below about in-memory store separation. - External integration tests:
crates/adapters/tests/pg_*.rs,s3_storage.rs— require real Postgres/MinIO (run viatask test:pg-*/task test:s3). - Fuzz targets:
fuzz/fuzz_targets/— run with nightly viatask fuzz.
InMemoryLocalRegistry (used by LocalRegistryService — publish/yank/delete) and InMemoryPackageRepository (used by AdminService — package list/block) are separate in-memory stores. In Postgres they share the same tables, so this separation only matters in tests.
Consequence: packages published via the local-registry HTTP endpoint do not appear in GET /api/v1/packages (which queries AdminService). Use TestServer::seed_package() to inject entries directly into InMemoryPackageRepository when testing commands like package list. To verify yank/unyank/delete state, query the registry-specific endpoints (e.g. the NuGet flat-index at /proxy/{reg}/nuget/v3/flat/{id}/index.json) rather than package list.
Coverage is enforced at 80% lines. Excluded paths (DB adapters, some registry clients, auth/OIDC handlers, server main) are listed in the COVERAGE_EXCLUDE variable in Taskfile.yml.
Artifacts are stored with artifact_storage_key(registry, name, version) → "{registry}/{name}/{version}". Maven uses maven_artifact_storage_key for multi-file artifacts. These keys are stable and shared between ProxyService (cache writes) and LocalRegistryService (local reads).
SQL migrations live in crates/adapters/migrations/. They are embedded via crates/adapters/src/migrations.rs using a mig! macro (avoids the sqlx::migrate! macro which pulls in sqlx-mysql → rsa RUSTSEC advisory). When adding a migration, increment the sequence number and add a mig! entry to embedded_migrator().
sqlx-macros and sqlx-mysql are patched to empty stubs in [patch.crates-io] (Cargo.toml) to remove the rsa crate (RUSTSEC-2023-0071) from the dependency tree. Do not add features = ["macros"] to the sqlx dependency or re-enable sqlx-macros/sqlx-mysql.
aws-sdk-s3 and aws-config use default-features = false to avoid legacy-rustls-ring (RUSTSEC-2026-0098/0099/0104). Do not enable default features on these crates.
The Vue SPA lives in ui/. The TypeScript API client (ui/src/client/) is auto-generated from ui/openapi.json via npm run generate — do not edit it manually. Setup snippets for the Setup Guide are defined in ui/src/config/registryTypes.ts as REGISTRY_TYPE_DEFS.
When the backend api is updated, the openapi spec/sdk must be resynced:
- Generate the swagger spec :
task dump-spec(copies toui/openapi.json) - Generate the Typescript client:
task ui:generate(reads fromui/openapi.json, outputs toui/src/client/)
The generated use fetch and include the full model spec, so any changes to the API will be reflected in the generated client. The client is used in the frontend and can also be imported by external users who want a typed API client for the server.
The doc live in two places:
- Docs site (batleforc.git.batleforc.fr/batlehub) — user-facing documentation, setup guides, API reference (generated from
ui/openapi.json), architecture overview. Markdown files indocs/are copied to the static site. - Base docs (/docs/) - Internal documentation and helper for some case
- Roadmap (ROADMAP.md) — high-level plans and progress tracking for features and registry types.
- Code comments - doc directly in the codebase, especially for complex logic like the request lifecycle, hot reload, and registry client implementations. Use
///for public items and//for internal comments.