Skip to main content

sioc_macros/
lib.rs

1//! Derive macros for Sioc: generate typed Socket.IO event and acknowledgement types.
2//!
3//! The four core traits are intentionally separate so you only derive what each
4//! use-site needs:
5//!
6//! | Trait | Derive | Needed for |
7//! |---|---|---|
8//! | [`EventType`] | `#[derive(EventType)]` | event name constant + ack/binary policy |
9//! | [`AckType`] | `#[derive(AckType)]` | ack binary policy |
10//! | [`SerializePayload`] | `#[derive(SerializePayload)]` | emitting (outbound) |
11//! | [`DeserializePayload`] | `#[derive(DeserializePayload)]` | receiving (inbound) |
12//!
13//! `#[derive(EventRouter)]` is a convenience that generates `TryFrom<DynEvent>` for
14//! a dispatcher enum whose variants each wrap an `Event<E>`.
15//!
16//! # Struct-level attributes (`#[sioc(...)]`)
17//!
18//! | Attribute | Applies to | Effect |
19//! |---|---|---|
20//! | `event(name = "str")` | `EventType` | Override the event name (default: snake_case of struct name). |
21//! | `event(ack = Type)` | `EventType` | Set ack policy to `HasAck<Type>`; default is `NoAck`. |
22//! | `event(binary)` | `EventType` | Set binary policy to `HasBinary`; default is `NoBinary`. |
23//! | `ack(binary)` | `AckType` | Set binary policy to `HasBinary`; default is `NoBinary`. |
24//! | `strict` | `DeserializePayload` | Reject payloads with more elements than fields (error on trailing data). |
25//!
26//! # Field-level attributes (`#[sioc(...)]`)
27//!
28//! | Attribute | Applies to | Effect |
29//! |---|---|---|
30//! | `flatten` | `SerializePayload`, `DeserializePayload` | Serialize each element of this collection field as a separate array element; deserialize all remaining elements into it. Place last. |
31//!
32//! # Example
33//!
34//! ```rust,ignore
35//! // ignore: sioc-macros cannot dev-depend on sioc (circular); see sioc crate for runnable examples.
36//! use sioc::prelude::*;
37//!
38//! // Emit-only: only SerializePayload is required.
39//! #[derive(EventType, SerializePayload)]
40//! struct Ping;
41//!
42//! // Recv-only: only DeserializePayload is required.
43//! #[derive(EventType, DeserializePayload)]
44//! struct Pong;
45//!
46//! // Bidirectional with an explicit name.
47//! #[derive(EventType, SerializePayload, DeserializePayload)]
48//! #[sioc(event(name = "chat_message"))]
49//! struct ChatMessage {
50//!     text: String,
51//! }
52//!
53//! // Ack receive-only.
54//! #[derive(AckType, DeserializePayload)]
55//! struct ChatAck {
56//!     ok: bool,
57//! }
58//! ```
59
60mod ack_type;
61mod attrs;
62mod deserialize_payload;
63mod event_router;
64mod event_type;
65mod serialize_payload;
66
67use proc_macro::TokenStream;
68use syn::parse_macro_input;
69
70/// Derives the [`EventType`] trait, providing the event
71/// name constant and ack/binary policy associated types.
72///
73/// Serialization and deserialization are **not** included; derive
74/// [`SerializePayload`] for emit and [`DeserializePayload`] for recv as needed.
75///
76/// # Attributes
77///
78/// - `#[sioc(event(name = "str"))]`: override the wire name.
79///   Without it the struct name is converted to snake_case.
80/// - `#[sioc(event(ack = Type))]`: set `type Ack = HasAck<Type>` (default: `NoAck`).
81/// - `#[sioc(event(binary))]`: set `type Binary = HasBinary` (default: `NoBinary`).
82///
83/// # Example
84///
85/// ```rust,ignore
86/// // ignore: sioc-macros cannot dev-depend on sioc (circular); see sioc crate for runnable examples.
87/// use sioc::prelude::*;
88///
89/// // Emit-only; name defaults to "server_ping".
90/// #[derive(EventType, SerializePayload)]
91/// struct ServerPing;
92///
93/// // Recv-only with an explicit name.
94/// #[derive(EventType, DeserializePayload)]
95/// #[sioc(event(name = "user_joined"))]
96/// struct UserJoined {
97///     id: u64,
98///     name: String,
99/// }
100/// ```
101#[proc_macro_derive(EventType, attributes(sioc))]
102pub fn derive_event_type(input: TokenStream) -> TokenStream {
103    event_type::expand(parse_macro_input!(input))
104        .unwrap_or_else(|err| err.write_errors())
105        .into()
106}
107
108/// Derives the [`AckType`] trait, providing the binary
109/// policy associated type.
110///
111/// Serialization and deserialization are **not** included; derive
112/// [`SerializePayload`] to send acks and [`DeserializePayload`] to receive them.
113///
114/// # Attributes
115///
116/// - `#[sioc(ack(binary))]`: set `type Binary = HasBinary` (default: `NoBinary`).
117///
118/// # Example
119///
120/// ```rust,ignore
121/// // ignore: sioc-macros cannot dev-depend on sioc (circular); see sioc crate for runnable examples.
122/// use sioc::prelude::*;
123///
124/// // Receive acks only.
125/// #[derive(AckType, DeserializePayload)]
126/// struct SaveAck {
127///     saved: bool,
128///     id: u64,
129/// }
130/// ```
131#[proc_macro_derive(AckType, attributes(sioc))]
132pub fn derive_ack_type(input: TokenStream) -> TokenStream {
133    ack_type::expand(parse_macro_input!(input))
134        .unwrap_or_else(|err| err.write_errors())
135        .into()
136}
137
138/// Derives the [`SerializePayload`] trait.
139///
140/// Serializes the struct's fields as sequential elements of a JSON array.
141/// Used for the **emit** (outbound) direction. Pair with
142/// [`EventType`] or [`AckType`].
143///
144/// # Field attributes
145///
146/// - `#[sioc(flatten)]`: serialize each element of this collection field as a
147///   separate array element. Recommended on the last field.
148///
149/// # Example
150///
151/// ```rust,ignore
152/// // ignore: sioc-macros cannot dev-depend on sioc (circular); see sioc crate for runnable examples.
153/// use sioc::prelude::*;
154///
155/// #[derive(EventType, SerializePayload)]
156/// struct Move {
157///     x: i32,
158///     y: i32,
159///     #[sioc(flatten)]
160///     tags: Vec<String>,
161/// }
162/// ```
163#[proc_macro_derive(SerializePayload, attributes(sioc))]
164pub fn derive_serialize_payload(input: TokenStream) -> TokenStream {
165    serialize_payload::expand(parse_macro_input!(input))
166        .unwrap_or_else(|err| err.write_errors())
167        .into()
168}
169
170/// Derives the [`DeserializePayload`] trait.
171///
172/// Deserializes the struct's fields from sequential elements of a JSON array.
173/// Used for the **recv** (inbound) direction. Pair with
174/// [`EventType`] or [`AckType`].
175///
176/// # Struct attributes
177///
178/// - `#[sioc(strict)]`: return an error if the array contains more elements
179///   than the struct has fields. Without it trailing elements are silently ignored.
180///
181/// # Field attributes
182///
183/// - `#[sioc(flatten)]`: deserialize all remaining array elements into this
184///   collection field. Recommended on the last field; fields after it cannot be
185///   deserialized because flatten consumes the remainder of the sequence.
186///
187/// # Example
188///
189/// ```rust,ignore
190/// // ignore: sioc-macros cannot dev-depend on sioc (circular); see sioc crate for runnable examples.
191/// use sioc::prelude::*;
192///
193/// // Accepts trailing elements.
194/// #[derive(EventType, DeserializePayload)]
195/// struct Join { room: String }
196///
197/// // Rejects trailing elements.
198/// #[derive(EventType, DeserializePayload)]
199/// #[sioc(strict)]
200/// struct Ping;
201///
202/// // Collects trailing elements.
203/// #[derive(EventType, DeserializePayload)]
204/// struct Stream {
205///     id: String,
206///     #[sioc(flatten)]
207///     tags: Vec<serde_json::Value>,
208/// }
209/// ```
210#[proc_macro_derive(DeserializePayload, attributes(sioc))]
211pub fn derive_deserialize_payload(input: TokenStream) -> TokenStream {
212    deserialize_payload::expand(parse_macro_input!(input))
213        .unwrap_or_else(|err: darling::Error| err.write_errors())
214        .into()
215}
216
217/// Derives `TryFrom<DynEvent>` for a dispatcher enum.
218///
219/// Each variant must be a newtype wrapping `Event<E>` for some `E: EventType + DeserializePayload`.
220///
221/// The macro generates an internal helper enum with a `serde::Deserialize` impl
222/// that dispatches on the event name string, plus a `TryFrom<DynEvent>` impl that
223/// routes each deserialized payload to the matching variant.
224///
225/// # Example
226///
227/// ```rust,ignore
228/// // ignore: sioc-macros cannot dev-depend on sioc (circular); see sioc crate for runnable examples.
229/// use sioc::prelude::*;
230///
231/// #[derive(Debug, EventType, DeserializePayload)]
232/// struct ChatMsg { text: String }
233///
234/// #[derive(Debug, EventType, DeserializePayload)]
235/// struct UserJoined { name: String }
236///
237/// #[derive(Debug, EventRouter)]
238/// enum AppEvent {
239///     ChatMsg(Event<ChatMsg>),
240///     UserJoined(Event<UserJoined>),
241/// }
242/// ```
243#[proc_macro_derive(EventRouter, attributes(sioc))]
244pub fn derive_from_event(input: TokenStream) -> TokenStream {
245    event_router::expand(parse_macro_input!(input))
246        .unwrap_or_else(|err: darling::Error| err.write_errors())
247        .into()
248}