anachro_client/
table.rs

1//! The Client Table Interface
2//!
3//! This module contains items used for defining pubsub tables, which
4//! are the primary way of specifying the topics that the client is
5//! interested in publishing or subscribing to.
6//!
7//! Each client may define their own table of publish and subscribe topics,
8//! or may use a table defined by a common shared library. Different clients
9//! do not need to have the same table, but for successful operation, all
10//! clients must agree on the same data type used for a given path or wildcard
11//! path topic.
12
13use anachro_icd::arbitrator::SubMsg;
14use postcard;
15
16/// An error type used by the `Table` trait
17#[derive(Debug, PartialEq, Eq)]
18pub enum TableError {
19    NoMatch,
20    Postcard(postcard::Error),
21}
22
23/// A trait describing publish and subscription topics
24///
25/// This is used to interact with the `Client` interface.
26pub trait Table: Sized {
27    /// A slice of all paths that the client subscribes to
28    fn sub_paths() -> &'static [&'static str];
29
30    /// A slice of all paths that the client publishes to
31    fn pub_paths() -> &'static [&'static str];
32
33    /// Create a Table item from a given SubMsg`
34    fn from_pub_sub<'a>(msg: &'a SubMsg<'a>) -> Result<Self, TableError>;
35}
36
37/// A macro for defining a publish and subscribe table
38///
39/// This macro assists with generating a table that defines publish
40/// and subscription topics that implement the `Table` trait.
41///
42/// The first argument is the name of the table type. For "Subs" and "Pubs"
43/// sections, the format `Variant Name: "pub/sub/path" => Variant Type` is
44/// used. All Variant Names must be unique in the table. All Variant Types
45/// must implement `serde::Serialize`, `serde::de::DeserializeOwned`, and
46/// `Clone`.
47///
48/// All paths must be unique, and wildcard patterns must not overlap with
49/// other wildcards or fixed paths. Publish topics must not include wildcards.
50///
51/// ## Example
52///
53/// ```rust,no_run
54/// pubsub_table!{
55///     AnachroTable,
56///     Subs => {
57///         Something: "foo/bar/baz" => Demo,
58///         Else: "bib/bim/bap" => (),
59///     },
60///     Pubs => {
61///         Etwas: "short/send" => (),
62///         Anders: "send/short" => (),
63///     },
64/// }
65#[macro_export]
66macro_rules! pubsub_table {
67    (
68        $enum_ty:ident,
69        Subs => {
70            $($sub_variant_name:ident: $sub_path:expr => $sub_variant_ty:ty,)+
71        },
72        Pubs => {
73            $($pub_variant_name:ident: $pub_path:expr => $pub_variant_ty:ty,)+
74        },
75    ) => {
76        #[doc="
77            An Anachro Protocol Client Table
78
79            This is an Anachro Protocol Client Table generated by the
80            `pubsub_table!()` macro.
81        "]
82        #[derive(Debug, serde::Deserialize, Clone)]
83        pub enum $enum_ty {
84            $($sub_variant_name($sub_variant_ty)),+,
85            $($pub_variant_name($pub_variant_ty)),+,
86        }
87
88        impl $crate::Table for $enum_ty {
89            fn from_pub_sub<'a>(msg: &'a $crate::SubMsg<'a>) -> core::result::Result<Self, $crate::TableError> {
90                let msg_path = match msg.path {
91                    $crate::anachro_icd::PubSubPath::Long(ref path) => path.as_str(),
92                    $crate::anachro_icd::PubSubPath::Short(sid) => {
93                        if sid < $crate::PUBLISH_SHORTCODE_OFFSET {
94                            // Subscribe
95                            if (sid as usize) < Self::sub_paths().len() {
96                                Self::sub_paths()[(sid as usize)]
97                            } else {
98                                return Err($crate::TableError::NoMatch);
99                            }
100                        } else {
101                            // publish
102                            let new_sid = (sid as usize) - ($crate::PUBLISH_SHORTCODE_OFFSET as usize);
103                            if new_sid < Self::pub_paths().len() {
104                                Self::pub_paths()[new_sid]
105                            } else {
106                                return Err($crate::TableError::NoMatch);
107                            }
108                        }
109                    },
110                };
111                $(
112                    if $crate::anachro_icd::matches(msg_path, $sub_path) {
113                        return Ok(
114                            $enum_ty::$sub_variant_name(
115                                $crate::from_bytes(msg.payload)
116                                    .map_err(|e| $crate::TableError::Postcard(e))?
117                            )
118                        );
119                    }
120                )+
121                $(
122                    if $crate::anachro_icd::matches(msg_path, $pub_path) {
123                        return Ok(
124                            $enum_ty::$pub_variant_name(
125                                $crate::from_bytes(msg.payload)
126                                    .map_err(|e| $crate::TableError::Postcard(e))?
127                            )
128                        );
129                    }
130                )+
131                Err($crate::TableError::NoMatch)
132            }
133
134            fn sub_paths() -> &'static [&'static str] {
135                Self::sub_paths()
136            }
137
138            fn pub_paths() -> &'static [&'static str] {
139                Self::pub_paths()
140            }
141        }
142
143        impl $enum_ty {
144            #[doc = "
145                Get the publish path for a given variant.
146
147                Returns None if the given table type is for a subscription topic
148            "]
149            pub fn get_pub_path(&self) -> core::option::Option<&'static str> {
150                match self {
151                    $(
152                        $enum_ty::$pub_variant_name(_) => Some($pub_path),
153                    )+
154                    _ => None,
155                }
156            }
157
158            #[doc = "
159                Serialize the table variant to the given buffer
160
161                This serializes the table variant into the given buffer, typically used to
162                prepare a payload for publishing.
163
164                Returns an Error if serialization failed, typically due to not enough space
165                in the destination buffer.
166            "]
167            pub fn serialize<'a>(&self, buffer: &'a mut [u8]) -> core::result::Result<$crate::SendMsg<'a>, ()> {
168                match self {
169                    $(
170                        $enum_ty::$pub_variant_name(msg) => {
171                            Ok($crate::SendMsg {
172                                buf: $crate::to_slice(msg, buffer)
173                                        .map_err(drop)?,
174                                path: $pub_path,
175                            })
176                        },
177                    )+
178                    _ => Err(()),
179                }
180            }
181
182            #[doc = "Get a list of all subscription paths defined in the pubsub_table"]
183            pub const fn sub_paths() -> &'static [&'static str] {
184                const PATHS: &[&str] = &[
185                    $($sub_path,)+
186                ];
187
188                PATHS
189            }
190
191            #[doc = "Get a list of all publishing paths defined in the pubsub_table"]
192            pub const fn pub_paths() -> &'static [&'static str] {
193                const PATHS: &[&str] = &[
194                    $($pub_path,)+
195                ];
196
197                PATHS
198            }
199        }
200    };
201}