acktor_derive/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use proc_macro::TokenStream;
4
5mod message;
6mod message_id;
7mod message_response;
8mod stable_id;
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;
18#[cfg(feature = "ipc")]
19mod remote_addressable;
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 [`StableId`] trait for a type.
74///
75/// The generated `TYPE_ID` is the first 16 bytes of the SHA-256 digest of the type's
76/// fully-qualified path (`module_path!() + "::" + ident`).
77///
78/// If the type contains type generic parameters, the generated `TYPE_ID` is combined with each
79/// type generic parameter's `TYPE_ID` with [`StableTypeId::combine`] in their declaration order.
80///
81/// If the type contains const generic parameters, the generated `TYPE_ID` is combined with the
82/// first 16 bytes of the SHA-256 digest of the big-endian form of each const generic parameter
83/// with [`StableTypeId::combine`] in their declaration order.
84///
85/// # Example
86///
87/// ```ignore
88/// use acktor_derive::StableId;
89///
90/// #[derive(StableId)]
91/// struct Ping(u64);
92/// ```
93///
94/// [`StableId`]: https://docs.rs/acktor/latest/acktor/stable_type_id/trait.StableId.html
95/// [`StableTypeId::combine`]: https://docs.rs/acktor/latest/acktor/stable_type_id/struct.StableTypeId.html#method.combine
96#[proc_macro_derive(StableId)]
97pub fn stable_id_derive(input: TokenStream) -> TokenStream {
98 let ast = syn::parse(input).unwrap();
99
100 stable_id::expand(&ast).into()
101}
102
103/// Derive the [`MessageId`] trait for a [`Message`].
104///
105/// By default, the derive also emits a [`StableId`] impl and sets
106/// `MessageId::ID = StableId::TYPE_ID.as_u64()`. In this case, do **not** also derive
107/// [`StableId`] separately, as that would produce conflicting impls. See derive macro
108/// [`StableId`][macro@StableId] for the hashing scheme and the rules around generic parameters.
109///
110/// An optional `#[custom_id(<u64 value>)]` attribute lets the user supply the id directly. When
111/// present, no [`StableId`] impl is emitted, and it is the user's responsibility to ensure the
112/// id is unique across all messages an actor can handle.
113///
114/// # Example
115///
116/// ```ignore
117/// use acktor_derive::MessageId;
118///
119/// #[derive(MessageId)]
120/// struct Ping(u64);
121///
122/// #[derive(MessageId)]
123/// #[custom_id(0xdead_beef)]
124/// struct Pong;
125/// ```
126///
127/// [`MessageId`]: https://docs.rs/acktor/latest/acktor/message/trait.MessageId.html
128/// [`Message`]: https://docs.rs/acktor/latest/acktor/message/trait.Message.html
129/// [`StableId`]: https://docs.rs/acktor/latest/acktor/stable_type_id/trait.StableId.html
130#[proc_macro_derive(MessageId, attributes(custom_id))]
131pub fn message_id_derive(input: TokenStream) -> TokenStream {
132 let ast = syn::parse(input).unwrap();
133
134 message_id::expand(&ast).into()
135}
136
137/// Derive the [`Encode`] trait for a message.
138///
139/// A `#[codec(..)]` attribute must be present to select the serialization method and the same
140/// attribute is shared with [`Decode`]. Encoding and decoding of the same message type must use
141/// the same method. The attribute also supports an optional bridge type that serves as an
142/// intermediary for encoding and decoding, which is useful when the message type itself cannot
143/// directly implement the required traits. Currently there are three supported codec methods:
144///
145/// - `#[codec(prost)]` — delegates to [`prost::Message::encode_to_vec`]. The target type (or the
146/// bridge type) must implement [`prost::Message`].
147/// - `#[codec(zerocopy)]` — delegates to [`zerocopy::IntoBytes::as_bytes`]. The target type (or
148/// the bridge type) must implement [`zerocopy::IntoBytes`].
149/// - `#[codec(rkyv)]` — delegates to [`rkyv::to_bytes`]. The target type (or the bridge type)
150/// must implement [`rkyv::Serialize`].
151///
152/// If a bridge type `T` is specified, the bridge type must be convertible from the target type
153/// with `impl From<&Self> for T`.
154///
155/// # Example
156///
157/// ```ignore
158/// use acktor_derive::Encode;
159///
160/// #[derive(zerocopy::IntoBytes, Encode)]
161/// #[codec(zerocopy)]
162/// struct Ping(u64);
163/// ```
164///
165/// [`Encode`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/codec/trait.Encode.html
166/// [`Decode`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/codec/trait.Decode.html
167/// [`prost::Message`]: https://docs.rs/prost/latest/prost/trait.Message.html
168/// [`prost::Message::encode_to_vec`]: https://docs.rs/prost/latest/prost/trait.Message.html#method.encode_to_vec
169/// [`zerocopy::IntoBytes`]: https://docs.rs/zerocopy/latest/zerocopy/trait.IntoBytes.html
170/// [`zerocopy::IntoBytes::as_bytes`]: https://docs.rs/zerocopy/latest/zerocopy/trait.IntoBytes.html#method.as_bytes
171/// [`rkyv::to_bytes`]: https://docs.rs/rkyv/latest/rkyv/fn.to_bytes.html
172/// [`rkyv::Serialize`]: https://docs.rs/rkyv/latest/rkyv/trait.Serialize.html
173#[cfg(feature = "ipc")]
174#[cfg_attr(docsrs, doc(cfg(feature = "ipc")))]
175#[proc_macro_derive(Encode, attributes(codec))]
176pub fn encode_derive(input: TokenStream) -> TokenStream {
177 let ast = syn::parse(input).unwrap();
178
179 encode::expand(&ast).into()
180}
181
182/// Derive the [`Decode`] trait for a message.
183///
184/// A `#[codec(..)]` attribute must be present to select the deserialization method and the same
185/// attribute is shared with [`Decode`]. Encoding and decoding of the same message type must use
186/// the same method. The attribute also supports an optional bridge type that serves as an
187/// intermediary for encoding and decoding, which is useful when the message type itself cannot
188/// directly implement the required traits. Currently there are three supported codec methods:
189///
190/// - `#[codec(prost)]` — delegates to [`prost::Message::decode`]. The target type (or the bridge
191/// type) must implement [`prost::Message`].
192/// - `#[codec(zerocopy)]` — delegates to [`zerocopy::FromBytes::read_from_bytes`]. The target
193/// type (or the bridge type) must implement [`zerocopy::FromBytes`].
194/// - `#[codec(rkyv)]` — delegates to [`rkyv::from_bytes`]. The target type (or the bridge type)
195/// must implement [`rkyv::Archive`] and [`rkyv::Deserialize`].
196///
197/// If a bridge type `T` is specified, the target type must be convertible from the bridge type
198/// with `impl TryFrom<T> for Self` and use [`DecodeError`] as the error type.
199///
200/// # Example
201///
202/// ```ignore
203/// use acktor_derive::Decode;
204///
205/// #[derive(zerocopy::FromBytes, Decode)]
206/// #[codec(zerocopy)]
207/// struct Ping(u64);
208/// ```
209///
210/// [`Encode`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/codec/trait.Encode.html
211/// [`Decode`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/codec/trait.Decode.html
212/// [`prost::Message`]: https://docs.rs/prost/latest/prost/trait.Message.html
213/// [`prost::Message::decode`]: https://docs.rs/prost/latest/prost/trait.Message.html#method.decode
214/// [`zerocopy::FromBytes`]: https://docs.rs/zerocopy/latest/zerocopy/trait.FromBytes.html
215/// [`zerocopy::FromBytes::read_from_bytes`]: https://docs.rs/zerocopy/latest/zerocopy/trait.FromBytes.html#method.read_from_bytes
216/// [`rkyv::from_bytes`]: https://docs.rs/rkyv/latest/rkyv/fn.from_bytes.html
217/// [`rkyv::Archive`]: https://docs.rs/rkyv/latest/rkyv/trait.Archive.html
218/// [`rkyv::Deserialize`]: https://docs.rs/rkyv/latest/rkyv/trait.Deserialize.html
219/// [`DecodeError`]: https://docs.rs/acktor-ipc/latest/acktor_ipc/errors/enum.DecodeError.html
220#[cfg(feature = "ipc")]
221#[cfg_attr(docsrs, doc(cfg(feature = "ipc")))]
222#[proc_macro_derive(Decode, attributes(codec))]
223pub fn decode_derive(input: TokenStream) -> TokenStream {
224 let ast = syn::parse(input).unwrap();
225
226 decode::expand(&ast).into()
227}
228
229/// Derive the [`RemoteAddressable`] trait for an actor.
230///
231/// A `#[message(M1, M2, ...)]` attribute must be present to specify the list of messages the
232/// actor can handle remotely. For each message `Mi`, the actor must have implemented the
233/// [`Handler`] trait; `Mi` itself must have implemented the [`MessageId`] trait, the [`Encode`]
234/// trait and the [`Decode`] trait; `Mi::Result` must also have implemented the [`Encode`] trait
235/// and the [`Decode`] trait.
236///
237/// The macro emits a [`Codec`] impl and a `Handler<BinaryMessage>` impl for the actor based on
238/// the message list specified in the `#[message(..)]` attribute. The [`Codec`] impl provides a
239/// codec table which defines how to encode the message and decode the message response for each
240/// message type `Mi`. The `Handler<BinaryMessage>` impl dispatches inbound messages by matching
241/// the message id and invoking the corresponding message handler.
242///
243/// # Example
244///
245/// ```ignore
246/// use acktor_derive::RemoteAddressable;
247///
248/// #[derive(RemoteAddressable)]
249/// #[message(Ping, Echo)]
250/// pub struct MyActor;
251/// ```
252///
253/// [`RemoteAddressable`]: https://docs.rs/acktor/latest/acktor/actor/remote/trait.RemoteAddressable.html
254/// [`Handler`]: https://docs.rs/acktor/latest/acktor/message/trait.Handler.html
255/// [`MessageId`]: https://docs.rs/acktor/latest/acktor/message/index/trait.MessageId.html
256/// [`Encode`]: https://docs.rs/acktor/latest/acktor/codec/trait.Encode.html
257/// [`Decode`]: https://docs.rs/acktor/latest/acktor/codec/trait.Decode.html
258/// [`Codec`]: https://docs.rs/acktor/latest/acktor/codec/table/trait.Codec.html
259#[cfg(feature = "ipc")]
260#[cfg_attr(docsrs, doc(cfg(feature = "ipc")))]
261#[proc_macro_derive(RemoteAddressable, attributes(message))]
262pub fn remote_addressable_derive(input: TokenStream) -> TokenStream {
263 let ast = syn::parse(input).unwrap();
264
265 remote_addressable::expand(&ast).into()
266}
267
268/// Attribute macro applies to the `impl Actor for MyActor` block, which overrides the internal
269/// method `Actor::remote_mailbox` to return a [`RemoteMailbox`] for a remote addressable actor.
270///
271/// This is a temporary workaround since specialization is not yet stable in Rust.
272///
273/// # Example
274///
275/// ```ignore
276/// use acktor_derive::remote;
277///
278/// #[remote]
279/// impl Actor for MyActor {
280/// type Error = anyhow::Error;
281/// type Context = Context<Self>;
282/// }
283/// ```
284///
285/// [`RemoteMailbox`]: https://docs.rs/acktor/latest/acktor/actor/remote/type.RemoteMailbox.html
286#[cfg(feature = "ipc")]
287#[cfg_attr(docsrs, doc(cfg(feature = "ipc")))]
288#[proc_macro_attribute]
289pub fn remote(_attr: TokenStream, item: TokenStream) -> TokenStream {
290 remote::expand(item.into()).into()
291}