## 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>
122 lines
3.9 KiB
Rust
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>;
|