So you don't want to change the .proto
, and want to deserialize from the bytes
according to id
. In this case, it is deserializing from sub_message ([Vec<u8> in Rust)
according to define_type (i32 in Rust)
.
Here I give a concreter example:
In proto:
syntax = "proto3";
package foo;
message Msg {
int32 type = 1;
bytes buffer = 2;
}
In build.rs:
use protobuf_codegen::Codegen;
fn main() {
Codegen::new()
.pure()
.include("proto")
.input("proto/foo.proto")
.cargo_out_dir("proto")
.run()
.unwrap();
// below is used to remove all `#!` in generated code,
// because `#!` is not allowed in `include!` used after
let path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("proto/foo.rs");
let gen = std::fs::read_to_string(&path).unwrap();
let processed = gen.replace("#!", "//").replace("//!", "//");
std::fs::write(path, processed).unwrap();
println!("cargo:return-if-changed=proto");
println!("cargo:return-if-changed=build.rs");
}
main.rs:
// --decrease the warnings
#![allow(unknown_lints)]
#![allow(clippy::all)]
#![allow(unused_attributes)]
#![cfg_attr(rustfmt, rustfmt::skip)]
#![allow(box_pointers)]
#![allow(dead_code)]
#![allow(missing_docs)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(trivial_casts)]
#![allow(unused_results)]
#![allow(unused_mut)]
// --decrease the warnings
// include generated code
include!(concat!(env!("OUT_DIR"), "/proto/foo.rs"));
use protobuf_json_mapping::PrintOptions;
static OPTION: PrintOptions = PrintOptions {
enum_values_int: true,
proto_field_name: true,
always_output_default_values: true,
_future_options: (),
};
fn main() {
let msgs = [
Msg {
type_: 1,
buffer: "hello".into(),
..Default::default()
},
Msg {
type_: 2,
buffer: vec![1, 2, 3],
..Default::default()
},
];
for m in msgs {
let s = protobuf_json_mapping::print_to_string_with_options(&m, &OPTION).unwrap();
println!("{}", s);
}
}
The output is:
{"type": 1, "buffer": "aGVsbG8="}
{"type": 2, "buffer": "AQID"}
It's absolutely wrong. This is because print_to_string_with_options
doesn't know how to convert the bytes into human readable str.
So, instead of using protobuf_json_mapping
, we can seriliaze it by ourselves. (Maybe protobuf_json_mapping
is better at deseriliazing)
include!(concat!(env!("OUT_DIR"), "/proto/foo.rs"));
use std::str::from_utf8_unchecked;
use serde::Serialize;
use serde::ser::SerializeMap;
impl Serialize for Msg {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", &self.type_)?;
match self.type_ {
1 => map.serialize_entry("buffer", unsafe{&from_utf8_unchecked(&self.buffer)})?,
2 => map.serialize_entry("buffer", &self.buffer)?,
_ => {}
}
map.end()
}
}
fn main() {
let msgs = [
Msg {
type_: 1,
buffer: "hello".into(),
..Default::default()
},
Msg {
type_: 2,
buffer: vec![1, 2, 3],
..Default::default()
},
];
for m in msgs {
let s = serde_json::to_string_pretty(&m).unwrap();
println!("{}", s);
}
}
This is the output:
{
"type": 1,
"buffer": "hello"
}
{
"type": 2,
"buffer": "[1, 2, 3]"
}
Above may be what you want! Both you and me may be not native speakers, hope that I understood your purpose.