graphile_worker_admin_ui 0.2.1

Embedded Leptos admin UI for graphile_worker
Documentation
use std::fmt;

use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use graphile_worker_admin_api::queries::error::AdminQueryError;
use thiserror::Error;

use super::types::ErrorResponse;

#[derive(Debug, Error)]
pub enum AdminUiError {
    #[error("admin auth without credentials is only allowed on loopback addresses")]
    InsecureNoAuth,
    #[error("invalid header name `{0}`")]
    InvalidHeaderName(String),
    #[error("failed to bind admin UI listener: {0}")]
    Bind(#[from] std::io::Error),
    #[error("failed to serve admin UI: {0}")]
    Serve(std::io::Error),
}

pub(crate) fn json_error_response(status: StatusCode, message: &str) -> Response {
    (
        status,
        Json(ErrorResponse {
            error: message.to_string(),
        }),
    )
        .into_response()
}

#[derive(Debug, thiserror::Error)]
#[error("{message}")]
pub(crate) struct ApiError {
    pub(crate) status: StatusCode,
    pub(crate) message: String,
}

impl ApiError {
    pub(crate) fn bad_request(message: impl Into<String>) -> Self {
        Self {
            status: StatusCode::BAD_REQUEST,
            message: message.into(),
        }
    }

    pub(crate) fn forbidden(message: impl Into<String>) -> Self {
        Self {
            status: StatusCode::FORBIDDEN,
            message: message.into(),
        }
    }

    pub(crate) fn not_found(message: impl Into<String>) -> Self {
        Self {
            status: StatusCode::NOT_FOUND,
            message: message.into(),
        }
    }

    pub(crate) fn internal(error: impl fmt::Display) -> Self {
        tracing::error!(error = %error, "admin UI request failed");
        Self {
            status: StatusCode::INTERNAL_SERVER_ERROR,
            message: "internal server error".to_string(),
        }
    }
}

impl From<sqlx::Error> for ApiError {
    fn from(value: sqlx::Error) -> Self {
        match value {
            sqlx::Error::RowNotFound => Self::not_found("resource not found"),
            error => Self::internal(error),
        }
    }
}

impl From<AdminQueryError> for ApiError {
    fn from(value: AdminQueryError) -> Self {
        match value {
            AdminQueryError::BadRequest(message) => Self::bad_request(message),
            AdminQueryError::NotFound(message) => Self::not_found(message),
            AdminQueryError::Sqlx(error) => error.into(),
        }
    }
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        json_error_response(self.status, &self.message)
    }
}