Skip to main content

cyclonedds_macros/
lib.rs

1//! Derive macro for the `Topicable` trait.
2
3use darling::{FromDeriveInput, FromField};
4use proc_macro::TokenStream;
5use quote::{ToTokens, format_ident, quote};
6
7#[derive(Debug, FromField)]
8#[darling(attributes(dds))]
9struct Field {
10    ident: Option<syn::Ident>,
11    ty: syn::Type,
12
13    #[darling(default)]
14    key: bool,
15}
16
17#[derive(Debug, FromDeriveInput)]
18#[darling(attributes(dds), supports(struct_named))]
19struct TopicableAttributes {
20    ident: syn::Ident,
21
22    data: darling::ast::Data<(), Field>,
23
24    type_name: Option<String>,
25}
26
27impl ToTokens for TopicableAttributes {
28    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
29        let TopicableAttributes {
30            ref ident,
31            ref data,
32            ref type_name,
33        } = *self;
34
35        let keys = data
36            .as_ref()
37            .take_struct()
38            .expect("the topicable attribute only accepts structs")
39            .fields
40            .into_iter()
41            .filter(|field| field.key)
42            .collect::<Vec<_>>();
43
44        let (key_type, from_key, as_key) = if keys.is_empty() {
45            (
46                quote!(()),
47                quote! {
48                    fn from_key((): &Self::Key) -> Self {
49                       Self::default()
50                    }
51                },
52                quote! {
53                    fn as_key(&self) -> Self::Key {}
54                },
55            )
56        } else {
57            let key_mod = format_ident!("__cyclonedds_topicable_{}", ident);
58            let key_field_defs = keys.iter().map(|f| {
59                let n = &f.ident;
60                let t = &f.ty;
61                quote! { pub #n: #t }
62            });
63            let key_field_inits = keys.iter().map(|f| {
64                let n = &f.ident;
65                quote! { #n: self.#n.clone() }
66            });
67            let key_field_from_key = keys.iter().map(|f| {
68                let n = &f.ident;
69                quote! { #n: key.#n.clone() }
70            });
71            let key_size_sum = keys.iter().map(|f| {
72                let t = &f.ty;
73                quote! {
74                    <#t as ::cyclonedds::cdr_bounds::CdrBounds>::max_serialized_cdr_size()
75                }
76            });
77            let key_alignment_max = keys.iter().map(|f| {
78                let t = &f.ty;
79                quote! {
80                    <#t as ::cyclonedds::cdr_bounds::CdrBounds>::alignment()
81                }
82            });
83
84            tokens.extend(
85                quote! {
86                    #[allow(non_snake_case)]
87                    #[doc(hidden)]
88                    mod #key_mod {
89                        #[doc(hidden)]
90                        #[derive(Default, serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Hash)]
91                        pub struct Key {
92                            #(#key_field_defs),*
93                        }
94
95                        impl ::cyclonedds::cdr_bounds::CdrBounds for Key {
96                            fn max_serialized_cdr_size() -> ::cyclonedds::cdr_bounds::CdrSize {
97                                #(#key_size_sum)+*
98                            }
99                            fn alignment() -> usize {
100                                0 #(.max(#key_alignment_max))*
101                            }
102                        }
103                    }
104                }
105            );
106            (
107                quote!(#key_mod::Key),
108                quote! {
109                    fn from_key(key: &Self::Key) -> Self {
110                        Self {
111                            #(#key_field_from_key),*,
112                            ..Default::default()
113                        }
114                    }
115                },
116                quote! {
117                    fn as_key(&self) -> Self::Key {
118                        Self::Key {
119                            #(#key_field_inits),*
120                        }
121                    }
122                },
123            )
124        };
125
126        let dds_type_name = type_name.as_ref().map(|type_name| {
127            quote! {
128                fn dds_type_name() -> impl AsRef<str> {
129                    #type_name
130                }
131            }
132        });
133
134        tokens.extend(quote! {
135            impl ::cyclonedds::Topicable for #ident {
136                type Key = #key_type;
137
138                #from_key
139
140                #as_key
141
142                #dds_type_name
143            }
144        });
145    }
146}
147
148/// Derives `Topicable` for a named-field struct.
149///
150/// Fields annotated with `#[dds(key)]` are collected into a generated
151/// `<Name>Key` struct that implements `CdrBounds`. Structs with no
152/// `#[dds(key)]` fields use [`()`](primitive@unit) as their key type and must
153/// implement [`Default`].
154///
155/// An optional `#[dds(type_name = "...")]` attribute overrides the DDS type
156/// name used for topic matching. Without it, the Rust type name is used.
157///
158/// # Examples
159///
160/// ```ignore
161/// #[derive(cyclonedds::Topicable, serde::Serialize, serde::Deserialize, Default, Clone, Debug)]
162/// #[dds(type_name = "MySensor")]
163/// pub struct Sensor {
164///     #[dds(key)]
165///     pub id: u32,
166///     pub value: f32,
167/// }
168///
169/// // NOTE: this results in a hidden module being generated `__cyclonedds_topicable_Sensor`
170/// // containing the following type: `Key { pub id: u32 }`
171/// ```
172///
173/// # Panics
174///
175/// Panics at compile time if applied to an enum, a union, a tuple struct, or
176/// if `#[dds(type_name)]` is not a valid string literal.
177#[proc_macro_derive(Topicable, attributes(dds))]
178pub fn derive_topicable(input: TokenStream) -> TokenStream {
179    let input = syn::parse_macro_input!(input as syn::DeriveInput);
180    let topicable = match TopicableAttributes::from_derive_input(&input) {
181        Ok(v) => v,
182        Err(e) => return e.write_errors().into(),
183    };
184
185    topicable.to_token_stream().into()
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    use syn::parse_quote;
193
194    #[test]
195    fn test_derive_parses_key_fields() {
196        let input = parse_quote! {
197            struct Sensor {
198                #[dds(key)]
199                pub id1: u32,
200                pub value: f32,
201                #[dds(key)]
202                pub id2: u32,
203            }
204        };
205        let attributes = TopicableAttributes::from_derive_input(&input).unwrap();
206        let fields = attributes.data.take_struct().unwrap();
207        let keys: Vec<_> = fields.fields.iter().filter(|f| f.key).collect();
208        assert_eq!(keys.len(), 2);
209        assert_eq!(keys[0].ident.as_ref().unwrap().to_string(), "id1");
210        assert_eq!(keys[1].ident.as_ref().unwrap().to_string(), "id2");
211    }
212
213    #[test]
214    fn test_derive_parses_type_name() {
215        let input = parse_quote! {
216            #[dds(type_name = "MySensor")]
217            struct Sensor {
218                pub id1: u32,
219                pub value: f32,
220            }
221        };
222        let attributes = TopicableAttributes::from_derive_input(&input).unwrap();
223        assert_eq!(attributes.type_name.as_deref(), Some("MySensor"));
224    }
225
226    #[test]
227    fn test_derive_double_type_name_fails() {
228        let input = parse_quote! {
229            #[dds(type_name = "MySensor1")]
230            #[dds(type_name = "MySensor2")]
231            struct Sensor {
232                pub id1: u32,
233                pub value: f32,
234            }
235        };
236        let error = TopicableAttributes::from_derive_input(&input).unwrap_err();
237        assert_eq!(
238            format!("{error}"),
239            format!("{}", darling::Error::duplicate_field("type_name"))
240        );
241    }
242
243    #[test]
244    fn test_derive_reject_tuple_struct() {
245        let input = parse_quote! {
246            struct Sensor ( pub u32, pub f32, pub u32 );
247        };
248        let error = TopicableAttributes::from_derive_input(&input).unwrap_err();
249        assert_eq!(
250            format!("{error}"),
251            format!(
252                "{}",
253                darling::Error::unsupported_shape_with_expected("unnamed fields", &"named fields")
254            )
255        );
256    }
257}