terrana 0.1.1

Zero-config spatial API server — point it at a CSV, Parquet, or GeoJSON file and get a REST API with spatial and geometry queries.
//! The application error type. Every handler returns `Result<_, AppError>`, and
//! `AppError` maps to an HTTP status + JSON body via its `IntoResponse` impl.

use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde_json::json;

#[derive(thiserror::Error, Debug)]
pub enum AppError {
    #[error("Invalid query parameter: {0}")]
    BadRequest(String),
    #[error("Column not found: {0}")]
    ColumnNotFound(String),
    #[error("File not found: {0}")]
    FileNotFound(String),
    #[error("Geometry error: {0}")]
    Geometry(String),
    #[error("Database error: {0}")]
    Database(#[from] duckdb::Error),
    #[error("Internal error: {0}")]
    Internal(#[from] anyhow::Error),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match &self {
            AppError::BadRequest(_) | AppError::ColumnNotFound(_) => {
                (StatusCode::BAD_REQUEST, self.to_string())
            }
            AppError::FileNotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
            AppError::Geometry(_) => (StatusCode::BAD_REQUEST, self.to_string()),
            AppError::Database(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
            AppError::Internal(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
        };

        let body = json!({ "error": message });
        (status, axum::Json(body)).into_response()
    }
}