athena_rs 2.9.1

Database gateway API
Documentation
//! Shared API response builder for consistent JSON envelope formatting.
//!
//! ## API response contract
//!
//! Every endpoint in the Athena API **must** document and support at least:
//! - **One 2xx response** (e.g. `200 OK`, `201 Created`, `204 No Content`) for success.
//! - **One 5xx response** (e.g. `500 Internal Server Error`, `503 Service Unavailable`) for server-side failures.
//!
//! **Optional:** One or more 3xx responses (e.g. `308 Permanent Redirect`) where redirects are used.
//!
//! Handlers should use the helpers below so that success paths return 2xx and
//! unexpected or backend failures return 5xx via [`internal_error`] or [`service_unavailable`].
//! The OpenAPI spec reflects this contract so every operation lists at least one 2xx and one 5xx.
//!
//! ## Envelope structure
//!
//! Success and error responses use a uniform JSON envelope:
//!
//! ```json
//! {
//!   "status": "success" | "error",
//!   "message": "...",
//!   "data": { ... },       // present on success
//!   "error": "..."         // present on error
//! }
//! ```
//!
//! This module provides typed builders that set the correct HTTP status codes
//! and produce the envelope automatically.

use actix_web::HttpResponse;
use serde::Serialize;
use serde_json::{Value, json};

use crate::error::ProcessedError;

/// Standard envelope for successful responses.
#[derive(Serialize)]
pub struct ApiSuccessResponse<T: Serialize> {
    pub status: &'static str,
    pub message: String,
    pub data: T,
}

/// Standard envelope for error responses.
#[derive(Serialize)]
pub struct ApiErrorResponse {
    pub status: &'static str,
    pub message: String,
    pub error: String,
}

/// Build a `200 OK` response with the standard success envelope.
///
/// # Example
/// ```ignore
/// api_success("Fetched records", json!({ "rows": rows }))
/// ```
pub fn api_success<T: Serialize>(message: impl Into<String>, data: T) -> HttpResponse {
    HttpResponse::Ok().json(ApiSuccessResponse {
        status: "success",
        message: message.into(),
        data,
    })
}

/// Build a `200 OK` response returning raw data (for backwards compatibility
/// where the envelope is not expected).
pub fn api_ok<T: Serialize>(data: T) -> HttpResponse {
    HttpResponse::Ok().json(data)
}

/// Build a `201 Created` response with the standard success envelope.
pub fn api_created<T: Serialize>(message: impl Into<String>, data: T) -> HttpResponse {
    HttpResponse::Created().json(ApiSuccessResponse {
        status: "success",
        message: message.into(),
        data,
    })
}

/// Build a `400 Bad Request` response with the standard error envelope.
pub fn bad_request(message: impl Into<String>, error: impl Into<String>) -> HttpResponse {
    HttpResponse::BadRequest().json(ApiErrorResponse {
        status: "error",
        message: message.into(),
        error: error.into(),
    })
}

/// Build a `401 Unauthorized` response.
pub fn unauthorized(message: impl Into<String>, error: impl Into<String>) -> HttpResponse {
    HttpResponse::Unauthorized().json(ApiErrorResponse {
        status: "error",
        message: message.into(),
        error: error.into(),
    })
}

/// Build a `403 Forbidden` response.
pub fn forbidden(message: impl Into<String>, error: impl Into<String>) -> HttpResponse {
    HttpResponse::Forbidden().json(ApiErrorResponse {
        status: "error",
        message: message.into(),
        error: error.into(),
    })
}

/// Build a `404 Not Found` response.
pub fn not_found(message: impl Into<String>, error: impl Into<String>) -> HttpResponse {
    HttpResponse::NotFound().json(ApiErrorResponse {
        status: "error",
        message: message.into(),
        error: error.into(),
    })
}

/// Build a `409 Conflict` response.
pub fn conflict(message: impl Into<String>, error: impl Into<String>) -> HttpResponse {
    HttpResponse::Conflict().json(ApiErrorResponse {
        status: "error",
        message: message.into(),
        error: error.into(),
    })
}

/// Build a `500 Internal Server Error` response.
pub fn internal_error(message: impl Into<String>, error: impl Into<String>) -> HttpResponse {
    HttpResponse::InternalServerError().json(ApiErrorResponse {
        status: "error",
        message: message.into(),
        error: error.into(),
    })
}

/// Build a `503 Service Unavailable` response.
pub fn service_unavailable(message: impl Into<String>, error: impl Into<String>) -> HttpResponse {
    HttpResponse::ServiceUnavailable().json(ApiErrorResponse {
        status: "error",
        message: message.into(),
        error: error.into(),
    })
}

/// Build a `502 Bad Gateway` response (e.g. upstream connection failure).
pub fn bad_gateway(message: impl Into<String>, error: impl Into<String>) -> HttpResponse {
    HttpResponse::BadGateway().json(ApiErrorResponse {
        status: "error",
        message: message.into(),
        error: error.into(),
    })
}

/// Build a success response wrapping a `serde_json::Value`.
pub fn api_success_value(message: impl Into<String>, data: Value) -> HttpResponse {
    HttpResponse::Ok().json(json!({
        "status": "success",
        "message": message.into(),
        "data": data
    }))
}

/// Build an error response from a plain string (shorthand).
pub fn api_error(message: impl Into<String>) -> HttpResponse {
    let msg: String = message.into();
    internal_error(&msg, &msg)
}

/// Build an error response from a ProcessedError with proper status code and formatting.
///
/// This function converts a `ProcessedError` (which contains sanitized error information,
/// user-friendly messages, and safe metadata) into an HTTP response with the appropriate
/// status code.
///
/// # Example
/// ```ignore
/// use athena_rs::error::sqlx_parser::process_sqlx_error;
///
/// let processed_error = process_sqlx_error(&sqlx_err);
/// return processed_error(processed_error);
/// ```
pub fn processed_error(error: ProcessedError) -> HttpResponse {
    let status = error.status_code;
    let json = error.to_json();
    HttpResponse::build(status).json(json)
}