-
Notifications
You must be signed in to change notification settings - Fork 245
Extract ProtobufConvert derive to separate crate #1501
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
Changes from all commits
6001f9a
27b9006
52cde7a
3a98da2
e66eeb8
6c24b44
a9b57dc
7a33f1c
f881a1c
29d450c
cfc0143
6915489
79fef04
097808c
dedbb12
fc9e17b
8e10f4f
c6f0e6b
442fa16
397dd63
9a172a1
8102214
0b85f41
cdaf6bd
c98dbdf
32b7614
efd8151
e9fa68c
cb81870
4d2a612
c874497
6cb5df4
ee261f0
7433538
6707ebe
35bbe31
f8629b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
// Copyright 2019 The Exonum Team | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
use darling::FromDeriveInput; | ||
use proc_macro::TokenStream; | ||
use proc_macro2::{Ident, Span}; | ||
use quote::{quote, ToTokens}; | ||
use syn::DeriveInput; | ||
|
||
#[derive(Debug, FromDeriveInput)] | ||
struct BinaryValueStruct { | ||
ident: Ident, | ||
} | ||
|
||
#[derive(Debug, FromDeriveInput)] | ||
struct ObjectHashStruct { | ||
ident: Ident, | ||
} | ||
|
||
impl ObjectHashStruct { | ||
pub fn implement_object_hash(&self) -> impl ToTokens { | ||
let name = &self.ident; | ||
|
||
quote! { | ||
impl exonum_merkledb::ObjectHash for #name { | ||
fn object_hash(&self) -> exonum_crypto::Hash { | ||
use exonum_merkledb::BinaryValue; | ||
let v = self.to_bytes(); | ||
exonum_crypto::hash(&v) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl BinaryValueStruct { | ||
pub fn implement_binary_value(&self) -> impl ToTokens { | ||
let name = &self.ident; | ||
|
||
quote! { | ||
// This trait assumes that we work with trusted data so we can unwrap here. | ||
impl exonum_merkledb::BinaryValue for #name { | ||
fn to_bytes(&self) -> Vec<u8> { | ||
self.to_pb().write_to_bytes().expect( | ||
concat!("Failed to serialize in BinaryValue for ", stringify!(#name)) | ||
) | ||
} | ||
|
||
fn from_bytes(value: std::borrow::Cow<[u8]>) -> Result<Self, failure::Error> { | ||
let mut block = <Self as exonum_proto::ProtobufConvert>::ProtoStruct::new(); | ||
block.merge_from_bytes(value.as_ref())?; | ||
exonum_proto::ProtobufConvert::from_pb(block) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl ToTokens for BinaryValueStruct { | ||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { | ||
let mod_name = Ident::new( | ||
&format!("binary_value_impl_{}", self.ident), | ||
Span::call_site(), | ||
); | ||
|
||
let binary_value = self.implement_binary_value(); | ||
let expanded = quote! { | ||
mod #mod_name { | ||
use super::*; | ||
|
||
use protobuf::Message as _ProtobufMessage; | ||
use exonum_proto::ProtobufConvert; | ||
|
||
#binary_value | ||
} | ||
}; | ||
|
||
tokens.extend(expanded); | ||
} | ||
} | ||
|
||
impl ToTokens for ObjectHashStruct { | ||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { | ||
let object_hash = self.implement_object_hash(); | ||
let expanded = quote! { #object_hash }; | ||
|
||
tokens.extend(expanded); | ||
} | ||
} | ||
|
||
pub fn binary_value(input: TokenStream) -> TokenStream { | ||
let input: DeriveInput = syn::parse(input).unwrap(); | ||
|
||
let db_object = BinaryValueStruct::from_derive_input(&input) | ||
.unwrap_or_else(|e| panic!("BinaryValue: {}", e)); | ||
let tokens = quote! { #db_object }; | ||
tokens.into() | ||
} | ||
|
||
pub fn object_hash(input: TokenStream) -> TokenStream { | ||
let input: DeriveInput = syn::parse(input).unwrap(); | ||
|
||
let db_object = | ||
ObjectHashStruct::from_derive_input(&input).unwrap_or_else(|e| panic!("ObjectHash: {}", e)); | ||
let tokens = quote! { #db_object }; | ||
tokens.into() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
[package] | ||
name = "exonum-proto-derive" | ||
version = "0.12.0" | ||
edition = "2018" | ||
authors = ["The Exonum Team <exonum@bitfury.com>"] | ||
homepage = "https://exonum.com/" | ||
repository = "https://github.com/exonum/exonum" | ||
documentation = "https://docs.rs/exonum-proto-derive" | ||
license = "Apache-2.0" | ||
keywords = ["protobuf", "exonum"] | ||
categories = ["development-tools"] | ||
description = "Helper macros for serialization structs in protobuf." | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
darling = "0.10.0" | ||
heck = "0.3.1" | ||
proc-macro2 = "1.0" | ||
quote = "1.0" | ||
syn = "1.0" | ||
semver = "0.9" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Copyright 2019 The Exonum Team | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//! This crate provides macros for deriving some useful methods and traits for the exonum services. | ||
|
||
#![recursion_limit = "128"] | ||
#![deny(unsafe_code, bare_trait_objects)] | ||
#![warn(missing_docs, missing_debug_implementations)] | ||
|
||
extern crate proc_macro; | ||
|
||
mod pb_convert; | ||
|
||
use proc_macro::TokenStream; | ||
use syn::{Attribute, NestedMeta}; | ||
|
||
/// ProtobufConvert derive macro. | ||
/// | ||
/// Attributes: | ||
/// | ||
/// ## Required | ||
/// | ||
/// * `#[protobuf_convert(source = "path")]` | ||
/// | ||
/// ```ignore | ||
/// #[derive(Clone, Debug, BinaryValue, ObjectHash, ProtobufConvert)] | ||
/// #[protobuf_convert(source = "proto::Wallet")] | ||
/// pub struct Wallet { | ||
/// /// `PublicKey` of the wallet. | ||
/// pub pub_key: PublicKey, | ||
/// /// Current balance of the wallet. | ||
/// pub balance: u64, | ||
/// } | ||
/// | ||
/// let wallet = Wallet::new(); | ||
/// let serialized_wallet = wallet.to_pb(); | ||
/// | ||
/// let deserialized_wallet = ProtobufConvert::from_pb(serialized_wallet).unwrap(); | ||
/// assert_eq!(wallet, deserialized_wallet); | ||
/// ``` | ||
/// | ||
/// Corresponding proto file: | ||
/// ```proto | ||
/// message Wallet { | ||
/// // Public key of the wallet owner. | ||
/// exonum.crypto.PublicKey pub_key = 1; | ||
/// // Current balance. | ||
/// uint64 balance = 2; | ||
/// } | ||
/// ``` | ||
/// | ||
/// This macro can also be applied to enums. In proto files enums are represented | ||
/// by `oneof` field. You can specify `oneof` field name, default is "message". | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do I understand correctly that:
It could be worth specifying these details. |
||
/// Corresponding proto file must contain only this oneof field. Possible enum | ||
/// variants are zero-field and one-field variants. | ||
/// ```ignore | ||
/// #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ProtobufConvert)] | ||
/// #[protobuf_convert(source = "schema::runtime::ConfigChange", oneof_field = "message")] | ||
/// pub enum ConfigChange { | ||
/// /// New consensus config. | ||
/// Consensus(ConsensusConfig), | ||
/// /// New service instance config. | ||
/// Service(ServiceConfig), | ||
/// } | ||
/// ``` | ||
/// | ||
/// Corresponding proto file: | ||
/// ```proto | ||
/// message ConfigChange { | ||
/// oneof message { | ||
/// // New consensus config. | ||
/// exonum.Config consensus = 1; | ||
/// // New service instance config. | ||
/// ServiceConfig service = 2; | ||
/// } | ||
/// } | ||
/// ``` | ||
/// | ||
/// Path is the name of the corresponding protobuf generated struct. | ||
/// | ||
/// * `#[protobuf_convert(source = "path", serde_pb_convert)]` | ||
/// | ||
/// Implement `serde::{Serialize, Deserialize}` using structs that were generated with | ||
/// protobuf. | ||
/// For example, it should be used if you want json representation of your struct | ||
/// to be compatible with protobuf representation (including proper nesting of fields). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit / off-topic: it's not clear what "compatible" means in this context. E.g., |
||
/// For example, struct with `exonum::crypto::Hash` with this | ||
slowli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// (de)serializer will be represented as | ||
/// ```text | ||
/// StructName { | ||
/// "hash": { | ||
/// "data": [1, 2, ...] | ||
/// }, | ||
/// // ... | ||
/// } | ||
/// // With default (de)serializer. | ||
/// StructName { | ||
/// "hash": "12af..." // HEX | ||
/// // ... | ||
/// } | ||
/// ``` | ||
#[proc_macro_derive(ProtobufConvert, attributes(protobuf_convert))] | ||
pub fn protobuf_convert(input: TokenStream) -> TokenStream { | ||
pb_convert::implement_protobuf_convert(input) | ||
} | ||
|
||
pub(crate) fn find_protobuf_convert_meta(args: &[Attribute]) -> Option<NestedMeta> { | ||
args.as_ref() | ||
.iter() | ||
.filter_map(|a| a.parse_meta().ok()) | ||
.find(|m| m.path().is_ident("protobuf_convert")) | ||
.map(NestedMeta::from) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needless ?