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}