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