Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prost-wkt-derive to derive MessageSerde #69

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ edition = "2021"
rust-version = "1.70"

[workspace]
members = [ "wkt-build", "wkt-types", "example" ]
members = ["wkt-types", "wkt-derive", "example"]

[dependencies]
prost = "0.13.1"
prost-wkt-derive = { path = "wkt-derive" }
erased-serde = "0.4"
inventory = "0.3.0"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
chrono = { version = "0.4.27", default-features = false, features = ["serde"] }
typetag = "0.2"
const_format = "0.2.32"
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,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:
Expand Down Expand Up @@ -290,7 +289,7 @@ Contributions are welcome!
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.
stabilized.

## MSRV ##

Expand Down
8 changes: 5 additions & 3 deletions example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ rust-version = "1.70"
prost = "0.13.1"
prost-wkt = { path = ".." }
prost-wkt-types = { path = "../wkt-types" }
serde = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
serde_json = "1.0"
chrono = { version = "0.4.27", default-features = false, features = ["clock", "serde"] }
chrono = { version = "0.4.27", default-features = false, features = [
"clock",
"serde",
] }

[build-dependencies]
prost-build = "0.13.1"
prost-wkt-build = { path = "../wkt-build" }
12 changes: 5 additions & 7 deletions example/build.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
use prost_wkt_build::*;
use std::{env, path::PathBuf};

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
.enable_type_names()
.type_name_domain(&[".my.requests", ".my.messages"], "type.googleapis.com")
.type_attribute(
".my.requests",
"#[derive(serde::Serialize, serde::Deserialize)] #[serde(default, rename_all=\"camelCase\")]",
"#[derive(serde::Serialize, serde::Deserialize, ::prost_wkt::MessageSerde)] #[serde(default, rename_all=\"camelCase\")]",
)
.type_attribute(
".my.messages.Foo",
"#[derive(serde::Serialize, serde::Deserialize)] #[serde(default, rename_all=\"camelCase\")]",
)
.message_attribute(".my.messages.Foo", "#[derive(::prost_wkt::MessageSerde)]")
.type_attribute(
".my.messages.Content",
"#[derive(serde::Serialize, serde::Deserialize)] #[serde(rename_all=\"camelCase\")]",
)
.message_attribute(".my.messages.Content", "#[derive(::prost_wkt::MessageSerde)]")
.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/requests.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);
}
5 changes: 3 additions & 2 deletions example/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ include!(concat!(env!("OUT_DIR"), "/my.messages.rs"));
include!(concat!(env!("OUT_DIR"), "/my.requests.rs"));

fn main() -> Result<(), AnyError> {

let content: Content = Content { body: Some(content::Body::SomeBool(true)) };
let content: Content = Content {
body: Some(content::Body::SomeBool(true)),
};

let foo_msg: Foo = Foo {
data: "Hello World".to_string(),
Expand Down
48 changes: 37 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
pub use inventory;

pub use typetag;
pub use const_format;
pub use erased_serde;
pub use prost_wkt_derive::MessageSerde;

/// Trait to support serialization and deserialization of `prost` messages.
#[typetag::serde(tag = "@type")]
pub trait MessageSerde: prost::Message + std::any::Any {
/// message name as in proto file
fn message_name(&self) -> &'static str;
/// package name as in proto file
fn package_name(&self) -> &'static str;
pub trait MessageSerde: prost::Message + std::any::Any + erased_serde::Serialize {
/// the message proto type url e.g. type.googleapis.com/my.package.MyMessage
fn type_url(&self) -> &'static str;
/// Creates a new instance of this message using the protobuf encoded data
fn new_instance(&self, data: Vec<u8>) -> Result<Box<dyn MessageSerde>, prost::DecodeError>;
fn type_url(&self) -> String;
/// Returns the encoded protobuf message as bytes
fn try_encoded(&self) -> Result<Vec<u8>, prost::EncodeError>;
/// Returns an erased serialize dynamic reference
fn as_erased_serialize(&self) -> &dyn erased_serde::Serialize;
}

/// The implementation here is a direct copy of the `impl dyn` of [`std::any::Any`]!
Expand Down Expand Up @@ -85,11 +82,40 @@ impl dyn MessageSerde {
}
}

type MessageSerdeTypeUrlFn = fn() -> String;
type MessageSerdeDecoderFn = fn(&[u8]) -> Result<Box<dyn MessageSerde>, ::prost::DecodeError>;
type MessageSerdeDeserializerFn =
fn(&mut dyn erased_serde::Deserializer) -> Result<Box<dyn MessageSerde>, erased_serde::Error>;

pub struct MessageSerdeDecoderEntry {
pub type_url: &'static str,
pub type_url: MessageSerdeTypeUrlFn,
pub decoder: MessageSerdeDecoderFn,
pub deserializer: MessageSerdeDeserializerFn,
}

impl MessageSerdeDecoderEntry {
pub const fn new<M>() -> Self
where
for<'a> M: MessageSerde + prost::Name + Default + serde::Deserialize<'a>,
{
Self {
type_url: <M as prost::Name>::type_url,
decoder: |buf| {
let msg: M = prost::Message::decode(buf)?;
Ok(Box::new(msg))
},
deserializer: deserialize_boxed::<M>,
}
}
}

fn deserialize_boxed<M>(
de: &mut dyn erased_serde::Deserializer,
) -> Result<Box<dyn MessageSerde>, erased_serde::Error>
where
for<'a> M: MessageSerde + serde::Deserialize<'a>,
{
erased_serde::deserialize::<M>(de).map(|v| Box::new(v) as _)
}

inventory::collect!(MessageSerdeDecoderEntry);
18 changes: 0 additions & 18 deletions wkt-build/Cargo.toml

This file was deleted.

101 changes: 0 additions & 101 deletions wkt-build/src/lib.rs

This file was deleted.

18 changes: 18 additions & 0 deletions wkt-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "prost-wkt-derive"
version = "0.6.0"
authors = ["fdeantoni <fdeantoni@gmail.com>"]
license = "Apache-2.0"
repository = "https://github.com/fdeantoni/prost-wkt"
description = "Derive traits to assist with JSON serialization and deserialization of Well Known Types."
readme = "../README.md"
documentation = "https://docs.rs/prost-wkt-derive"
edition = "2021"
rust-version = "1.70"

[lib]
proc-macro = true

[dependencies]
quote = "1.0.37"
syn = "2.0.76"
29 changes: 29 additions & 0 deletions wkt-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::DeriveInput;

#[proc_macro_derive(MessageSerde)]
pub fn message_serde_derive(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
let gen = quote! {
const _: () = {
impl ::prost_wkt::MessageSerde for #name {
fn type_url(&self) -> String { <#name as ::prost::Name>::type_url() }
fn try_encoded(&self) -> ::std::result::Result<::std::vec::Vec<u8>, ::prost::EncodeError> {
let mut buf = ::std::vec::Vec::with_capacity(::prost::Message::encoded_len(self));
::prost::Message::encode(self, &mut buf)?;
Ok(buf)
}
fn as_erased_serialize(&self) -> &dyn ::prost_wkt::erased_serde::Serialize {
self
}
}

::prost_wkt::inventory::submit!{
::prost_wkt::MessageSerdeDecoderEntry::new::<#name>()
}
};
};
gen.into()
}
2 changes: 1 addition & 1 deletion wkt-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ prost = "0.13.1"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
serde-value = "0.7"
chrono = { version = "0.4.27", default-features = false, features = ["serde"] }

[build-dependencies]
prost = "0.13.1"
prost-types = "0.13.1"
prost-build = "0.13.1"
prost-wkt-build = { version = "0.6.0", path = "../wkt-build" }
regex = "1"
protobuf-src = { version = "1.1.0", optional = true }
protox = { version = "0.6.0", optional = true }
28 changes: 12 additions & 16 deletions wkt-types/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ use std::env;
use std::fs::create_dir_all;
use std::path::{Path, PathBuf};

use prost::Message;
use prost_types::FileDescriptorSet;

fn main() {
#[cfg(feature = "vendored-protoc")]
std::env::set_var("PROTOC", protobuf_src::protoc());
Expand Down Expand Up @@ -34,20 +31,19 @@ fn build(dir: &Path, proto: &str) {

prost_build
.compile_well_known_types()
.type_attribute("google.protobuf.Empty","#[derive(serde_derive::Serialize, serde_derive::Deserialize)]")
.type_attribute("google.protobuf.FieldMask","#[derive(serde_derive::Serialize, serde_derive::Deserialize)]")
.enable_type_names()
.type_name_domain(&["."], "type.googleapis.com")
.type_attribute(
"google.protobuf.Empty",
"#[derive(serde_derive::Serialize, serde_derive::Deserialize)]",
)
.type_attribute(
"google.protobuf.FieldMask",
"#[derive(serde_derive::Serialize, serde_derive::Deserialize)]",
)
.message_attribute(".", "#[derive(::prost_wkt::MessageSerde)]")
.file_descriptor_set_path(&descriptor_file)
.out_dir(&out)
.compile_protos(
&[
source
],
&["proto/".to_string()],
)
.compile_protos(&[source], &["proto/".to_string()])
.unwrap();

let descriptor_bytes = std::fs::read(descriptor_file).unwrap();
let descriptor = FileDescriptorSet::decode(&descriptor_bytes[..]).unwrap();

prost_wkt_build::add_serde(out, descriptor);
}
Loading