compact_thrift_runtime/
macros.rs

1/// Parse several thrift struct / union / enum / namespace definitions and generate the
2/// corresponding Rust types and code to read and write those types.
3///
4/// See the `thrift_struct` / `thrift_union` / `thrift_enum` macros for details of the
5/// accepted syntax.
6///
7/// Implementation notes:
8///
9/// This uses the [incremental tt muncher](https://lukaswirth.dev/tlborm/decl-macros/patterns/tt-muncher.html)
10/// pattern to process the first thrift definition and then recursively calls itself to process the
11/// remainder.
12///
13/// Field definitions are handled as token trees in this macro, the more specific `thrift_struct`,
14/// `thrift_union` and `thrift_enum` macros destructure those trees.
15///
16/// The `$(#[$($def_attrs:meta)*])*` pattern matches any number of attributes on those structures.
17/// The rust compiler internally treats doc comments as `#[doc = ""]` attributes,
18/// this pattern allows to keep comments in the generated code.
19#[macro_export]
20macro_rules! thrift {
21    ($(#[$($def_attrs:meta)*])* struct $identifier:ident { $($definitions:tt)* } $($remainder:tt)*) => {
22        $crate::thrift_struct!($(#[$($def_attrs)*])* struct $identifier { $($definitions)* });
23        $crate::thrift!($($remainder)*);
24    };
25    ($(#[$($def_attrs:meta)*])* union $identifier:ident { $($definitions:tt)* } $($remainder:tt)*) => {
26        $crate::thrift_union!($(#[$($def_attrs)*])* union $identifier { $($definitions)* });
27        $crate::thrift!($($remainder)*);
28    };
29    ($(#[$($def_attrs:meta)*])* enum $identifier:ident { $($definitions:tt)* } $($remainder:tt)*) => {
30        $crate::thrift_enum!($(#[$($def_attrs)*])* enum $identifier { $($definitions)* });
31        $crate::thrift!($($remainder)*);
32    };
33    ($(#[$($def_attrs:meta)*])* namespace $identifier:ident $namespace:ident $($remainder:tt)*) => {
34        $crate::thrift!($($remainder)*);
35    };
36
37    () => {};
38}
39
40/// Generate the type and `CompactThriftProtocol` implementation for a thrift `struct`.
41///
42/// Syntax (square brackets indicate optional parts or alternatives):
43///
44/// ```thrift
45/// [ doc_comment ]
46/// struct struct_name {
47///    [ doc_comment ]
48///    field_id: [ required | optional ] field_type [ < element_type > ] field_name [ = default_value ] [ ; ]
49///    ...
50/// }
51/// ```
52#[macro_export]
53macro_rules! thrift_struct {
54    ($(#[$($def_attrs:meta)*])* struct $identifier:ident { $($(#[$($field_attrs:meta)*])* $field_id:literal : $required_or_optional:ident $field_type:ident $(< $element_type:ident >)? $field_name:ident $(= $default_value:literal)? $(;)?)* }) => {
55        $(#[cfg_attr(not(doctest), $($def_attrs)*)])*
56        #[derive(Default, Clone, Debug, PartialEq)]
57        #[allow(non_camel_case_types)]
58        #[allow(non_snake_case)]
59        pub struct $identifier {
60            $($(#[cfg_attr(not(doctest), $($field_attrs)*)])* pub $field_name: $crate::__thrift_required_or_optional!($required_or_optional $crate::__thrift_field_type!($field_type $($element_type)?))),*
61        }
62
63        impl $identifier {
64            #[allow(clippy::too_many_arguments)]
65            pub fn new($($field_name: impl Into<$crate::__thrift_required_or_optional!($required_or_optional $crate::__thrift_field_type!($field_type $($element_type)?))>),*) -> Self {
66                Self {
67                    $($field_name: $field_name.into(),)*
68                }
69            }
70        }
71
72        #[allow(non_snake_case)]
73        impl <'i> $crate::CompactThriftProtocol<'i> for $identifier {
74            const FIELD_TYPE: u8 = 12;
75
76            #[inline(never)]
77            fn fill_thrift<T: $crate::CompactThriftInput<'i>>(&mut self, input: &mut T) -> std::result::Result<(), $crate::ThriftError> {
78                let mut last_field_id = 0_i16;
79                $($crate::__thrift_required_flag!($required_or_optional $field_name);)*
80                loop {
81                    let field_type = input.read_field_header(&mut last_field_id)?;
82                    if field_type == 0 {
83                        break;
84                    }
85
86                    match last_field_id {
87                        $($field_id => {
88                            $crate::__thrift_required_set!($required_or_optional $field_name);
89                            self.$field_name.fill_thrift_field(input, field_type)?;
90                        }),*
91                        _ => {
92                            input.skip_field(field_type)?;
93                        }
94                    }
95                }
96
97                $($crate::__thrift_required_check!($required_or_optional $identifier $field_name);)*
98
99                Ok(())
100            }
101
102            fn write_thrift<T: $crate::CompactThriftOutput>(&self, output: &mut T) -> std::result::Result<(), $crate::ThriftError> {
103                #[allow(unused_variables)]
104                #[allow(unused_mut)]
105                let mut last_field_id = 0_i16;
106                $(self.$field_name.write_thrift_field(output, $field_id, &mut last_field_id)?;)*
107                output.write_byte(0)?;
108                Ok(())
109            }
110        }
111    }
112}
113
114/// Generate the type and `CompactThriftProtocol` implementation for a thrift `union`.
115///
116/// Syntax (square brackets indicate optional parts or alternatives):
117///
118/// ```thrift
119/// [ doc_comment ]
120/// union union_name {
121///    [ doc_comment ]
122///    field_id : [ required | optional ] field_type [ < element_type > ] field_name [ ; ]
123///    ...
124/// }
125/// ```
126#[macro_export]
127macro_rules! thrift_union {
128    ($(#[$($def_attrs:meta)*])* union $identifier:ident { $($(#[$($field_attrs:meta)*])* $field_id:literal : $field_type:ident $(< $element_type:ident >)? $field_name:ident $(;)?)* }) => {
129        $(#[cfg_attr(not(doctest), $($def_attrs)*)])*
130        #[derive(Clone, Debug, PartialEq)]
131        #[allow(non_camel_case_types)]
132        #[allow(non_snake_case)]
133        pub enum $identifier {
134            $($(#[cfg_attr(not(doctest), $($field_attrs)*)])* $field_name($crate::__thrift_field_type!($field_type $($element_type)?))),*
135        }
136
137        impl Default for $identifier {
138            fn default() -> Self {
139                $crate::__thrift_union_default!($($field_name;)*)
140            }
141        }
142
143        #[allow(non_snake_case)]
144        impl <'i> $crate::CompactThriftProtocol<'i> for $identifier {
145            const FIELD_TYPE: u8 = 12;
146
147            #[inline(never)]
148            fn fill_thrift<T: $crate::CompactThriftInput<'i>>(&mut self, input: &mut T) -> std::result::Result<(), $crate::ThriftError> {
149                let mut last_field_id = 0_i16;
150                let field_type = input.read_field_header(&mut last_field_id)?;
151
152                if field_type == 0 {
153                    return Err($crate::ThriftError::InvalidType);
154                }
155
156                match last_field_id {
157                    $($field_id => {
158                        *self = Self::$field_name(Default::default());
159                        #[allow(unreachable_patterns)]
160                        match self {
161                            Self::$field_name(inner) => inner.fill_thrift(input)?,
162                            _ => unsafe { std::hint::unreachable_unchecked() },
163                        }
164                    }),*
165                    _ => {
166                        return Err($crate::ThriftError::MissingField(concat!(stringify!($struct_name), "\0").into()))
167                    }
168                }
169                let stop = input.read_byte()?;
170                if stop != 0 {
171                    return Err($crate::ThriftError::MissingStop)
172                }
173
174                Ok(())
175            }
176
177            fn write_thrift<T: $crate::CompactThriftOutput>(&self, output: &mut T) -> std::result::Result<(), $crate::ThriftError> {
178                let mut last_field_id = 0_i16;
179                match self {
180                    $(Self::$field_name(inner) => inner.write_thrift_field(output, $field_id, &mut last_field_id)?),*
181                }
182                output.write_byte(0)?;
183                Ok(())
184            }
185        }
186    }
187}
188
189/// Generate the type and `CompactThriftProtocol` implementation for a thrift `enum`.
190///
191/// Syntax (square brackets indicate optional parts or alternatives):
192///
193/// ```thrift
194/// [ doc_comment ]
195/// enum enum_name {
196///    [ doc_comment ]
197///    field_name = field_value ;
198///    ...
199/// }
200/// ```
201#[macro_export]
202macro_rules! thrift_enum {
203    ($(#[$($def_attrs:meta)*])* enum $identifier:ident { $($(#[$($field_attrs:meta)*])* $field_name:ident = $field_value:literal;)* }) => {
204        $(#[$($def_attrs)*])*
205        #[derive(Default, Debug, Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
206        #[allow(non_camel_case_types)]
207        pub struct $identifier(pub i32);
208
209        impl From<i32> for $identifier {
210            #[inline]
211            fn from(value: i32) -> Self {
212                Self(value)
213            }
214        }
215
216        impl $identifier {
217            $(pub const $field_name: Self = Self($field_value);)*
218        }
219
220        impl <'i> $crate::CompactThriftProtocol<'i> for $identifier {
221            const FIELD_TYPE: u8 = 5; // i32
222
223            #[inline]
224            fn fill_thrift<T: $crate::CompactThriftInput<'i>>(&mut self, input: &mut T) -> std::result::Result<(), $crate::ThriftError> {
225                self.0 = input.read_i32()?;
226                Ok(())
227            }
228
229            #[inline]
230            fn write_thrift<T: $crate::CompactThriftOutput>(&self, output: &mut T) -> std::result::Result<(), $crate::ThriftError> {
231                output.write_i32(self.0)
232            }
233        }
234    }
235}
236
237#[doc(hidden)]
238#[macro_export]
239macro_rules! __thrift_union_default {
240    ($head:ident; $($tail:ident;)*) => {
241        Self::$head(Default::default())
242    };
243    () => {
244        Self
245    };
246}
247
248#[doc(hidden)]
249#[macro_export]
250macro_rules! __thrift_field_type {
251    (list $element_type:ident) => { Vec< $crate::__thrift_field_type!($element_type) > };
252    (set $element_type:ident) => { Vec< $crate::__thrift_field_type!($element_type) > };
253    (binary) => { Vec<u8> };
254    (string) => { String };
255    (byte) => { u8 };
256    (double) => { f64 };
257    ($field_type:ty) => { $field_type }; // this covers bool | i8 | i16 | i32 | i64
258    (Box $element_type:ident) => { std::boxed::Box< $crate::__thrift_field_type!($element_type) > };
259    (Rc $element_type:ident) => { std::rc::Rc< $crate::__thrift_field_type!($element_type) > };
260    (Arc $element_type:ident) => { std::sync::Arc< $crate::__thrift_field_type!($element_type) > };
261}
262
263/// Wraps an `optional` thrift field in a rust `Option`.
264#[doc(hidden)]
265#[macro_export]
266macro_rules! __thrift_required_or_optional {
267    (required $field_type:ty) => { $field_type };
268    (optional $field_type:ty) => { Option<$field_type> };
269}
270
271/// For required fields, this creates a boolean flag indicating whether the field was set.
272#[doc(hidden)]
273#[macro_export]
274macro_rules! __thrift_required_flag {
275    (required $field_name:ident) => { let mut $field_name = false; };
276    (optional $field_name:ident) => {};
277}
278
279/// For required fields, this sets the boolean flag generated by `__thrift_required_flag` to `true.
280#[doc(hidden)]
281#[macro_export]
282macro_rules! __thrift_required_set {
283    (required $field_name:ident) => { $field_name = true; };
284    (optional $field_name:ident) => {};
285}
286
287/// For required fields, this checks the boolean flag generated by `__thrift_required_flag`
288/// and returns an error if it was `false`.
289#[doc(hidden)]
290#[macro_export]
291macro_rules! __thrift_required_check {
292    (required $struct_name:ident $field_name:ident) => {
293        if !$field_name {
294            return Err($crate::ThriftError::MissingField(concat!(stringify!($struct_name), "::", stringify!($field_name), "\0").into()))
295        }
296    };
297    (optional $struct_name:ident $field_name:ident) => {};
298}
299
300#[cfg(test)]
301#[allow(dead_code)]
302mod tests {
303    thrift! {
304        /// doc
305        namespace rust test
306        /** doc */
307        struct SomeStructure {
308            /** doc */
309            1: required i64 offset;
310            2: optional i64 length;
311            3: optional list<i64> foobar;
312            4: optional string data;
313            5: optional bool flag;
314            6: optional double value;
315        }
316        struct AnotherStructure {
317            1: required i64 foobar;
318        }
319    }
320
321    thrift! {
322        struct MilliSeconds {}
323        struct MicroSeconds {}
324        struct NanoSeconds {}
325        union TimeUnit {
326          1: MilliSeconds MILLIS
327          2: MicroSeconds MICROS
328          3: NanoSeconds NANOS
329        }
330    }
331
332    thrift!{
333        enum Type {
334          BOOLEAN = 0;
335          INT32 = 1;
336          INT64 = 2;
337          INT96 = 3;  // deprecated, only used by legacy implementations.
338          FLOAT = 4;
339          DOUBLE = 5;
340          BYTE_ARRAY = 6;
341          FIXED_LEN_BYTE_ARRAY = 7;
342        }
343        enum CompressionCodec {
344          UNCOMPRESSED = 0;
345          SNAPPY = 1;
346          GZIP = 2;
347          LZO = 3;
348          BROTLI = 4;  // Added in 2.4
349          LZ4 = 5;     // DEPRECATED (Added in 2.4)
350          ZSTD = 6;    // Added in 2.4
351          LZ4_RAW = 7; // Added in 2.9
352        }
353    }
354
355    thrift! {
356        struct ReferenceCounted {
357            1: required Rc<String> rc_string;
358            2: required Arc<String> arc_string;
359            3: required Rc<str> rc_str;
360            4: required Arc<str> arc_str;
361        }
362    }
363
364    #[test]
365    pub fn test_constructor() {
366        let _s = SomeStructure::new(1_i64, 2_i64, Some(vec![3_i64]), Some("foo".into()), true, 1.0);
367        let _r = ReferenceCounted::default();
368    }
369}