1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// Copyright 2019 The Exonum Team, 2019 Witnet Foundation
//
// 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.

#![recursion_limit = "256"]

extern crate proc_macro;

mod pb_convert;

use proc_macro::TokenStream;
use syn::{Attribute, NestedMeta};

const PB_CONVERT_ATTRIBUTE: &str = "protobuf_convert";
const PB_SNAKE_CASE_ATTRIBUTE: &str = "snake_case";
const DEFAULT_ONEOF_FIELD_NAME: &str = "kind";

/// ProtobufConvert derive macro.
///
/// Attributes:
///
/// ## Required
///
/// * `#[protobuf_convert(source = "path")]`
///
/// ```ignore
/// #[derive(Clone, Debug, ProtobufConvert)]
/// #[protobuf_convert(source = "proto::Message")]
/// pub struct Message {
///     /// Message author id.
///     pub author: u32,
///     /// Message text.
///     pub text: String,
/// }
///
/// let msg = Message::new();
/// let serialized_msg = msg.to_pb();
///
/// let deserialized_msg = ProtobufConvert::from_pb(serialized_msg).unwrap();
/// assert_eq!(msg, deserialized_msg);
/// ```
///
/// Corresponding proto file:
/// ```proto
/// message Message {
///     // Message author id..
///     uint32 author = 1;
///     // Message text.
///     string text = 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 "kind".
/// Corresponding proto file must contain only this oneof field. Possible enum
/// variants are zero-field and one-field variants.
/// Another enum attribute is `impl_from_trait`. If you specify it then `From` and `TryFrom`
/// traits for enum variants will be generated. Note that this will not work if enum has
/// variants with the same field types.
/// ```ignore
/// #[derive(Debug, Clone, ProtobufConvert)]
/// #[protobuf_convert(source = "proto::Message", oneof_field = "message")]
/// pub enum Message {
///     /// Plain message.
///     Plain(String),
///     /// Encoded message.
///     Encoded(String),
/// }
/// ```
///
/// Corresponding proto file:
/// ```proto
/// message Message {
///     oneof message {
///         // Plain message.
///         string plain = 1;
///         // Encoded message.
///         string encoded = 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).
/// For example, struct with `crypto::Hash` with this
/// (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 generate_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(PB_CONVERT_ATTRIBUTE))
        .map(NestedMeta::from)
}