acktor_derive/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use proc_macro::TokenStream;
4
5mod has_stable_type_id;
6mod message;
7mod message_id;
8mod message_response;
9
10#[cfg(feature = "ipc")]
11mod common;
12#[cfg(feature = "ipc")]
13mod decode;
14#[cfg(feature = "ipc")]
15mod encode;
16#[cfg(feature = "ipc")]
17mod remote_actor;
18#[cfg(feature = "ipc")]
19mod remote_actor_attr;
20
21/// Derive the [`Message`] trait for a struct or enum.
22///
23/// A `#[result_type(..)]` attribute must be present to specify the type returned when the message
24/// is handled by an actor.
25///
26/// # Examples
27///
28/// ```ignore
29/// use acktor_derive::{Message, MessageResponse};
30///
31/// #[derive(MessageResponse)]
32/// struct Sum(i64);
33///
34/// #[derive(Message)]
35/// #[result_type(Sum)]
36/// struct Add(i64, i64);
37/// ```
38///
39/// [`Message`]: https://docs.rs/acktor/latest/acktor/message/trait.Message.html
40#[proc_macro_derive(Message, attributes(result_type))]
41pub fn message_derive(input: TokenStream) -> TokenStream {
42 let ast = syn::parse(input).unwrap();
43
44 message::expand(&ast).into()
45}
46
47/// Derive the [`MessageResponse`] trait for a struct or enum.
48///
49/// This implements the default response handling, which sends the value back through an oneshot
50/// channel to the sender of the message.
51///
52/// # Examples
53///
54/// ```ignore
55/// use acktor_derive::MessageResponse;
56///
57/// #[derive(MessageResponse)]
58/// struct Sum(i64);
59///
60/// #[derive(Message)]
61/// #[result_type(Sum)]
62/// struct Add(i64, i64);
63/// ```
64///
65/// [`MessageResponse`]: https://docs.rs/acktor/latest/acktor/message/trait.MessageResponse.html
66#[proc_macro_derive(MessageResponse)]
67pub fn message_response_derive(input: TokenStream) -> TokenStream {
68 let ast = syn::parse(input).unwrap();
69
70 message_response::expand(&ast).into()
71}
72
73/// Derive the [`HasStableTypeId`] trait for a type.
74///
75/// The generated `STABLE_TYPE_ID` is a SHA-256 hash value of the type's fully-qualified path
76/// (`module_path!() + "::" + ident`).
77///
78/// If the type contains type generic parameters, the generated `STABLE_TYPE_ID` is combined with
79/// each type generic parameter's `STABLE_TYPE_ID` with [`StableTypeId::combine`] in their
80/// declaration order.
81///
82/// If the type contains const generic parameters, the generated `STABLE_TYPE_ID` is combined with
83/// the SHA-256 hash value of the little-endian byte form of each const generic parameter with
84/// [`StableTypeId::combine`] in their declaration order. Only const generics of integer
85/// primitives, `bool`, and `char` are supported.
86///
87/// # Example
88///
89/// ```ignore
90/// use acktor_derive::HasStableTypeId;
91///
92/// #[derive(HasStableTypeId)]
93/// struct Ping(u64);
94/// ```
95///
96/// [`HasStableTypeId`]: https://docs.rs/acktor/latest/acktor/stable_type_id/trait.HasStableTypeId.html
97/// [`StableTypeId::combine`]: https://docs.rs/acktor/latest/acktor/stable_type_id/struct.StableTypeId.html#method.combine
98#[proc_macro_derive(HasStableTypeId)]
99pub fn has_stable_type_id_derive(input: TokenStream) -> TokenStream {
100 let ast = syn::parse(input).unwrap();
101
102 has_stable_type_id::expand(&ast).into()
103}
104
105/// Derive the [`MessageId`] trait for a [`Message`].
106///
107/// By default, the derive also emits a [`HasStableTypeId`] impl and sets
108/// `MessageId::ID = STABLE_TYPE_ID.as_u64()`. In that case, do **not** also derive
109/// [`HasStableTypeId`] separately, as that would produce conflicting impls. See the
110/// [`HasStableTypeId`] derive for the hashing scheme and the rules around generic parameters.
111///
112/// An optional `#[custom_id(<u64 value>)]` attribute lets the user supply the id directly. When
113/// present, no [`HasStableTypeId`] impl is emitted, and it is the user's responsibility to ensure
114/// the id is unique across all messages an actor can handle.
115///
116/// # Example
117///
118/// ```ignore
119/// use acktor_derive::MessageId;
120///
121/// #[derive(MessageId)]
122/// struct Ping(u64);
123///
124/// #[derive(MessageId)]
125/// #[custom_id(0xdead_beef)]
126/// struct Pong;
127/// ```
128///
129/// [`MessageId`]: https://docs.rs/acktor/latest/acktor/message/trait.MessageId.html
130/// [`Message`]: https://docs.rs/acktor/latest/acktor/message/trait.Message.html
131/// [`HasStableTypeId`]: https://docs.rs/acktor/latest/acktor/stable_type_id/trait.HasStableTypeId.html
132#[proc_macro_derive(MessageId, attributes(custom_id))]
133pub fn message_id_derive(input: TokenStream) -> TokenStream {
134 let ast = syn::parse(input).unwrap();
135
136 message_id::expand(&ast).into()
137}
138
139/// Derive the [`Encode`] trait for a message.
140///
141/// A `#[codec(..)]` attribute must be present to select the serialization method and the same
142/// attribute is shared with [`Decode`]. Encoding and decoding of the same message type must use
143/// the same method. The attribute also supports an optional bridge type that serves as an
144/// intermediary for encoding and decoding, which is useful when the message type itself cannot
145/// directly implement the required traits. Currently there are three supported codec methods:
146///
147/// - `#[codec(prost)]` — delegates to [`prost::Message::encode_to_vec`]. The target type (or the
148/// bridge type) must implement [`prost::Message`].
149/// - `#[codec(zerocopy)]` — delegates to [`zerocopy::IntoBytes::as_bytes`]. The target type (or
150/// the bridge type) must implement [`zerocopy::IntoBytes`].
151/// - `#[codec(rkyv)]` — delegates to [`rkyv::to_bytes`]. The target type (or the bridge type)
152/// must implement [`rkyv::Serialize`].
153///
154/// If a bridge type `T` is specified, the bridge type must be convertible from the target type
155/// with `impl From<&Self> for T`.
156///
157/// # Example
158///
159/// ```ignore
160/// use acktor_derive::Encode;
161///
162/// #[derive(zerocopy::IntoBytes, Encode)]
163/// #[codec(zerocopy)]
164/// struct Ping(u64);
165/// ```
166///
167/// [`Encode`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/codec/trait.Encode.html
168/// [`Decode`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/codec/trait.Decode.html
169/// [`prost::Message`]: https://docs.rs/prost/latest/prost/trait.Message.html
170/// [`prost::Message::encode_to_vec`]: https://docs.rs/prost/latest/prost/trait.Message.html#method.encode_to_vec
171/// [`zerocopy::IntoBytes`]: https://docs.rs/zerocopy/latest/zerocopy/trait.IntoBytes.html
172/// [`zerocopy::IntoBytes::as_bytes`]: https://docs.rs/zerocopy/latest/zerocopy/trait.IntoBytes.html#method.as_bytes
173/// [`rkyv::to_bytes`]: https://docs.rs/rkyv/latest/rkyv/fn.to_bytes.html
174/// [`rkyv::Serialize`]: https://docs.rs/rkyv/latest/rkyv/trait.Serialize.html
175#[cfg(feature = "ipc")]
176#[cfg_attr(docsrs, doc(cfg(feature = "ipc")))]
177#[proc_macro_derive(Encode, attributes(codec))]
178pub fn encode_derive(input: TokenStream) -> TokenStream {
179 let ast = syn::parse(input).unwrap();
180
181 encode::expand(&ast).into()
182}
183
184/// Derive the [`Decode`] trait for a message.
185///
186/// A `#[codec(..)]` attribute must be present to select the deserialization method and the same
187/// attribute is shared with [`Decode`]. Encoding and decoding of the same message type must use
188/// the same method. The attribute also supports an optional bridge type that serves as an
189/// intermediary for encoding and decoding, which is useful when the message type itself cannot
190/// directly implement the required traits. Currently there are three supported codec methods:
191///
192/// - `#[codec(prost)]` — delegates to [`prost::Message::decode`]. The target type (or the bridge
193/// type) must implement [`prost::Message`].
194/// - `#[codec(zerocopy)]` — delegates to [`zerocopy::FromBytes::read_from_bytes`]. The target
195/// type (or the bridge type) must implement [`zerocopy::FromBytes`].
196/// - `#[codec(rkyv)]` — delegates to [`rkyv::from_bytes`]. The target type (or the bridge type)
197/// must implement [`rkyv::Archive`] and [`rkyv::Deserialize`].
198///
199/// If a bridge type `T` is specified, the target type must be convertible from the bridge type
200/// with `impl TryFrom<T> for Self` and use [`DecodeError`] as the error type.
201///
202/// # Example
203///
204/// ```ignore
205/// use acktor_derive::Decode;
206///
207/// #[derive(zerocopy::FromBytes, Decode)]
208/// #[codec(zerocopy)]
209/// struct Ping(u64);
210/// ```
211///
212/// [`Encode`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/codec/trait.Encode.html
213/// [`Decode`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/codec/trait.Decode.html
214/// [`prost::Message`]: https://docs.rs/prost/latest/prost/trait.Message.html
215/// [`prost::Message::decode`]: https://docs.rs/prost/latest/prost/trait.Message.html#method.decode
216/// [`zerocopy::FromBytes`]: https://docs.rs/zerocopy/latest/zerocopy/trait.FromBytes.html
217/// [`zerocopy::FromBytes::read_from_bytes`]: https://docs.rs/zerocopy/latest/zerocopy/trait.FromBytes.html#method.read_from_bytes
218/// [`rkyv::from_bytes`]: https://docs.rs/rkyv/latest/rkyv/fn.from_bytes.html
219/// [`rkyv::Archive`]: https://docs.rs/rkyv/latest/rkyv/trait.Archive.html
220/// [`rkyv::Deserialize`]: https://docs.rs/rkyv/latest/rkyv/trait.Deserialize.html
221/// [`DecodeError`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/errors/enum.DecodeError.html
222#[cfg(feature = "ipc")]
223#[cfg_attr(docsrs, doc(cfg(feature = "ipc")))]
224#[proc_macro_derive(Decode, attributes(codec))]
225pub fn decode_derive(input: TokenStream) -> TokenStream {
226 let ast = syn::parse(input).unwrap();
227
228 decode::expand(&ast).into()
229}
230
231/// Derive the [`RemoteActor`] trait for an actor.
232///
233/// Without any attribute, only the marker `impl RemoteActor for Self {}` is emitted.
234///
235/// With an optional `#[message(M1, M2, ...)]` attribute, an additional
236/// `impl Handler<RemoteMessage> for Self` is emitted which dispatches inbound messages by
237/// matching their `message_id` against `<Mi as Decode>::ID` and invoking the corresponding
238/// message handler `<Self as Handler<Mi>>::handle`. After handling the message, the response is
239/// encoded and sent back through an oneshot channel to the sender of the [`RemoteMessage`].
240///
241/// For each `Mi`, the actor must implement [`Handler<Mi>`] trait and the result type of the
242/// trait must implement [`Encode`] trait.
243///
244/// This also emits the [`HasStableTypeId`] impl, so `#[derive(RemoteActor)]` alone is sufficient
245/// — do **not** also derive [`HasStableTypeId`] separately, as that would produce conflicting
246/// impls. See the [`HasStableTypeId`] derive for the hashing scheme and the rules around generic
247/// parameters.
248///
249/// # Example
250///
251/// ```ignore
252/// use acktor_derive::RemoteActor;
253///
254/// #[derive(RemoteActor)]
255/// #[message(Ping, Echo)]
256/// pub struct MyActor;
257/// ```
258///
259/// [`RemoteActor`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/remote_actor/trait.RemoteActor.html
260/// [`RemoteMessage`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/remote_message/struct.RemoteMessage.html
261/// [`Handler<Mi>`]: https://docs.rs/acktor/latest/acktor/message/trait.Handler.html
262/// [`Encode`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/codec/trait.Encode.html
263/// [`HasStableTypeId`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/stable_type_id/trait.HasStableTypeId.html
264#[cfg(feature = "ipc")]
265#[cfg_attr(docsrs, doc(cfg(feature = "ipc")))]
266#[proc_macro_derive(RemoteActor, attributes(message))]
267pub fn remote_actor_derive(input: TokenStream) -> TokenStream {
268 let ast = syn::parse(input).unwrap();
269
270 remote_actor::expand(&ast).into()
271}
272
273/// Attribute macro applies to the `impl Actor for MyActor { ... }` block, which overrides the
274/// [`Actor::type_erased_recipient_fn`] used by `acktor-ipc` with a custom implementation that
275/// converts [`Address<Self>`] to `Recipient<RemoteMessage>` first and then erases the type.
276///
277/// See the documentation of [`Actor::type_erased_recipient_fn`] for more details.
278///
279/// # Example
280///
281/// ```ignore
282/// use acktor_derive::remote_actor;
283///
284/// #[remote_actor]
285/// impl Actor for MyActor {
286/// type Error = anyhow::Error;
287/// type Context = Context<Self>;
288/// }
289/// ```
290///
291/// [`Actor::type_erased_recipient_fn`]: https://docs.rs/acktor/latest/acktor/trait.Actor.html#method.type_erased_recipient_fn
292/// [`Address<Self>`]: https://docs.rs/acktor/latest/acktor/address/struct.Address.html
293#[cfg(feature = "ipc")]
294#[cfg_attr(docsrs, doc(cfg(feature = "ipc")))]
295#[proc_macro_attribute]
296pub fn remote_actor(_attr: TokenStream, item: TokenStream) -> TokenStream {
297 remote_actor_attr::expand(item.into()).into()
298}