majorsilence-reporting
Genera informes RDL en PDF, Excel, CSV y más desde Rust — mediante FFI nativa en proceso a través de libloading. Sin runtime de .NET. Sin subprocesos. Handle de biblioteca seguro para hilos, respaldado por Arc. Una sola dependencia.
Instalación
El crate aún no está publicado en crates.io. Añádelo como dependencia de ruta (path):
# Cargo.toml [dependencies] majorsilence-reporting = { path = "/path/to/reporting-rust" } # Una vez publicado en crates.io: # majorsilence-reporting = "1.0"
La única dependencia en tiempo de ejecución es libloading = "0.8". Edición 2021 de Rust, versión mínima 1.65.
Inicio rápido
use majorsilence_reporting::RdlLibrary; fn main() -> Result<(), Box<dyn std::error::Error>> { // Cargar la biblioteca nativa una vez por proceso. let lib = RdlLibrary::load("/path/to/rdlnative/librdlnative.so")?; // Construir una configuración de informe. let mut rpt = lib.report("/path/to/report.rdl"); rpt.set_connection_string("Data Source=/path/to/northwindEF.db") .set_parameter("Country", "Germany"); // Exportar a un archivo. rpt.export("pdf", "/tmp/output.pdf")?; // O exportar a memoria — no se escribe ningún archivo temporal. let pdf_bytes: Vec<u8> = rpt.export_to_memory("pdf")?; println!("Se obtuvieron {} bytes", pdf_bytes.len()); Ok(()) }
Carga de la biblioteca
RdlLibrary::load realiza toda la configuración necesaria: define RDLNATIVE_LIB_DIR, precarga los archivos .so / .dylib asociados con RTLD_GLOBAL para que el resolutor P/Invoke de .NET pueda encontrarlos, resuelve todos los símbolos FFI, y llama a rdl_init().
use majorsilence_reporting::RdlLibrary; // Llamar una vez al iniciar el proceso. RdlLibrary está respaldado por Arc — clonación económica y Send + Sync. let lib = RdlLibrary::load("/path/to/librdlnative.so")?; // Clonarlo entre hilos (por ejemplo en un estado de Axum o Data de Actix) let lib2 = lib.clone(); std::thread::spawn(move || { let mut rpt = lib2.report("/path/to/report.rdl"); rpt.export("pdf", "/tmp/thread.pdf").unwrap(); });
LD_LIBRARY_PATH (Linux) o DYLD_LIBRARY_PATH (macOS) debe incluir el directorio de la biblioteca nativa antes de que arranque el proceso. RdlLibrary::load gestiona RDLNATIVE_LIB_DIR y la precarga de bibliotecas asociadas, pero no puede modificar retroactivamente la ruta de búsqueda del enlazador dinámico para el proceso en ejecución.
API principal
Los métodos constructores devuelven &mut Self para permitir encadenamiento. Se abre y se cierra un nuevo handle de informe nativo en cada llamada a export o export_to_memory, por lo que un valor Report puede reutilizarse en varios renderizados.
| Método | Descripción |
|---|---|
| RdlLibrary::load(path) | Carga la biblioteca nativa e inicializa la tabla de símbolos FFI. Devuelve Result<RdlLibrary, Error>. Llamar una vez por proceso. |
| lib.report(rdl_path) | Crea un valor Report vinculado a un archivo .rdl. Económico — todavía no se abre ningún handle nativo. |
| rpt.set_connection_string(cs) | Establece la cadena de conexión a la base de datos. Devuelve &mut Self. |
| rpt.set_parameter(name, value) | Define un parámetro de informe con nombre. Devuelve &mut Self. Se puede llamar tantas veces como sea necesario. |
| rpt.add_data(name, rows) | Suministra un conjunto de datos en memoria. rows es de tipo Vec<HashMap<String, String>>. Devuelve &mut Self. Ver Conjuntos de datos en memoria. |
| rpt.export(format, path) | Renderiza y escribe en una ruta de archivo. Devuelve Result<(), Error>. |
| rpt.export_to_memory(format) | Renderiza y devuelve los bytes. Devuelve Result<Vec<u8>, Error>. No se escribe ningún archivo temporal. |
Conjuntos de datos en memoria
Pasa datos estructurados directamente al informe sin necesidad de una base de datos. Cada fila es un HashMap<String, String> cuyas claves son los nombres de columna y cuyos valores son los campos codificados como cadenas.
use std::collections::HashMap; use majorsilence_reporting::RdlLibrary; let lib = RdlLibrary::load("/path/to/librdlnative.so")?; let mut rpt = lib.report("/path/to/report.rdl"); let rows: Vec<HashMap<String, String>> = vec![ [("Product", "PDF Library Pro"), ("Revenue", "1200.00"), ("Units", "3")] .iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(), [("Product", "Report Designer"), ("Revenue", "250.00"), ("Units", "1")] .iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(), ]; rpt.add_data("SalesData", rows); let pdf = rpt.export_to_memory("pdf")?;
.rdl. Todos los valores de campo deben ser cadenas — convierte números, fechas y booleanos antes de insertarlos en el mapa. Se pueden suministrar varios conjuntos de datos con varias llamadas a add_data.
Exportar a memoria
export_to_memory llama a la función FFI nativa rdl_report_render_buffer, que rellena un búfer asignado del lado nativo y devuelve un puntero y una longitud. El wrapper de Rust copia los bytes en un Vec<u8> y llama a rdl_free. No se escribe ningún archivo temporal en ningún momento — útil para cuerpos de respuesta HTTP o integración en pipelines.
// Ejemplo de handler de Axum async fn report_handler(State(lib): State<RdlLibrary>) -> impl IntoResponse { let mut rpt = lib.report("/reports/sales.rdl"); rpt.set_connection_string("Data Source=/data/northwindEF.db"); match rpt.export_to_memory("pdf") { Ok(bytes) => ( StatusCode::OK, [("content-type", "application/pdf")], bytes, ).into_response(), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), } }
Gestión de errores
Todas las operaciones que pueden fallar devuelven Result<T, majorsilence_reporting::Error>. Error implementa std::error::Error y encapsula el mensaje devuelto por la función nativa rdl_last_error().
match rpt.export_to_memory("pdf") { Ok(bytes) => { /* usar bytes */ } Err(e) => eprintln!("fallo al renderizar: {e}"), } // O propagar con ? let bytes = rpt.export_to_memory("pdf")?;
"pdf". Para enumerar los formatos válidos en tiempo de compilación, usa la constante pública majorsilence_reporting::VALID_FORMATS: &[&str].
Formatos de exportación
Se admiten los diez formatos. A diferencia de los modos de subproceso de Python/PHP/Ruby, todos los formatos están disponibles en el wrapper de Rust porque siempre usa la vía FFI nativa.
| Cadena de formato | Salida |
|---|---|
pdf | PDF (también el valor de reserva para cualquier cadena no reconocida) |
csv | Valores separados por comas |
xlsx | Libro de Excel |
xlsx_table | Libro de Excel (estilo tabla) |
xml | Datos XML |
rtf | Rich Text Format |
tif | Imagen TIFF (color) |
tifb | Imagen TIFF (blanco y negro) |
html | HTML |
mht | Archivo web MHTML |