vl-convert-server 2.0.0-rc2

HTTP server for converting Vega-Lite and Vega specifications to static images
use axum::extract::{Path, State};
use axum::http::{HeaderMap, StatusCode};
use axum::response::{IntoResponse, Json, Response};
use itertools::Itertools;
use serde_json::Value;
use std::sync::Arc;

use crate::config::AppState;
use crate::util::{append_vlc_logs_header, error_response};

#[utoipa::path(
    get,
    path = "/themes",
    responses(
        (status = 200, content_type = "application/json", description = "List of theme names"),
        (status = 500, body = crate::types::ErrorResponse, description = "Internal error"),
    ),
    tag = "Themes"
)]
pub async fn list_themes(State(state): State<Arc<AppState>>) -> Response {
    let snap = state.runtime.load_full();
    let result = snap.converter.get_themes().await;
    let mut headers = HeaderMap::new();
    append_vlc_logs_header(&mut headers, &[]);

    match result {
        Ok(Value::Object(themes)) => {
            let names: Vec<String> = themes.keys().sorted().cloned().collect();
            (
                headers,
                Json(Value::Array(names.into_iter().map(Value::String).collect())),
            )
                .into_response()
        }
        Ok(_) => error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "unexpected themes format",
            state.opaque_errors,
        ),
        Err(e) => error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            &format!("Failed to load themes: {e}"),
            state.opaque_errors,
        ),
    }
}

#[utoipa::path(
    get,
    path = "/themes/{name}",
    params(
        ("name" = String, Path, description = "Theme name"),
    ),
    responses(
        (status = 200, content_type = "application/json", description = "Theme configuration object"),
        (status = 404, body = crate::types::ErrorResponse, description = "Theme not found"),
        (status = 500, body = crate::types::ErrorResponse, description = "Internal error"),
    ),
    tag = "Themes"
)]
pub async fn get_theme(State(state): State<Arc<AppState>>, Path(name): Path<String>) -> Response {
    let snap = state.runtime.load_full();
    let result = snap.converter.get_themes().await;
    let mut headers = HeaderMap::new();
    append_vlc_logs_header(&mut headers, &[]);

    match result {
        Ok(Value::Object(themes)) => {
            if let Some(theme_config) = themes.get(&name) {
                (headers, Json(theme_config.clone())).into_response()
            } else {
                error_response(
                    StatusCode::NOT_FOUND,
                    &format!("unknown theme: {name}"),
                    state.opaque_errors,
                )
            }
        }
        Ok(_) => error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "unexpected themes format",
            state.opaque_errors,
        ),
        Err(e) => error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            &format!("Failed to load themes: {e}"),
            state.opaque_errors,
        ),
    }
}