vl-convert-server 2.0.0-rc2

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

use vl_convert_rs::converter::{JpegOpts, PdfOpts, PngOpts};

use crate::config::AppState;
use crate::types::{ErrorResponse, SvgJpegRequest, SvgPdfRequest, SvgPngRequest};
use crate::util::{
    append_vlc_logs_header, attach_google_font_usage, conversion_error_response, format_log_entries,
};

#[utoipa::path(
    post,
    path = "/svg/png",
    request_body = SvgPngRequest,
    responses(
        (status = 200, content_type = "image/png", description = "PNG image"),
        (status = 400, body = ErrorResponse, description = "Invalid request"),
        (status = 422, body = ErrorResponse, description = "Conversion failed"),
    ),
    tag = "SVG"
)]
pub async fn svg_to_png(
    State(state): State<Arc<AppState>>,
    Json(req): Json<SvgPngRequest>,
) -> Response {
    let snap = state.runtime.load_full();
    let png_opts = PngOpts {
        scale: req.scale,
        ppi: req.ppi,
    };

    match snap.converter.svg_to_png(&req.svg, png_opts).await {
        Ok(output) => {
            let mut headers = HeaderMap::new();
            append_vlc_logs_header(&mut headers, &format_log_entries(&output.logs));
            let mut response = (
                headers,
                [(axum::http::header::CONTENT_TYPE, "image/png")],
                output.data,
            )
                .into_response();
            attach_google_font_usage(&mut response, output.google_fonts);
            response
        }
        Err(e) => conversion_error_response(
            StatusCode::UNPROCESSABLE_ENTITY,
            "SVG to PNG conversion failed",
            &e,
            state.opaque_errors,
        ),
    }
}

#[utoipa::path(
    post,
    path = "/svg/jpeg",
    request_body = SvgJpegRequest,
    responses(
        (status = 200, content_type = "image/jpeg", description = "JPEG image"),
        (status = 400, body = ErrorResponse, description = "Invalid request"),
        (status = 422, body = ErrorResponse, description = "Conversion failed"),
    ),
    tag = "SVG"
)]
pub async fn svg_to_jpeg(
    State(state): State<Arc<AppState>>,
    Json(req): Json<SvgJpegRequest>,
) -> Response {
    let snap = state.runtime.load_full();
    let jpeg_opts = JpegOpts {
        scale: req.scale,
        quality: req.quality,
    };

    match snap.converter.svg_to_jpeg(&req.svg, jpeg_opts).await {
        Ok(output) => {
            let mut headers = HeaderMap::new();
            append_vlc_logs_header(&mut headers, &format_log_entries(&output.logs));
            let mut response = (
                headers,
                [(axum::http::header::CONTENT_TYPE, "image/jpeg")],
                output.data,
            )
                .into_response();
            attach_google_font_usage(&mut response, output.google_fonts);
            response
        }
        Err(e) => conversion_error_response(
            StatusCode::UNPROCESSABLE_ENTITY,
            "SVG to JPEG conversion failed",
            &e,
            state.opaque_errors,
        ),
    }
}

#[utoipa::path(
    post,
    path = "/svg/pdf",
    request_body = SvgPdfRequest,
    responses(
        (status = 200, content_type = "application/pdf", description = "PDF document"),
        (status = 400, body = ErrorResponse, description = "Invalid request"),
        (status = 422, body = ErrorResponse, description = "Conversion failed"),
    ),
    tag = "SVG"
)]
pub async fn svg_to_pdf(
    State(state): State<Arc<AppState>>,
    Json(req): Json<SvgPdfRequest>,
) -> Response {
    let snap = state.runtime.load_full();
    match snap
        .converter
        .svg_to_pdf(&req.svg, PdfOpts::default())
        .await
    {
        Ok(output) => {
            let mut headers = HeaderMap::new();
            append_vlc_logs_header(&mut headers, &format_log_entries(&output.logs));
            let mut response = (
                headers,
                [(axum::http::header::CONTENT_TYPE, "application/pdf")],
                output.data,
            )
                .into_response();
            attach_google_font_usage(&mut response, output.google_fonts);
            response
        }
        Err(e) => conversion_error_response(
            StatusCode::UNPROCESSABLE_ENTITY,
            "SVG to PDF conversion failed",
            &e,
            state.opaque_errors,
        ),
    }
}