Skip to main content

zerodds_cdr_derive/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Crate `zerodds-cdr-derive`. Safety classification: **STANDARD**.
5//!
6//! `#[derive(DdsType)]` Proc-Macro — implementiert
7//! `zerodds-xcdr2-rust-1.0` §11.1.
8//!
9//! Leitet aus einem Plain-`struct` einen `impl DdsType` ab, der ueber
10//! die `zerodds_cdr::CdrEncode`/`CdrDecode`-Traits seriealisiert.
11//! Unterstuetzt heute Final-Extensibility (kein DHEADER) — Appendable
12//! und Mutable bleiben dem `idl-rust`-Codegen vorbehalten weil deren
13//! Logik nicht trivial pro Field rein-derive-fie ist.
14//!
15//! Beispiel:
16//!
17//! ```ignore
18//! use zerodds_cdr_derive::DdsType;
19//!
20//! #[derive(DdsType, Debug, Clone, PartialEq)]
21//! pub struct Sensor {
22//!     #[dds(key)]
23//!     pub id: i32,
24//!     pub value: f64,
25//! }
26//! ```
27
28#![allow(clippy::expect_used)]
29
30extern crate proc_macro;
31
32use proc_macro::TokenStream;
33use quote::{ToTokens, quote};
34use syn::{Attribute, Data, DeriveInput, Fields, Lit, Meta, parse_macro_input};
35
36/// Derives `DdsType` for a plain `struct`.
37#[proc_macro_derive(DdsType, attributes(dds))]
38pub fn derive_dds_type(input: TokenStream) -> TokenStream {
39    let ast = parse_macro_input!(input as DeriveInput);
40    expand(&ast).unwrap_or_else(|e| e.to_compile_error()).into()
41}
42
43fn expand(ast: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
44    let name = &ast.ident;
45    let name_str = name.to_string();
46
47    let Data::Struct(data) = &ast.data else {
48        return Err(syn::Error::new_spanned(
49            ast,
50            "DdsType derive supports plain structs only",
51        ));
52    };
53
54    let fields = match &data.fields {
55        Fields::Named(named) => named.named.iter().collect::<Vec<_>>(),
56        _ => {
57            return Err(syn::Error::new_spanned(
58                &data.fields,
59                "DdsType derive supports named fields only",
60            ));
61        }
62    };
63
64    let opts = parse_struct_opts(&ast.attrs)?;
65    let type_name = opts.type_name.unwrap_or(name_str);
66
67    // Walk fields to find @key markers.
68    let mut keyed_fields = Vec::new();
69    let mut all_fields = Vec::new();
70    for f in &fields {
71        let opt = parse_field_opts(&f.attrs)?;
72        let ident = f.ident.as_ref().expect("named field has ident");
73        all_fields.push(ident.clone());
74        if opt.key {
75            keyed_fields.push(ident.clone());
76        }
77    }
78    let is_keyed = !keyed_fields.is_empty();
79
80    let encode_field_lines = all_fields.iter().map(|ident| {
81        quote! {
82            ::zerodds_cdr::CdrEncode::encode(&self.#ident, &mut writer)?;
83        }
84    });
85    let decode_field_lines = all_fields.iter().map(|ident| {
86        quote! {
87            #ident: ::zerodds_cdr::CdrDecode::decode(&mut reader)?,
88        }
89    });
90    // PlainCdr2BeKeyHolder hat write_u8/u16/u32/u64/i8/.../bytes Methoden.
91    // Wir delegieren via CdrEncode auf einen tempBufferWriter und kopieren
92    // die Bytes ins holder buffer ueber `holder.write_bytes`.
93    let key_field_lines = keyed_fields.iter().map(|ident| {
94        quote! {
95            {
96                let mut __bw = ::zerodds_cdr::BufferWriter::new(
97                    ::zerodds_cdr::Endianness::Big,
98                );
99                ::zerodds_cdr::CdrEncode::encode(&self.#ident, &mut __bw)?;
100                holder.write_bytes(&__bw.into_bytes());
101            }
102        }
103    });
104
105    let key_holder_method = if is_keyed {
106        quote! {
107            fn encode_key_holder_be(
108                &self,
109                holder: &mut ::zerodds_cdr::PlainCdr2BeKeyHolder,
110            ) {
111                let _ = (|| -> ::core::result::Result<(), ::zerodds_cdr::EncodeError> {
112                    #( #key_field_lines )*
113                    ::core::result::Result::Ok(())
114                })();
115            }
116        }
117    } else {
118        quote! {}
119    };
120
121    let extensibility_const = if is_keyed {
122        quote! {
123            const EXTENSIBILITY: ::zerodds_dcps::Extensibility =
124                ::zerodds_dcps::Extensibility::Final;
125            const HAS_KEY: bool = true;
126        }
127    } else {
128        quote! {
129            const EXTENSIBILITY: ::zerodds_dcps::Extensibility =
130                ::zerodds_dcps::Extensibility::Final;
131        }
132    };
133
134    let expanded = quote! {
135        impl ::zerodds_dcps::DdsType for #name {
136            const TYPE_NAME: &'static str = #type_name;
137            #extensibility_const
138
139            fn encode(&self, out: &mut ::std::vec::Vec<u8>)
140                -> ::core::result::Result<(), ::zerodds_dcps::EncodeError>
141            {
142                let mut writer = ::zerodds_cdr::BufferWriter::new(
143                    ::zerodds_cdr::Endianness::Little,
144                );
145                #( #encode_field_lines )*
146                out.extend_from_slice(&writer.into_bytes());
147                ::core::result::Result::Ok(())
148            }
149
150            fn decode(bytes: &[u8])
151                -> ::core::result::Result<Self, ::zerodds_dcps::DecodeError>
152            {
153                let mut reader = ::zerodds_cdr::BufferReader::new(
154                    bytes,
155                    ::zerodds_cdr::Endianness::Little,
156                );
157                ::core::result::Result::Ok(Self {
158                    #( #decode_field_lines )*
159                })
160            }
161
162            #key_holder_method
163        }
164    };
165
166    Ok(expanded)
167}
168
169#[derive(Default)]
170struct StructOpts {
171    type_name: Option<String>,
172}
173
174fn parse_struct_opts(attrs: &[Attribute]) -> syn::Result<StructOpts> {
175    let mut out = StructOpts::default();
176    for attr in attrs {
177        if !attr.path().is_ident("dds") {
178            continue;
179        }
180        attr.parse_nested_meta(|meta| {
181            if meta.path.is_ident("type_name") {
182                let v: Lit = meta.value()?.parse()?;
183                if let Lit::Str(s) = v {
184                    out.type_name = Some(s.value());
185                }
186            }
187            Ok(())
188        })?;
189    }
190    Ok(out)
191}
192
193#[derive(Default)]
194struct FieldOpts {
195    key: bool,
196}
197
198fn parse_field_opts(attrs: &[Attribute]) -> syn::Result<FieldOpts> {
199    let mut out = FieldOpts::default();
200    for attr in attrs {
201        if !attr.path().is_ident("dds") {
202            continue;
203        }
204        if let Meta::List(list) = &attr.meta {
205            let toks = list.tokens.clone().into_token_stream().to_string();
206            if toks.split(',').any(|t| t.trim() == "key") {
207                out.key = true;
208            }
209        }
210    }
211    Ok(out)
212}