1use 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#[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}