zenobuf_macros/lib.rs
1//! # Zenobuf Macros - Procedural macros for the Zenobuf framework
2//!
3//! This crate provides procedural macros that simplify working with the Zenobuf framework,
4//! particularly for integrating Protocol Buffer messages with the type-safe messaging system.
5//!
6//! ## Overview
7//!
8//! The main macro provided is [`ZenobufMessage`], which automatically implements the
9//! [`zenobuf_core::Message`] trait for Protocol Buffer messages generated by Prost.
10//!
11//! ## Quick Start
12//!
13//! ### 1. Add to your `Cargo.toml`
14//!
15//! ```toml
16//! [dependencies]
17//! zenobuf-core = "0.3"
18//! zenobuf-macros = "0.3"
19//! prost = "0.14"
20//!
21//! [build-dependencies]
22//! prost-build = "0.14"
23//! ```
24//!
25//! ### 2. Setup automatic derive in `build.rs`
26//!
27//! ```rust,ignore
28//! fn main() -> std::io::Result<()> {
29//! prost_build::Config::new()
30//! .type_attribute(".", "#[derive(zenobuf_macros::ZenobufMessage)]")
31//! .compile_protos(&["protos/messages.proto"], &["protos"])?;
32//! Ok(())
33//! }
34//! ```
35//!
36//! ### 3. Use in your code
37//!
38//! ```rust,ignore
39//! // Generated protobuf code automatically has ZenobufMessage derived
40//! pub mod proto {
41//! include!(concat!(env!("OUT_DIR"), "/my_messages.rs"));
42//! }
43//!
44//! use zenobuf_core::Node;
45//!
46//! #[tokio::main]
47//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
48//! let node = Node::new("my_node").await?;
49//!
50//! // Your protobuf messages now work seamlessly with Zenobuf
51//! let publisher = node
52//! .publisher::<proto::MyMessage>("topic")
53//! .build()
54//! .await?;
55//!
56//! Ok(())
57//! }
58//! ```
59//!
60//! ## Manual Usage
61//!
62//! You can also manually derive the macro on your own types:
63//!
64//! ```rust,ignore
65//! use zenobuf_macros::ZenobufMessage;
66//!
67//! #[derive(Clone, PartialEq, Default, ZenobufMessage)]
68//! pub struct MyMessage {
69//! pub value: i32,
70//! }
71//! ```
72//!
73//! ## What the Macro Does
74//!
75//! The `ZenobufMessage` derive macro implements the [`zenobuf_core::Message`] trait,
76//! which provides:
77//! - Type name information for debugging and introspection
78//! - Integration with Zenobuf's type-safe messaging system
79//! - Automatic serialization/deserialization support
80//!
81//! ## Requirements
82//!
83//! Types that derive `ZenobufMessage` must also implement:
84//! - `Clone`
85//! - `PartialEq`
86//! - `Default`
87//! - `prost::Message` (for Protocol Buffer types)
88//! - `Send + Sync + 'static` (automatically satisfied by most types)
89
90use proc_macro::TokenStream;
91use quote::quote;
92use syn::{parse_macro_input, DeriveInput};
93
94/// Derives the [`zenobuf_core::Message`] trait for Protocol Buffer messages
95///
96/// This macro automatically implements the [`zenobuf_core::Message`] trait for Protocol
97/// Buffer messages generated by Prost. It extracts the type name from the struct name
98/// and implements the required methods for integration with Zenobuf's messaging system.
99///
100/// # Requirements
101///
102/// The type must implement:
103/// - `Clone`
104/// - `PartialEq`
105/// - `Default`
106/// - `prost::Message` (for Protocol Buffer types)
107/// - `Send + Sync + 'static`
108///
109/// # Examples
110///
111/// ## Manual Usage
112///
113/// ```rust,ignore
114/// use zenobuf_macros::ZenobufMessage;
115///
116/// #[derive(Clone, PartialEq, Default, ZenobufMessage)]
117/// pub struct Point {
118/// pub x: f32,
119/// pub y: f32,
120/// pub z: f32,
121/// }
122/// ```
123///
124/// ## With Protocol Buffers (typical usage)
125///
126/// In your `build.rs`:
127/// ```rust,ignore
128/// prost_build::Config::new()
129/// .type_attribute(".", "#[derive(zenobuf_macros::ZenobufMessage)]")
130/// .compile_protos(&["protos/messages.proto"], &["protos"])?;
131/// # Ok::<(), std::io::Error>(())
132/// ```
133///
134/// This automatically adds the derive macro to all generated Protocol Buffer types.
135///
136/// ## Using with Zenobuf
137///
138/// ```rust,ignore
139/// use zenobuf_core::Node;
140///
141/// #[tokio::main]
142/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
143/// let node = Node::new("publisher").await?;
144///
145/// // Point now implements Message and can be used with publishers
146/// let publisher = node
147/// .publisher::<Point>("points")
148/// .build()
149/// .await?;
150///
151/// let point = Point { x: 1.0, y: 2.0, z: 3.0 };
152/// publisher.publish(&point)?;
153///
154/// Ok(())
155/// }
156/// ```
157///
158/// # Topic Naming
159///
160/// When using derived message types with Zenobuf publishers or subscribers, topic names
161/// should follow valid Zenoh key-expression conventions. Avoid using Zenoh wildcard
162/// characters (`*`, `**`, `$`, `?`) in topic names unless you intend wildcard matching.
163/// Stick to alphanumeric characters, `/`, `-`, and `_` for topic and service names.
164///
165/// # Generated Implementation
166///
167/// The macro generates an implementation like this:
168///
169/// ```rust,ignore
170/// impl zenobuf_core::Message for Point {
171/// fn type_name() -> &'static str {
172/// // Returns fully qualified name, e.g. "my_app::proto::Point"
173/// concat!(module_path!(), "::", stringify!(Point))
174/// }
175/// }
176/// ```
177#[proc_macro_derive(ZenobufMessage)]
178pub fn derive_zenobuf_message(input: TokenStream) -> TokenStream {
179 let input = parse_macro_input!(input as DeriveInput);
180
181 if !matches!(input.data, syn::Data::Struct(_)) {
182 return TokenStream::from(
183 syn::Error::new_spanned(&input, "ZenobufMessage can only be derived for structs")
184 .to_compile_error(),
185 );
186 }
187
188 let name = &input.ident;
189 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
190
191 let expanded = quote! {
192 impl #impl_generics ::zenobuf_core::Message for #name #ty_generics #where_clause {
193 fn type_name() -> &'static str {
194 concat!(module_path!(), "::", stringify!(#name))
195 }
196 }
197 };
198
199 TokenStream::from(expanded)
200}