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)
}