Implementación de serialización JSON para código Rust generado con prost

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.

Etiquetas: Rust prost protobuf serde serialización JSON

Publicado el 5-30 23:05