\usepackage{rust}
Compile LaTeX a PDF desde Rust con la API REST de FormaTeX. reqwest asíncrono + tokio: sin instalar TeX Live y sin binarios que empaquetar.
\section{Por qué Rust}
El cliente asíncrono de reqwest encaja de forma natural con el runtime de Tokio. Compile varios documentos a la vez sin complejidad adicional.
Su binario de Rust se mantiene pequeño: sin TeX Live y sin apt install texlive-full. FormaTeX se encarga por completo del entorno de compilación.
El tipo Result de Rust combina muy bien con la API de FormaTeX. Convierta los errores HTTP en errores de dominio a nivel de tipos, sin sorpresas en tiempo de ejecución.
\section{Dependencies}
[dependencies] reqwest = { version = "0.12", features = ["json"] } tokio = { version = "1", features = ["full"] } serde_json = "1" serde = { version = "1", features = ["derive"] } # For Axum integration axum = "0.7" bytes = "1"
\section{Inicio rápido}
El siguiente ejemplo compila un documento LaTeX mínimo con reqwest y escribe el PDF resultante en disco. Lea su clave de API del entorno con std::env::var("FORMATEX_API_KEY") .
use reqwest::Client;
use serde_json::json;
use std::fs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = std::env::var("FORMATEX_API_KEY")?;
let latex = r#"\documentclass{article}
\usepackage{amsmath}
\begin{document}
Hello from Rust! $E = mc^2$
\end{document}"#;
let client = Client::new();
let response = client
.post("https://api.formatex.io/api/v1/compile")
.header("X-API-Key", &api_key)
.json(&json!({ "latex": latex, "engine": "pdflatex" }))
.send()
.await?;
if !response.status().is_success() {
let body = response.text().await?;
return Err(format!("API error: {body}").into());
}
let pdf = response.bytes().await?;
fs::write("output.pdf", &pdf)?;
println!("PDF saved to output.pdf");
Ok(())
}\section{Handler de Axum}
Registre un AppState compartido en su router de Axum y monte compile_handler en cualquier ruta. El handler acepta un cuerpo JSON con source y, opcionalmente, engine, y devuelve los bytes del PDF directamente al cliente.
use axum::{
body::Bytes,
extract::State,
http::{HeaderMap, StatusCode},
response::{IntoResponse, Response},
Json,
};
use reqwest::Client;
use serde::Deserialize;
#[derive(Clone)]
pub struct AppState {
pub http: Client,
pub api_key: String,
}
#[derive(Deserialize)]
pub struct CompileRequest {
pub source: String,
#[serde(default = "default_engine")]
pub engine: String,
}
fn default_engine() -> String {
"pdflatex".to_string()
}
pub async fn compile_handler(
State(state): State<AppState>,
Json(body): Json<CompileRequest>,
) -> Result<Response, (StatusCode, String)> {
let res = state
.http
.post("https://api.formatex.io/api/v1/compile")
.header("X-API-Key", &state.api_key)
.json(&serde_json::json!({
"latex": body.source,
"engine": body.engine,
}))
.send()
.await
.map_err(|e| (StatusCode::BAD_GATEWAY, e.to_string()))?;
if !res.status().is_success() {
let msg = res.text().await.unwrap_or_default();
return Err((StatusCode::UNPROCESSABLE_ENTITY, msg));
}
let pdf: Bytes = res
.bytes()
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let mut headers = HeaderMap::new();
headers.insert("Content-Type", "application/pdf".parse().unwrap());
headers.insert(
"Content-Disposition",
"attachment; filename=output.pdf".parse().unwrap(),
);
Ok((headers, pdf).into_response())
}\section{Authentication}
Guarde su clave de API en el entorno; nunca la escriba directamente en los archivos de código. Léala al iniciar y falle de inmediato si no existe:
// Fail fast at startup let api_key = std::env::var( "FORMATEX_API_KEY" ).expect( "FORMATEX_API_KEY not set" );
.env con el crate dotenvy durante el desarrollo local. X-API-Key: <key> .\section{Reference}
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| latex | string | Sí | El código fuente LaTeX completo como cadena de texto |
| engine | string | No | Motor que se usará. Valor predeterminado: "pdflatex". Opciones: pdflatex, xelatex, lualatex, latexmk |
POST https://api.formatex.io/api/v1/compile — Devuelve los bytes del PDF sin procesar si tiene éxito ( 200), o un error JSON si falla (422/ 401).
\section{Gestión de errores}
Compruebe response.status().is_success() antes de leer el cuerpo. Convierta los errores HTTP a su propio tipo de error para propagarlos de forma limpia:
200 — bytes del PDF sin procesar. Léalos con response.bytes().await? .422 — error de compilación de LaTeX. El cuerpo JSON contiene un campo error con un extracto del registro de TeX.401 — clave de API no válida o ausente.\end{rust}
Obtenga su clave de API y consiga un PDF desde Rust en minutos, sin TeX Live.