Major additions: - Community Next.js app (port 18187) for browsing claims with API docs - stemedb-chaos crate: Fault injection, chaos testing, CRDT properties - Latent ingestion system: Reddit/FDA ingesters with ADK-Go agents - Disputed claims handling: Manual review workflows and validation - Aphoria security scanner: New extractors (SQL injection, command injection, weak crypto, TLS version), policy-based ignores, UAT reports - Docker infrastructure: Dockerfile, docker-compose.yml for full stack - VulnBank demo: Intentionally vulnerable multi-language test corpus SDK & API enhancements: - Source registry handlers for tracking data provenance - Metrics endpoint - Skeptic filtering improvements Code quality: - Split 14 large files (>500 lines) into focused modules - All files now under 500-line limit per project guidelines Documentation: - Chaos testing guide, circuit breakers, observability docs - Phase 7 UAT documentation updates - Martin Kleppmann technical writer agent Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
3.9 KiB
Add an API Endpoint
When to use: You need to add a new HTTP endpoint to the Episteme API.
Prerequisites
- Familiarity with
axumhandler patterns - Understanding of
utoipaderive macros - Read ai-lookup/services/api.md for architecture overview
Quick Start
# After adding endpoint code:
cargo build -p stemedb-api
cargo test -p stemedb-api
cargo clippy -p stemedb-api -- -D warnings
Step-by-Step
1. Define DTO Types
Create request/response types in crates/stemedb-api/src/dto.rs. These derive BOTH serde (for JSON) and utoipa (for OpenAPI docs):
#[derive(Serialize, Deserialize, ToSchema)]
pub struct MyRequest {
/// Description appears in OpenAPI docs.
#[schema(example = "Tesla_Inc")]
pub subject: String,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct MyResponse {
pub hash: String,
pub status: String,
}
Rules:
- DTOs live in
dto.rs, NOT in handler files - Implement
From<stemedb_core::types::X>for conversions to/from internal types - Use
#[schema(example = "...")]for meaningful OpenAPI examples - Never expose internal rkyv types in the public API
2. Write the Handler
Create handler in crates/stemedb-api/src/handlers/{name}.rs:
#[utoipa::path(
post,
path = "/my-endpoint",
request_body = MyRequest,
responses(
(status = 202, description = "Accepted", body = MyResponse),
(status = 400, description = "Validation failed", body = ApiError),
),
tag = "assertions"
)]
pub async fn my_handler(
State(state): State<AppState>,
Json(req): Json<MyRequest>,
) -> Result<(StatusCode, Json<MyResponse>), ApiError> {
// Convert DTO -> internal type
// Call library code (QueryEngine, Ingestor, etc.)
// Convert result -> DTO
// Return
}
Rules:
- Handlers are thin: convert types, delegate to library code, convert back
- No business logic in handlers
- Always return
Result<_, ApiError>for consistent error responses - Use
tag = "..."to group related endpoints in docs
3. Register the Route
Add to the OpenApiRouter in crates/stemedb-api/src/lib.rs:
let app = OpenApiRouter::new()
.route("/my-endpoint", post(my_handler))
// ...existing routes...
The OpenApiRouter automatically collects the #[utoipa::path] metadata. No separate registration step.
4. Verify
# Build and test
cargo test -p stemedb-api
# Check docs generated correctly
cargo run -p stemedb-api &
curl localhost:18180/api-doc/openapi.json | jq .paths
Error Responses
All error responses use a consistent ApiError type that implements ToSchema:
#[derive(Serialize, ToSchema)]
pub struct ApiError {
pub error: String,
pub code: String,
}
Map internal errors to API errors in the handler layer. Never leak internal error details.
Checklist
- DTO types in
dto.rswithSerialize, Deserialize, ToSchema From<>conversions between DTOs and internal types- Handler annotated with
#[utoipa::path] - Error responses documented in
responses(...) - Route registered on
OpenApiRouter - Tests cover happy path and error cases
cargo clippypasses- Schema examples are meaningful (not "string")
Troubleshooting
OpenAPI spec missing my endpoint
The handler must have #[utoipa::path] AND be registered on OpenApiRouter (not a plain axum::Router).
Type not showing in schemas
The DTO must derive ToSchema. If it references other types, those must also derive ToSchema.
Swagger UI not updating
Hard refresh the browser. The spec is regenerated on each startup.
Related
- ai-lookup/patterns/api-documentation.md - Toolchain overview
- ai-lookup/services/api.md - Service facts
- Rust Guidelines - General Rust patterns