La integración de la API de Bots de Telegram con sistemas escritos en Rust a menudo requiere el procesamiento de respuestas en formato JSON. Una alternativa eficiente es utilizar Protocol Buffers para definir esquemas de datos y generar automáticamente estructuras de datos en Rust que puedan serializarse y deserializarse a JSON mediante serde.
Para ilustrar el enfoque inicial, conisderemos la definición manual de una estructura con serde:
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Coordenada {
eje_x: i32,
eje_y: i32,
}
fn main() {
let punto = Coordenada { eje_x: 5, eje_y: 10 };
let datos_json = serde_json::to_string(&punto).unwrap();
println!("JSON: {}", datos_json);
let punto_restaurado: Coordenada = serde_json::from_str(&datos_json).unwrap();
println!("Restaurado: {:?}", punto_restaurado);
}
Aunque funcional, este método implica crear manualmente cada estructura. Una alternatvia sería definir los esquemas en JSON y usar macros para generación de código, pero aún requiere escribir definiciones JSON extensas.
Una solución más elegante consiste en emplear archivos .proto para la definición de esquemas:
syntax = "proto3";
package ejemplo;
message Usuario {
int64 identificador = 1;
bool es_bot = 2;
string nombre = 3;
optional string apellido = 4;
optional string idioma = 5;
}
Para que las estructuras generadas por prost soporten serde, se puede configurar prost-build durante la compilación. La clave está en aplicar atributos derive a nivel global en la configuración:
// build.rs
use std::io::Result;
fn main() -> Result<()> {
let mut configuracion = prost_build::Config::new();
configuracion.out_dir("src/proto");
configuracion.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");
configuracion.compile_protos(&["src/proto/esquema.proto"], &["src/proto"])?;
Ok(())
}
Al compilar el proyecto, prost generará estructuras con las macros de serde incluidas. El archivo resultante contendrá definiciones similares a:
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Usuario {
#[prost(int64, tag="1")]
pub identificador: i64,
#[prost(bool, tag="2")]
pub es_bot: bool,
#[prost(string, tag="3")]
pub nombre: ::prost::alloc::string::String,
#[prost(string, optional, tag="4")]
pub apellido: ::core::option::Option<::prost::alloc::string::String>,
#[prost(string, optional, tag="5")]
pub idioma: ::core::option::Option<::prost::alloc::string::String>,
}
Se puede verificar la funcionalidad con pruebas unitarias que validen la conversión bidireccional entre estructuras Rust y JSON:
#[cfg(test)]
mod pruebas {
mod proto {
include!("proto/esquema.rs");
}
#[test]
fn verificacion_serializacion() {
let usuario = proto::Usuario::default();
let representacion = serde_json::to_string(&usuario).unwrap();
let usuario_deserializado: proto::Usuario = serde_json::from_str(&representacion).unwrap();
assert_eq!(usuario.identificador, usuario_deserializado.identificador);
assert_eq!(usuario.es_bot, usuario_deserializado.es_bot);
assert_eq!(usuario.nombre, usuario_deserializado.nombre);
}
}
Este enfoque permite automatizar completamente el flujo de trabajo: desde la definición de esquemas en .proto hasta la obtención de estructuras Rust listas para consumir y producir JSON, manteniendo la compatibilidad con el ecosistema serde.