prost-wkt-types 0.6.0

Helper crate for prost to allow JSON serialization and deserialization of Well Known Types.
Documentation
# *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::<Foo>()
            .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<some_one::Body>,
}
/// 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<some_one::Body>,
}
/// 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