stemedb/crates/stemedb-api/src/error.rs
jordan 157dbbb9eb feat: Complete Aphoria Phase 8-9 + UAT suite (90/90 tests passing)
## Phase 8: Enterprise Extractor Improvements 
- 14 security extractors (TLS, JWT, SQL injection, XSS, etc.)
- 10 framework-specific extractors (Spring, Django, Rails, etc.)
- Config file security detection (YAML, TOML)

## Phase 9: Autonomous Extractor Generation 
- Shadow mode executor with TP/FP tracking
- Graduation pipeline with confidence thresholds
- Auto-rollback on regression detection
- Cross-project pattern syncing

## UAT Suite Complete (14 scripts, 90 tests)
- test-core-detection.sh (6 tests)
- test-declarative-extractors.sh (5 tests)
- test-domain-frameworks.sh (5 tests)
- test-domain-unreal.sh (3 tests)
- test-llm-extraction.sh (6 tests)
- test-eval-harness.sh (5 tests)
- test-cross-language.sh (3 tests)
- test-precommit-performance.sh (4 tests)
- test-output-formats.sh (8 tests)
- test-drift-detection.sh (6 tests)
- test-exit-codes.sh (12 tests)
+ 3 more scripts

## Other Changes
- Updated roadmap to mark Phase 8-9 complete
- Added .gitignore entries for build artifacts
- Updated pre-commit: 800 line limit, exclude tests/data/cmd

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 22:50:55 -07:00

122 lines
3.9 KiB
Rust

//! Error types for the StemeDB API.
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use thiserror::Error;
use crate::dto::ErrorResponse;
/// API-level errors.
#[derive(Debug, Error)]
pub enum ApiError {
/// Invalid hex encoding in request.
#[error("Invalid hex encoding: {0}")]
InvalidHex(String),
/// Hash has incorrect byte length.
#[error("Invalid hash length: expected {expected}, got {actual}")]
InvalidHashLength {
/// Expected length in bytes.
expected: usize,
/// Actual length in bytes.
actual: usize,
},
/// Write-ahead log error.
#[error("WAL error: {0}")]
Wal(#[from] stemedb_wal::QuarantineError),
/// Storage backend error.
#[error("Storage error: {0}")]
Storage(#[from] stemedb_storage::StorageError),
/// Serialization/deserialization error.
#[error("Serialization error: {0}")]
Serialization(String),
/// Ingestion pipeline error.
#[error("Ingest error: {0}")]
Ingest(#[from] stemedb_ingest::error::IngestError),
/// Query execution error.
#[error("Query error: {0}")]
Query(#[from] stemedb_query::QueryError),
/// Invalid request parameters.
#[error("Invalid request: {0}")]
InvalidRequest(String),
/// Resource not found.
#[error("Not found: {0}")]
NotFound(String),
/// Resource conflict (e.g., already superseded).
#[error("Conflict: {0}")]
Conflict(String),
/// Internal server error.
#[error("Internal error: {0}")]
Internal(String),
/// Missing or invalid API key.
#[error("Unauthorized: {0}")]
Unauthorized(String),
/// Valid API key but insufficient permissions.
#[error("Forbidden: {0}")]
Forbidden(String),
/// Rate limit exceeded.
#[error("Rate limit exceeded: {0}")]
RateLimited(String),
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let (status, code, message) = match self {
ApiError::InvalidHex(ref msg) => (StatusCode::BAD_REQUEST, "INVALID_HEX", msg.clone()),
ApiError::InvalidHashLength { .. } => {
(StatusCode::BAD_REQUEST, "INVALID_HASH_LENGTH", self.to_string())
}
ApiError::InvalidRequest(ref msg) => {
(StatusCode::BAD_REQUEST, "INVALID_REQUEST", msg.clone())
}
ApiError::NotFound(ref msg) => (StatusCode::NOT_FOUND, "NOT_FOUND", msg.clone()),
ApiError::Wal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "WAL_ERROR", self.to_string()),
ApiError::Storage(_) => {
(StatusCode::INTERNAL_SERVER_ERROR, "STORAGE_ERROR", self.to_string())
}
ApiError::Serialization(_) => {
(StatusCode::INTERNAL_SERVER_ERROR, "SERIALIZATION_ERROR", self.to_string())
}
ApiError::Ingest(_) => {
(StatusCode::INTERNAL_SERVER_ERROR, "INGEST_ERROR", self.to_string())
}
ApiError::Query(_) => {
(StatusCode::INTERNAL_SERVER_ERROR, "QUERY_ERROR", self.to_string())
}
ApiError::Conflict(ref msg) => (StatusCode::CONFLICT, "CONFLICT", msg.clone()),
ApiError::Internal(ref msg) => {
(StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL_ERROR", msg.clone())
}
ApiError::Unauthorized(ref msg) => {
(StatusCode::UNAUTHORIZED, "UNAUTHORIZED", msg.clone())
}
ApiError::Forbidden(ref msg) => (StatusCode::FORBIDDEN, "FORBIDDEN", msg.clone()),
ApiError::RateLimited(ref msg) => {
(StatusCode::TOO_MANY_REQUESTS, "RATE_LIMITED", msg.clone())
}
};
let error_response = ErrorResponse { error: message, code: code.to_string() };
(status, Json(error_response)).into_response()
}
}
/// Result type for API operations.
pub type Result<T> = std::result::Result<T, ApiError>;