postcard_rpc/
macros.rs

1/// ## Endpoint macro
2///
3/// Used to define a single Endpoint marker type that implements the
4/// [Endpoint][crate::Endpoint] trait.
5///
6/// Prefer the [`endpoints!()`][crate::endpoints] macro instead.
7///
8/// ```rust
9/// # use postcard_schema::Schema;
10/// # use serde::{Serialize, Deserialize};
11/// use postcard_rpc::endpoint;
12///
13/// #[derive(Debug, Serialize, Deserialize, Schema)]
14/// pub struct Req1 {
15///     a: u8,
16///     b: u64,
17/// }
18///
19/// #[derive(Debug, Serialize, Deserialize, Schema)]
20/// pub struct Resp1 {
21///     c: [u8; 4],
22///     d: i32,
23/// }
24///
25/// endpoint!(Endpoint1, Req1, Resp1, "endpoint/1");
26/// ```
27///
28/// If the path is omitted, the type name is used instead.
29#[macro_export]
30macro_rules! endpoint {
31    ($tyname:ident, $req:ty, $resp:ty) => {
32        endpoint!($tyname, $req, $resp, stringify!($tyname));
33    };
34    ($tyname:ident, $req:ty, $resp:ty, $path:expr,) => {
35        endpoint!($tyname, $req, $resp, $path)
36    };
37    ($tyname:ident, $req:ty, $resp:ty, $path:expr) => {
38        pub struct $tyname;
39
40        impl $crate::Endpoint for $tyname {
41            type Request = $req;
42            type Response = $resp;
43            const PATH: &'static str = $path;
44            const REQ_KEY: $crate::Key = $crate::Key::for_path::<$req>($path);
45            const RESP_KEY: $crate::Key = $crate::Key::for_path::<$resp>($path);
46        }
47    };
48}
49
50/// ## Endpoints macro
51///
52/// Used to define multiple Endpoint marker types that implements the
53/// [Endpoint][crate::Endpoint] trait.
54///
55/// NOTE: Do NOT set the `omit_std` flag in your code! This is for internal use only.
56///
57/// ```rust
58/// # use postcard_schema::Schema;
59/// # use serde::{Serialize, Deserialize};
60/// use postcard_rpc::endpoints;
61///
62/// #[derive(Debug, Serialize, Deserialize, Schema)]
63/// pub struct Req1 {
64///     a: u8,
65///     b: u64,
66/// }
67///
68/// #[derive(Debug, Serialize, Deserialize, Schema)]
69/// pub struct Resp1 {
70///     c: [u8; 4],
71///     d: i32,
72/// }
73///
74/// #[derive(Debug, Serialize, Deserialize, Schema)]
75/// pub struct Req2 {
76///     a: i8,
77///     b: i64,
78/// }
79///
80/// #[derive(Debug, Serialize, Deserialize, Schema)]
81/// pub struct Resp2 {
82///     c: [i8; 4],
83///     d: u32,
84/// }
85///
86/// endpoints!{
87///     list = ENDPOINTS_LIST;
88///     | EndpointTy     | RequestTy     | ResponseTy    | Path              |
89///     | ----------     | ---------     | ----------    | ----              |
90///     | Endpoint1      | Req1          | Resp1         | "endpoints/one"   |
91///     | Endpoint2      | Req2          | Resp2         | "endpoints/two"   |
92/// }
93/// ```
94#[macro_export]
95macro_rules! endpoints {
96    (@ep_tys $([[$($meta:meta)?] $ep_name:ident])*) => {
97        $crate::endpoints!(@ep_tys omit_std=false; $([[$($meta)?] $ep_name])*)
98    };
99    (@ep_tys omit_std=true; $([[$($meta:meta)?] $ep_name:ident])*) => {
100        const {
101            const LISTS: &[&[&'static $crate::postcard_schema::schema::NamedType]] = &[
102                $(
103                    $(#[$meta])?
104                    $crate::unique_types!(<$ep_name as $crate::Endpoint>::Request),
105                    $(#[$meta])?
106                    $crate::unique_types!(<$ep_name as $crate::Endpoint>::Response),
107                )*
108            ];
109
110            const TTL_COUNT: usize = $crate::uniques::total_len(LISTS);
111            const BIG_RPT: ([Option<&'static $crate::postcard_schema::schema::NamedType>; TTL_COUNT], usize) = $crate::uniques::merge_nty_lists(LISTS);
112            const SMALL_RPT: [&'static $crate::postcard_schema::schema::NamedType; BIG_RPT.1] = $crate::uniques::cruncher(BIG_RPT.0.as_slice());
113            SMALL_RPT.as_slice()
114        }
115    };
116    (@ep_tys omit_std=false; $([[$($meta:meta)?] $ep_name:ident])*) => {
117        const {
118            const USER_TYS: &[&'static $crate::postcard_schema::schema::NamedType] =
119                $crate::endpoints!(@ep_tys omit_std=true; $([[$($meta)?] $ep_name])*);
120            const STD_TYS: &[&'static $crate::postcard_schema::schema::NamedType]
121                = $crate::standard_icd::STANDARD_ICD_ENDPOINTS.types;
122
123            const BOTH: &[&[&'static $crate::postcard_schema::schema::NamedType]] = &[
124                USER_TYS, STD_TYS,
125            ];
126            const TTL_COUNT: usize = $crate::uniques::total_len(BOTH);
127            const BIG_RPT: ([Option<&'static $crate::postcard_schema::schema::NamedType>; TTL_COUNT], usize) = $crate::uniques::merge_nty_lists(BOTH);
128            const SMALL_RPT: [&'static $crate::postcard_schema::schema::NamedType; BIG_RPT.1] = $crate::uniques::cruncher(BIG_RPT.0.as_slice());
129            SMALL_RPT.as_slice()
130        }
131    };
132    (@ep_eps $([[$($meta:meta)?] $ep_name:ident])*) => {
133        $crate::endpoints!(@ep_eps omit_std=false; $([[$($meta)?] $ep_name])*)
134    };
135    (@ep_eps omit_std=true; $([[$($meta:meta)?] $ep_name:ident])*) => {
136        &[
137            $(
138                $(#[$meta])?
139                (
140                    <$ep_name as $crate::Endpoint>::PATH,
141                    <$ep_name as $crate::Endpoint>::REQ_KEY,
142                    <$ep_name as $crate::Endpoint>::RESP_KEY,
143                ),
144            )*
145        ]
146    };
147    (@ep_eps omit_std=false; $([[$($meta:meta)?] $ep_name:ident])*) => {
148        const {
149            const USER_EPS: &[(&str, $crate::Key, $crate::Key)] =
150                $crate::endpoints!(@ep_eps omit_std=true; $([[$($meta)?] $ep_name])*);
151            const NULL_KEY: $crate::Key = unsafe { $crate::Key::from_bytes([0u8; 8]) };
152            const STD_EPS: &[(&str, $crate::Key, $crate::Key)] =
153                $crate::standard_icd::STANDARD_ICD_ENDPOINTS.endpoints;
154
155            $crate::concat_arrays! {
156                init = ("", NULL_KEY, NULL_KEY);
157                ty = (&str, $crate::Key, $crate::Key);
158                [STD_EPS, USER_EPS]
159            }
160        }
161    };
162    (
163           list = $list_name:ident;
164           $(omit_std = $omit:tt;)?
165           | EndpointTy     | RequestTy                                | ResponseTy                                  | Path              | $( Cfg           |)?
166           | $(-)*          | $(-)*                                    | $(-)*                                       | $(-)*             | $($(-)*          |)?
167        $( | $ep_name:ident | $req_ty:tt $(< $($req_lt:lifetime),+ >)? | $resp_ty:tt $(< $($resp_lt:lifetime),+ >)?  | $path_str:literal | $($meta:meta)? $(|)? )*
168    ) => {
169        // struct definitions and trait impls
170        $(
171            /// Macro Generated Marker Type
172            $(#[$meta])?
173            pub struct $ep_name < $($($req_lt,)+)? $($($resp_lt,)+)? > {
174                $(
175                    _plt_req: core::marker::PhantomData<($(& $req_lt (),)+)>,
176                )?
177                $(
178                    _plt_resp: core::marker::PhantomData<($(& $resp_lt (),)+)>,
179                )?
180                _priv: core::marker::PhantomData<()>,
181            }
182
183            $(#[$meta])?
184            impl < $($($req_lt,)+)? $($($resp_lt,)+)? > $crate::Endpoint for $ep_name < $($($req_lt,)+)? $($($resp_lt,)+)? > {
185                type Request = $req_ty $(< $($req_lt,)+ >)?;
186                type Response = $resp_ty $(< $($resp_lt,)+ >)?;
187                const PATH: &'static str = $path_str;
188                const REQ_KEY: $crate::Key = $crate::Key::for_path::<$req_ty>($path_str);
189                const RESP_KEY: $crate::Key = $crate::Key::for_path::<$resp_ty>($path_str);
190            }
191        )*
192
193        /// Macro Generated Endpoint Map
194        pub const $list_name: $crate::EndpointMap = $crate::EndpointMap {
195            types: $crate::endpoints!(@ep_tys $(omit_std = $omit;)? $([[$($meta)?] $ep_name])*),
196            endpoints: $crate::endpoints!(@ep_eps $(omit_std = $omit;)? $([[$($meta)?] $ep_name])*),
197        };
198    };
199}
200
201/// ## Topic macro
202///
203/// Used to define a single Topic marker type that implements the
204/// [Topic][crate::Topic] trait.
205///
206/// Prefer the [`topics!()` macro](crate::topics) macro instead.
207///
208/// ```rust
209/// # use postcard_schema::Schema;
210/// # use serde::{Serialize, Deserialize};
211/// use postcard_rpc::topic;
212///
213/// #[derive(Debug, Serialize, Deserialize, Schema)]
214/// pub struct Message1 {
215///     a: u8,
216///     b: u64,
217/// }
218///
219/// topic!(Topic1, Message1, "topic/1");
220/// ```
221///
222/// If the path is omitted, the type name is used instead.
223#[macro_export]
224macro_rules! topic {
225    ($tyname:ident, $msg:ty) => {
226        topic!($tyname, $msg, stringify!($tyname));
227    };
228    ($tyname:ident, $msg:ty, $path:expr,) => {
229        topic!($tyname, $msg, $path)
230    };
231    ($tyname:ident, $msg:ty, $path:expr) => {
232        /// $tyname - A Topic definition type
233        ///
234        /// Generated by the `topic!()` macro
235        pub struct $tyname;
236
237        impl $crate::Topic for $tyname {
238            type Message = $msg;
239            const PATH: &'static str = $path;
240            const TOPIC_KEY: $crate::Key = $crate::Key::for_path::<$msg>($path);
241        }
242    };
243}
244
245/// ## Topics macro
246///
247/// Used to define multiple Topic marker types that implements the
248/// [Topic][crate::Topic] trait.
249///
250/// NOTE: Do NOT set the `omit_std` flag in your code! This is for internal use only.
251///
252/// ```rust
253/// # use postcard_schema::Schema;
254/// # use serde::{Serialize, Deserialize};
255/// use postcard_rpc::{topics, TopicDirection};
256///
257/// #[derive(Debug, Serialize, Deserialize, Schema)]
258/// pub struct Message1 {
259///     a: u8,
260///     b: u64,
261/// }
262///
263/// #[derive(Debug, Serialize, Deserialize, Schema)]
264/// pub struct Message2 {
265///     a: i8,
266///     b: i64,
267/// }
268///
269/// topics!{
270///    list = TOPIC_LIST_NAME;
271///    direction = TopicDirection::ToServer;
272///    | TopicTy        | MessageTy     | Path              |
273///    | -------        | ---------     | ----              |
274///    | Topic1         | Message1      | "topics/one"      |
275///    | Topic2         | Message2      | "topics/two"      |
276/// }
277/// ```
278#[macro_export]
279macro_rules! topics {
280    (@tp_tys ( $dir:expr ) $([[$($meta:meta)?] $tp_name:ident])*) => {
281        $crate::topics!(@tp_tys ( $dir ) omit_std=false; $([[$($meta)?] $tp_name])*)
282    };
283    (@tp_tys ( $dir:expr ) omit_std=true; $([[$($meta:meta)?] $tp_name:ident])*) => {
284        const {
285            const LISTS: &[&[&'static $crate::postcard_schema::schema::NamedType]] = &[
286                $(
287                    $(#[$meta])?
288                    $crate::unique_types!(<$tp_name as $crate::Topic>::Message),
289                )*
290            ];
291
292            const TTL_COUNT: usize = $crate::uniques::total_len(LISTS);
293            const BIG_RPT: ([Option<&'static $crate::postcard_schema::schema::NamedType>; TTL_COUNT], usize) = $crate::uniques::merge_nty_lists(LISTS);
294            const SMALL_RPT: [&'static $crate::postcard_schema::schema::NamedType; BIG_RPT.1] = $crate::uniques::cruncher(BIG_RPT.0.as_slice());
295            SMALL_RPT.as_slice()
296        }
297    };
298    (@tp_tys ( $dir:expr ) omit_std=false; $([[$($meta:meta)?] $tp_name:ident])*) => {
299        const {
300            const USER_TYS: &[&'static $crate::postcard_schema::schema::NamedType] =
301                $crate::topics!(@tp_tys ( $dir ) omit_std=true; $([[$($meta)?] $tp_name])*);
302            const STD_TYS: &[&'static $crate::postcard_schema::schema::NamedType] = const {
303                match $dir {
304                    $crate::TopicDirection::ToServer => $crate::standard_icd::STANDARD_ICD_TOPICS_IN.types,
305                    $crate::TopicDirection::ToClient => $crate::standard_icd::STANDARD_ICD_TOPICS_OUT.types,
306                }
307            };
308
309            const BOTH: &[&[&'static $crate::postcard_schema::schema::NamedType]] = &[
310                STD_TYS, USER_TYS,
311            ];
312            const TTL_COUNT: usize = $crate::uniques::total_len(BOTH);
313            const BIG_RPT: ([Option<&'static $crate::postcard_schema::schema::NamedType>; TTL_COUNT], usize) = $crate::uniques::merge_nty_lists(BOTH);
314            const SMALL_RPT: [&'static $crate::postcard_schema::schema::NamedType; BIG_RPT.1] = $crate::uniques::cruncher(BIG_RPT.0.as_slice());
315            SMALL_RPT.as_slice()
316        }
317    };
318    (@tp_tps ( $dir:expr ) $([[$($meta:meta)?] $tp_name:ident])*) => {
319        $crate::topics!(@tp_tps ( $dir ) omit_std=false; $([[$($meta)?] $tp_name])*)
320    };
321    (@tp_tps ( $dir:expr ) omit_std=true; $([[$($meta:meta)?] $tp_name:ident])*) => {
322        &[
323            $(
324                $(#[$meta])?
325                (
326                    <$tp_name as $crate::Topic>::PATH,
327                    <$tp_name as $crate::Topic>::TOPIC_KEY,
328                ),
329            )*
330        ]
331    };
332    (@tp_tps ( $dir:expr ) omit_std=false; $([[$($meta:meta)?] $tp_name:ident])*) => {
333        const {
334            const USER_TPS: &[(&str, $crate::Key)] =
335                $crate::topics!(@tp_tps ( $dir ) omit_std=true; $([[$($meta)?] $tp_name])*);
336            const NULL_KEY: $crate::Key = unsafe { $crate::Key::from_bytes([0u8; 8]) };
337            const STD_TPS: &[(&str, $crate::Key)] = const {
338                match $dir {
339                    $crate::TopicDirection::ToServer => $crate::standard_icd::STANDARD_ICD_TOPICS_IN.topics,
340                    $crate::TopicDirection::ToClient => $crate::standard_icd::STANDARD_ICD_TOPICS_OUT.topics,
341                }
342            };
343
344            $crate::concat_arrays! {
345                init = ("", NULL_KEY);
346                ty = (&str, $crate::Key);
347                [STD_TPS, USER_TPS]
348            }
349        }
350    };
351    (
352        list = $list_name:ident;
353        direction = $direction:expr;
354        $(omit_std = $omit:tt;)?
355        | TopicTy        | MessageTy                                | Path              | $( Cfg           |)?
356        | $(-)*          | $(-)*                                    | $(-)*             | $($(-)*          |)?
357      $(| $tp_name:ident | $msg_ty:tt $(< $($msg_lt:lifetime),+ >)? | $path_str:literal | $($meta:meta)? $(|)?)*
358    ) => {
359        // struct definitions and trait impls
360        $(
361            /// $tp_name - A Topic definition type
362            ///
363            /// Generated by the `topics!()` macro
364            $(#[$meta])?
365            pub struct $tp_name $(< $($msg_lt,)+ >)? {
366                $(
367                    _plt: core::marker::PhantomData<($(& $msg_lt (),)+)>,
368                )?
369                _priv: core::marker::PhantomData<()>,
370            }
371
372            $(#[$meta])?
373            impl $(< $($msg_lt),+ >)? $crate::Topic for $tp_name $(< $($msg_lt,)+ >)? {
374                type Message = $msg_ty $(< $($msg_lt,)+ >)?;
375                const PATH: &'static str = $path_str;
376                const TOPIC_KEY: $crate::Key = $crate::Key::for_path::<$msg_ty>($path_str);
377            }
378        )*
379
380        /// Macro Generated Topic Map
381        pub const $list_name: $crate::TopicMap = $crate::TopicMap {
382            direction: $direction,
383            types: $crate::topics!(@tp_tys ( $direction ) $(omit_std = $omit;)? $([[$($meta)?] $tp_name])*),
384            topics: $crate::topics!(@tp_tps ( $direction ) $(omit_std = $omit;)? $([[$($meta)?] $tp_name])*),
385        };
386    };
387}
388
389/// A macro for turning `&[&[T]]` into `&[T]`
390#[macro_export]
391macro_rules! concat_arrays {
392    (
393        init = $init:expr;
394        ty = $tyname:ty;
395        [$($arr:ident),+]
396    ) => {
397        const {
398            const SLI: &[&[$tyname]] = &[
399                $($arr,)+
400            ];
401            const LEN: usize = $crate::uniques::total_len(SLI);
402            const ARR: [$tyname; LEN] = $crate::uniques::combine_with_copy(SLI, $init);
403
404            ARR.as_slice()
405        }
406    };
407}
408
409#[cfg(test)]
410mod concat_test {
411    #[test]
412    fn concats() {
413        const A: &[u32] = &[1, 2, 3];
414        const B: &[u32] = &[4, 5, 6];
415        const BOTH: &[u32] = concat_arrays!(
416            init = 0xFFFF_FFFF;
417            ty = u32;
418            [A, B]
419        );
420        assert_eq!(BOTH, [1, 2, 3, 4, 5, 6]);
421    }
422}
423
424/// A helper function for logging with the [Sender][crate::server::Sender]
425#[macro_export]
426macro_rules! sender_fmt {
427    ($sender:ident, $($arg:tt)*) => {
428        $sender.log_fmt(format_args!($($arg)*))
429    };
430    ($($arg:tt)*) => {
431        compile_error!("You must pass the sender to `sender_log`!");
432    }
433}
434
435#[cfg(test)]
436mod endpoints_test {
437    use postcard_schema::{schema::owned::OwnedNamedType, Schema};
438    use serde::{Deserialize, Serialize};
439
440    #[derive(Serialize, Deserialize, Schema)]
441    pub struct AReq(pub u8);
442    #[derive(Serialize, Deserialize, Schema)]
443    pub struct AResp(pub u16);
444    #[derive(Serialize, Deserialize, Schema)]
445    pub struct BTopic(pub u32);
446
447    endpoints! {
448        list = ENDPOINT_LIST;
449        | EndpointTy     | RequestTy     | ResponseTy    | Path              |
450        | ----------     | ---------     | ----------    | ----              |
451        | AlphaEndpoint1 | AReq          | AResp         | "test/alpha1"     |
452        | AlphaEndpoint2 | AReq          | AResp         | "test/alpha2"     |
453        | AlphaEndpoint3 | AReq          | AResp         | "test/alpha3"     |
454    }
455
456    topics! {
457        list = TOPICS_IN_LIST;
458        direction = crate::TopicDirection::ToServer;
459        | TopicTy        | MessageTy     | Path              |
460        | ----------     | ---------     | ----              |
461        | BetaTopic1     | BTopic        | "test/in/beta1"   |
462        | BetaTopic2     | BTopic        | "test/in/beta2"   |
463        | BetaTopic3     | BTopic        | "test/in/beta3"   |
464    }
465
466    topics! {
467        list = TOPICS_OUT_LIST;
468        direction = crate::TopicDirection::ToClient;
469        | TopicTy        | MessageTy     | Path              |
470        | ----------     | ---------     | ----              |
471        | BetaTopic4     | BTopic        | "test/out/beta1"  |
472    }
473
474    #[test]
475    fn eps() {
476        for ep in ENDPOINT_LIST.types {
477            println!("{}", OwnedNamedType::from(*ep));
478        }
479        assert_eq!(ENDPOINT_LIST.types.len(), 3);
480        for ep in ENDPOINT_LIST.endpoints {
481            println!("{}", ep.0);
482        }
483        assert_eq!(ENDPOINT_LIST.endpoints.len(), 5);
484    }
485
486    #[test]
487    fn tps() {
488        for tp in TOPICS_IN_LIST.types {
489            println!("TY IN:  {}", OwnedNamedType::from(*tp));
490        }
491        for tp in TOPICS_IN_LIST.topics {
492            println!("TP IN:  {}", tp.0);
493        }
494        for tp in TOPICS_OUT_LIST.types {
495            println!("TY OUT: {}", OwnedNamedType::from(*tp));
496        }
497        for tp in TOPICS_OUT_LIST.topics {
498            println!("TP OUT: {}", tp.0);
499        }
500        assert_eq!(TOPICS_IN_LIST.types.len(), 1);
501        assert_eq!(TOPICS_IN_LIST.topics.len(), 3);
502        assert_eq!(TOPICS_OUT_LIST.types.len(), 5);
503        assert_eq!(TOPICS_OUT_LIST.topics.len(), 3);
504    }
505}