zenoh_protocol/core/
wire_expr.rs

1//
2// Copyright (c) 2023 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14
15//! This module defines the wire representation of Key Expressions.
16use alloc::{
17    borrow::Cow,
18    string::{String, ToString},
19};
20use core::{convert::TryInto, fmt, sync::atomic::AtomicU16};
21
22use zenoh_keyexpr::{keyexpr, OwnedKeyExpr};
23use zenoh_result::{bail, ZResult};
24
25use crate::network::Mapping;
26
27/// A numerical Id mapped to a key expression.
28pub type ExprId = u16;
29pub type ExprLen = u16;
30
31pub type AtomicExprId = AtomicU16;
32pub const EMPTY_EXPR_ID: ExprId = 0;
33
34/// A zenoh **resource** is represented by a pair composed by a **key** and a
35/// **value**, such as, ```(car/telemetry/speed, 320)```.  A **resource key**
36/// is an arbitrary array of characters, with the exclusion of the symbols
37/// ```*```, ```**```, ```?```, ```[```, ```]```, and ```#```,
38/// which have special meaning in the context of zenoh.
39///
40/// A key including any number of the wildcard symbols, ```*``` and ```**```,
41/// such as, ```/car/telemetry/*```, is called a **key expression** as it
42/// denotes a set of keys. The wildcard character ```*``` expands to an
43/// arbitrary string not including zenoh's reserved characters and the ```/```
44/// character, while the ```**``` expands to  strings that may also include the
45/// ```/``` character.  
46///
47/// Finally, it is worth mentioning that for time and space efficiency matters,
48/// zenoh will automatically map key expressions to small integers. The mapping is automatic,
49/// but it can be triggered excplicily by with `zenoh::Session::declare_keyexpr()`.
50///
51//
52//  7 6 5 4 3 2 1 0
53// +-+-+-+-+-+-+-+-+
54// ~      id       — if Expr: id=0
55// +-+-+-+-+-+-+-+-+
56// ~    suffix     ~ if flag K==1 in Message's header
57// +---------------+
58//
59#[derive(PartialEq, Eq, Hash, Clone, Debug)]
60pub struct WireExpr<'a> {
61    pub scope: ExprId, // 0 marks global scope
62    pub suffix: Cow<'a, str>,
63    pub mapping: Mapping,
64}
65
66impl<'a> WireExpr<'a> {
67    pub fn empty() -> Self {
68        WireExpr {
69            scope: 0,
70            suffix: "".into(),
71            mapping: Mapping::Sender,
72        }
73    }
74
75    pub fn is_empty(&self) -> bool {
76        self.scope == 0 && self.suffix.as_ref().is_empty()
77    }
78
79    pub fn as_str(&'a self) -> &'a str {
80        if self.scope == 0 {
81            self.suffix.as_ref()
82        } else {
83            "<encoded_expr>"
84        }
85    }
86
87    pub fn try_as_str(&'a self) -> ZResult<&'a str> {
88        if self.scope == EMPTY_EXPR_ID {
89            Ok(self.suffix.as_ref())
90        } else {
91            bail!("Scoped key expression")
92        }
93    }
94
95    pub fn as_id(&'a self) -> ExprId {
96        self.scope
97    }
98
99    pub fn try_as_id(&'a self) -> ZResult<ExprId> {
100        if self.has_suffix() {
101            bail!("Suffixed key expression")
102        } else {
103            Ok(self.scope)
104        }
105    }
106
107    pub fn as_id_and_suffix(&'a self) -> (ExprId, &'a str) {
108        (self.scope, self.suffix.as_ref())
109    }
110
111    pub fn has_suffix(&self) -> bool {
112        !self.suffix.as_ref().is_empty()
113    }
114
115    pub fn to_owned(&self) -> WireExpr<'static> {
116        WireExpr {
117            scope: self.scope,
118            suffix: self.suffix.to_string().into(),
119            mapping: self.mapping,
120        }
121    }
122
123    pub fn with_suffix(mut self, suffix: &'a str) -> Self {
124        if self.suffix.is_empty() {
125            self.suffix = suffix.into();
126        } else {
127            self.suffix += suffix;
128        }
129        self
130    }
131}
132
133impl TryInto<String> for WireExpr<'_> {
134    type Error = zenoh_result::Error;
135    fn try_into(self) -> Result<String, Self::Error> {
136        if self.scope == 0 {
137            Ok(self.suffix.into_owned())
138        } else {
139            bail!("Scoped key expression")
140        }
141    }
142}
143
144impl TryInto<ExprId> for WireExpr<'_> {
145    type Error = zenoh_result::Error;
146    fn try_into(self) -> Result<ExprId, Self::Error> {
147        self.try_as_id()
148    }
149}
150
151impl From<ExprId> for WireExpr<'_> {
152    fn from(scope: ExprId) -> Self {
153        Self {
154            scope,
155            suffix: "".into(),
156            mapping: Mapping::Sender,
157        }
158    }
159}
160
161impl<'a> From<&'a OwnedKeyExpr> for WireExpr<'a> {
162    fn from(val: &'a OwnedKeyExpr) -> Self {
163        WireExpr {
164            scope: 0,
165            suffix: Cow::Borrowed(val.as_str()),
166            mapping: Mapping::Sender,
167        }
168    }
169}
170
171impl<'a> From<&'a keyexpr> for WireExpr<'a> {
172    fn from(val: &'a keyexpr) -> Self {
173        WireExpr {
174            scope: 0,
175            suffix: Cow::Borrowed(val.as_str()),
176            mapping: Mapping::Sender,
177        }
178    }
179}
180
181impl fmt::Display for WireExpr<'_> {
182    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
183        if self.scope == 0 {
184            write!(f, "{}", self.suffix)
185        } else {
186            write!(f, "{}:{:?}:{}", self.scope, self.mapping, self.suffix)
187        }
188    }
189}
190
191impl<'a> From<&WireExpr<'a>> for WireExpr<'a> {
192    #[inline]
193    fn from(key: &WireExpr<'a>) -> WireExpr<'a> {
194        key.clone()
195    }
196}
197
198impl<'a> From<&'a str> for WireExpr<'a> {
199    #[inline]
200    fn from(name: &'a str) -> WireExpr<'a> {
201        WireExpr {
202            scope: 0,
203            suffix: name.into(),
204            mapping: Mapping::Sender,
205        }
206    }
207}
208
209impl From<String> for WireExpr<'_> {
210    #[inline]
211    fn from(name: String) -> WireExpr<'static> {
212        WireExpr {
213            scope: 0,
214            suffix: name.into(),
215            mapping: Mapping::Sender,
216        }
217    }
218}
219
220impl<'a> From<&'a String> for WireExpr<'a> {
221    #[inline]
222    fn from(name: &'a String) -> WireExpr<'a> {
223        WireExpr {
224            scope: 0,
225            suffix: name.into(),
226            mapping: Mapping::Sender,
227        }
228    }
229}
230
231impl WireExpr<'_> {
232    #[cfg(feature = "test")]
233    pub fn rand() -> Self {
234        use rand::{
235            distributions::{Alphanumeric, DistString},
236            Rng,
237        };
238
239        const MIN: usize = 2;
240        const MAX: usize = 64;
241
242        let mut rng = rand::thread_rng();
243
244        let scope: ExprId = rng.gen_range(0..20);
245        let suffix: String = if rng.gen_bool(0.5) {
246            let len = rng.gen_range(MIN..MAX);
247            Alphanumeric.sample_string(&mut rng, len)
248        } else {
249            String::new()
250        };
251
252        WireExpr {
253            scope,
254            suffix: suffix.into(),
255            mapping: Mapping::DEFAULT,
256        }
257    }
258}