ares-api 0.4.0

HTTP server for Ares AI scraper
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};

use ares_core::error::AppError;

use crate::dto::ErrorResponse;

/// Wrapper so we can implement `IntoResponse` for `AppError`.
pub struct ApiError(pub AppError);

impl From<AppError> for ApiError {
    fn from(err: AppError) -> Self {
        Self(err)
    }
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let (status, error_type) = match &self.0 {
            AppError::SchemaValidationError(_) | AppError::SchemaError(_) => {
                (StatusCode::BAD_REQUEST, "validation_error")
            }
            // A bad/unsupported request parameter (e.g. an unknown `provider`).
            AppError::InvalidInput(_) => (StatusCode::BAD_REQUEST, "invalid_input"),
            // The request was well-formed, but the extraction the model produced
            // did not conform to the schema — surface as Unprocessable Entity.
            AppError::ExtractionValidationError(_) => (
                StatusCode::UNPROCESSABLE_ENTITY,
                "extraction_validation_error",
            ),
            AppError::SchemaNotFound { .. } => (StatusCode::NOT_FOUND, "not_found"),
            AppError::SerializationError(_) => (StatusCode::BAD_REQUEST, "serialization_error"),
            AppError::DatabaseError(_) => (StatusCode::INTERNAL_SERVER_ERROR, "database_error"),
            AppError::ConfigError(_) => (StatusCode::INTERNAL_SERVER_ERROR, "config_error"),
            AppError::RateLimitExceeded => (StatusCode::TOO_MANY_REQUESTS, "rate_limit_exceeded"),
            AppError::Timeout(_) => (StatusCode::GATEWAY_TIMEOUT, "timeout"),
            _ => (StatusCode::INTERNAL_SERVER_ERROR, "internal_error"),
        };

        let body = ErrorResponse {
            error: error_type.to_string(),
            message: self.0.to_string(),
        };

        (status, axum::Json(body)).into_response()
    }
}