# *PROST Well Known Types JSON Serialization and Deserialization* # [![crates.io](https://buildstats.info/crate/prost-wkt-types)](https://crates.io/crates/prost-wkt-types) [![build](https://github.com/fdeantoni/prost-wkt/actions/workflows/rust.yml/badge.svg)](https://github.com/fdeantoni/prost-wkt/actions/workflows/rust.yml) [Prost](https://github.com/tokio-rs/prost) is a [Protocol Buffers](https://developers.google.com/protocol-buffers/) implementation for the [Rust Language](https://www.rust-lang.org/) that generates simple, idiomatic Rust code from `proto2` and `proto3` files. It includes `prost-types` which gives basic support for protobuf Well-Known-Types (WKT), but support is basic. For example, it does not include packing or unpacking of messages in the `Any` type, nor much support in the way of JSON serialization and deserialization of that type. This crate can help you if you need: - helper methods for packing and unpacking messages to/from an [Any](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Any), - helper methods for converting [chrono](https://github.com/chronotope/chrono) types to [Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) and back again, - helper methods for converting common rust types to [Value](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Value) and back again, - serde support for the types above. To use it, include this crate along with prost: ```toml [dependencies] prost = "0.13" prost-wkt = "0.6" prost-wkt-types = "0.6" serde = { version = "1.0", features = ["derive"] } [build-dependencies] prost-build = "0.13" prost-wkt-build = "0.6" ``` In your `build.rs`, make sure to add the following options: ```rust use std::{env, path::PathBuf}; use prost_wkt_build::*; fn main() { let out = PathBuf::from(env::var("OUT_DIR").unwrap()); let descriptor_file = out.join("descriptors.bin"); let mut prost_build = prost_build::Config::new(); prost_build .type_attribute( ".", "#[derive(serde::Serialize,serde::Deserialize)]" ) .extern_path( ".google.protobuf.Any", "::prost_wkt_types::Any" ) .extern_path( ".google.protobuf.Timestamp", "::prost_wkt_types::Timestamp" ) .extern_path( ".google.protobuf.Value", "::prost_wkt_types::Value" ) .file_descriptor_set_path(&descriptor_file) .compile_protos( &[ "proto/messages.proto" ], &["proto/"], ) .unwrap(); let descriptor_bytes = std::fs::read(descriptor_file) .unwrap(); let descriptor = FileDescriptorSet::decode(&descriptor_bytes[..]) .unwrap(); prost_wkt_build::add_serde(out, descriptor); } ``` The above configuration will include `Serialize`, and `Deserialize` on each generated struct. This will allow you to use `serde` fully. Moreover, it ensures that the `Any` type is deserialized properly as JSON. For example, assume we have the following messages defined in our proto file: ```proto syntax = "proto3"; import "google/protobuf/any.proto"; import "google/protobuf/timestamp.proto"; package my.pkg; message Request { string requestId = 1; google.protobuf.Any payload = 2; } message Foo { string data = 1; google.protobuf.Timestamp timestamp = 2; } ``` After generating the rust structs for the above using `prost-build` with the above configuration, you will then be able to do the following: ```rust use serde::{Deserialize, Serialize}; use chrono::prelude::*; use prost_wkt_types::*; include!(concat!(env!("OUT_DIR"), "/my.pkg.rs")); fn main() -> Result<(), AnyError> { let foo_msg: Foo = Foo { data: "Hello World".to_string(), timestamp: Some(Utc::now().into()), }; let mut request: Request = Request::default(); let any = Any::try_pack(foo_msg)?; request.request_id = "test1".to_string(); request.payload = Some(any); let json = serde_json::to_string_pretty(&request).expect("Failed to serialize request"); println!("JSON:\n{}", json); let back: Request = serde_json::from_str(&json).expect("Failed to deserialize request"); if let Some(payload) = back.payload { let unpacked: Box< dyn MessageSerde> = payload.try_unpack()?; let unpacked_foo: &Foo = unpacked .downcast_ref::() .expect("Failed to downcast payload to Foo"); println!("Unpacked: {:?}", unpacked_foo); } } ``` The above will generate the following stdout: ``` JSON: { "requestId": "test1", "payload": { "@type": "type.googleapis.com/my.pkg.Foo", "data": "Hello World", "timestamp": "2020-05-25T12:19:57.755998Z" } } Unpacked: Foo { data: "Hello World", timestamp: Some(Timestamp { seconds: 1590409197, nanos: 755998000 }) } ``` Notice that the request message is properly serialized to JSON as per the [protobuf specification](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Any), and that it can be deserialized as well. See the `example` sub-project for a fully functioning example. ## Known Problems ## ### oneOf types ### The way `prost-build` generates the `oneOf` type is to place it in a sub module, for example: ```proto message SomeOne { oneof body { string some_string = 1; bool some_bool = 2; float some_float = 3; } } ``` is converted to rust as follows: ```rust #[derive(Serialize, Deserialize)] #[derive(Clone, PartialEq, ::prost::Message)] #[prost(package="my.pkg")] pub struct SomeOne { #[prost(oneof="some_one::Body", tags="1, 2, 3")] pub body: ::core::option::Option, } /// Nested message and enum types in `SomeOne`. pub mod some_one { #[derive(Serialize, Deserialize)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Body { #[prost(string, tag="1")] SomeString(::prost::alloc::string::String), #[prost(bool, tag="2")] SomeBool(bool), #[prost(float, tag="3")] SomeFloat(f32), } } ``` However, rust requires the importation of macros in each module, so each should have the following added: ```rust use serde::{Serialize, Deserialize}; ``` In the generated code snippet, the above statement is missing in the `some_one` module, and the rust compiler will complain about it. To fix it, we would have to add the appropriate use statement in the `some_one` module like so: ```rust #[derive(Serialize, Deserialize)] #[derive(Clone, PartialEq, ::prost::Message)] #[prost(package="my.pkg")] pub struct SomeOne { #[prost(oneof="some_one::Body", tags="1, 2, 3")] pub body: ::core::option::Option, } /// Nested message and enum types in `SomeOne`. pub mod some_one { use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Body { #[prost(string, tag="1")] SomeString(::prost::alloc::string::String), #[prost(bool, tag="2")] SomeBool(bool), #[prost(float, tag="3")] SomeFloat(f32), } } ``` Luckily, you can achieve the above by tweaking the `build.rs`. The configuration below, for example, will add the required serde import to the `some_one` module as needed: ```rust fn main() { let out = PathBuf::from(env::var("OUT_DIR").unwrap()); let descriptor_file = out.join("descriptors.bin"); let mut prost_build = prost_build::Config::new(); prost_build .type_attribute( ".my.pkg.MyEnum", "#[derive(serde::Serialize,serde::Deserialize)]" ) .type_attribute( ".my.pkg.MyMessage", "#[derive(serde::Serialize,serde::Deserialize)] #[serde(default)]" ) .type_attribute( ".my.pkg.SomeOne.body", "#[derive(serde::Serialize,serde::Deserialize)]" ) .extern_path( ".google.protobuf.Any", "::prost_wkt_types::Any" ) .extern_path( ".google.protobuf.Timestamp", "::prost_wkt_types::Timestamp" ) .extern_path( ".google.protobuf.Value", "::prost_wkt_types::Value" ) .file_descriptor_set_path(&descriptor_file) .compile_protos( &[ "proto/messages.proto" ], &["proto/"], ) .unwrap(); let descriptor_bytes = std::fs::read(descriptor_file).unwrap(); let descriptor = FileDescriptorSet::decode(&descriptor_bytes[..]).unwrap(); prost_wkt_build::add_serde(out, descriptor); } ``` ## Development ## Contributions are welcome! ### Upgrading Prost ### When upgrading Prost to the latest version, make sure the latest changes from `prost-types` are incorporated into `prost-wkt-types` to ensure full compatibility. Currently the `Name` traits have specifically not been implemented until this implementation in Prost has fully stabilized. ## MSRV ## The minimum supported Rust version is Rust 1.70.0. ## License ## `prost-wkt` is distributed under the terms of the Apache License (Version 2.0). See [LICENSE](LICENSE) for details. Copyright 2023 Ferdinand de Antoni