zenoh/api/
query.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
15use std::{collections::HashMap, error::Error, fmt::Display};
16
17#[cfg(feature = "unstable")]
18use serde::Deserialize;
19#[cfg(feature = "unstable")]
20use zenoh_config::wrappers::EntityGlobalId;
21use zenoh_keyexpr::OwnedKeyExpr;
22#[cfg(feature = "unstable")]
23use zenoh_protocol::core::EntityGlobalIdProto;
24use zenoh_protocol::core::Parameters;
25/// The [`Queryable`](crate::query::Queryable)s to which a query from
26/// a [`Session::get`](crate::Session::get) or a [`Querier::get`](crate::query::Querier::get)
27/// is delivered.
28///
29/// * [`QueryTarget::All`] makes the query be delivered to all the matching queryables.
30/// * [`QueryTarget::AllComplete`] makes the query be delivered to all the matching queryables
31///   which are marked as "complete" with
32///   [`QueryableBuilder::complete`](crate::query::QueryableBuilder::complete).
33/// * [`QueryTarget::BestMatching`] (default) makes the data to be requested from the
34///   queryable(s) selected by zenoh to get the fastest and most complete reply.
35///
36/// It is set by [`SessionGetBuilder::target`](crate::session::SessionGetBuilder::target)
37/// or [`QuerierBuilder::target`](crate::query::QuerierBuilder::target) methods.
38///
39pub use zenoh_protocol::network::request::ext::QueryTarget;
40#[doc(inline)]
41pub use zenoh_protocol::zenoh::query::ConsolidationMode;
42
43use crate::api::{
44    bytes::ZBytes,
45    encoding::Encoding,
46    handlers::{Callback, CallbackParameter},
47    key_expr::KeyExpr,
48    sample::Sample,
49    selector::Selector,
50};
51
52/// The reply consolidation strategy to apply to replies to a [`get`](crate::Session::get).
53///
54/// By default, the consolidation strategy is [`QueryConsolidation::AUTO`], which lets the implementation
55/// choose the best strategy depending on the query parameters and the number of responders.
56/// Other strategies can be selected by using
57/// a specific [`ConsolidationMode`] as a parameter of the
58/// [`QuerierBuilder::consolidation`](crate::query::QuerierBuilder::consolidation)
59/// or [`SessionGetBuilder::consolidation`](crate::session::SessionGetBuilder::consolidation)
60/// methods.
61///
62/// See the documentation of [`ConsolidationMode`] for more details about each strategy.
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub struct QueryConsolidation {
65    pub(crate) mode: ConsolidationMode,
66}
67
68impl QueryConsolidation {
69    pub const DEFAULT: Self = Self::AUTO;
70    /// Automatic query consolidation strategy selection.
71    pub const AUTO: Self = Self {
72        mode: ConsolidationMode::Auto,
73    };
74
75    pub(crate) const fn from_mode(mode: ConsolidationMode) -> Self {
76        Self { mode }
77    }
78
79    /// Returns the requested [`ConsolidationMode`].
80    pub fn mode(&self) -> ConsolidationMode {
81        self.mode
82    }
83}
84
85impl From<ConsolidationMode> for QueryConsolidation {
86    fn from(mode: ConsolidationMode) -> Self {
87        Self::from_mode(mode)
88    }
89}
90
91impl Default for QueryConsolidation {
92    fn default() -> Self {
93        Self::DEFAULT
94    }
95}
96
97/// An error reply variant returned by [`Querier::get`](crate::query::Querier::get)
98/// or [`Session::get`](crate::Session::get) in [`Reply`]
99///
100/// The `ReplyError` contains the payload with the error information (message or some structured data)
101/// and the encoding of this payload.
102#[derive(Clone, Debug, PartialEq, Eq, Default)]
103pub struct ReplyError {
104    pub(crate) payload: ZBytes,
105    pub(crate) encoding: Encoding,
106}
107
108impl ReplyError {
109    pub(crate) fn new(payload: impl Into<ZBytes>, encoding: Encoding) -> Self {
110        Self {
111            payload: payload.into(),
112            encoding,
113        }
114    }
115
116    /// Gets the payload of this ReplyError.
117    #[inline]
118    pub fn payload(&self) -> &ZBytes {
119        &self.payload
120    }
121
122    /// Gets the mutable payload of this ReplyError.
123    #[inline]
124    pub fn payload_mut(&mut self) -> &mut ZBytes {
125        &mut self.payload
126    }
127
128    /// Gets the encoding of this ReplyError.
129    #[inline]
130    pub fn encoding(&self) -> &Encoding {
131        &self.encoding
132    }
133
134    /// Constructs an uninitialized empty ReplyError.
135    #[zenoh_macros::internal]
136    pub fn empty() -> Self {
137        ReplyError {
138            payload: ZBytes::new(),
139            encoding: Encoding::default(),
140        }
141    }
142}
143
144impl Display for ReplyError {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        write!(
147            f,
148            "query returned an error with a {}-byte payload and encoding {}",
149            self.payload.len(),
150            self.encoding
151        )
152    }
153}
154
155impl Error for ReplyError {}
156
157/// An answer received from a [`Queryable`](crate::query::Queryable).
158///
159/// The `Reply` contains the result of the request to a [`Queryable`](crate::query::Queryable) by
160/// [`Session::get`](crate::Session::get) or [`Querier::get`](crate::query::Querier::get).
161///
162/// It may be either a successful result with a [`Sample`](crate::sample::Sample) or an error with a [`ReplyError`].
163/// The method [`Reply::result`] is provided to access the result.
164#[non_exhaustive]
165#[derive(Clone, Debug)]
166pub struct Reply {
167    pub(crate) result: Result<Sample, ReplyError>,
168    #[cfg(feature = "unstable")]
169    pub(crate) replier_id: Option<EntityGlobalIdProto>,
170}
171
172impl Reply {
173    /// Gets a borrowed result of this `Reply`. Use [`Reply::into_result`] to take ownership of the result.
174    pub fn result(&self) -> Result<&Sample, &ReplyError> {
175        self.result.as_ref()
176    }
177
178    /// Gets a mutable borrowed result of this `Reply`. Use [`Reply::into_result`] to take ownership of the result.
179    pub fn result_mut(&mut self) -> Result<&mut Sample, &mut ReplyError> {
180        self.result.as_mut()
181    }
182
183    /// Converts this `Reply` into its result. Use [`Reply::result`] if you don't want to take ownership.
184    pub fn into_result(self) -> Result<Sample, ReplyError> {
185        self.result
186    }
187
188    #[zenoh_macros::unstable]
189    /// Gets the ID of the zenoh instance that answered this reply.
190    pub fn replier_id(&self) -> Option<EntityGlobalId> {
191        self.replier_id.map(Into::into)
192    }
193
194    /// Constructs an uninitialized empty Reply.
195    #[zenoh_macros::internal]
196    pub fn empty() -> Self {
197        Reply {
198            result: Ok(Sample::empty()),
199            #[cfg(feature = "unstable")]
200            replier_id: None,
201        }
202    }
203}
204
205impl CallbackParameter for Reply {
206    type Message<'a> = Self;
207
208    fn from_message(msg: Self::Message<'_>) -> Self {
209        msg
210    }
211}
212
213impl From<Reply> for Result<Sample, ReplyError> {
214    fn from(value: Reply) -> Self {
215        value.into_result()
216    }
217}
218
219pub(crate) struct LivelinessQueryState {
220    pub(crate) callback: Callback<Reply>,
221}
222
223pub(crate) struct QueryState {
224    pub(crate) nb_final: usize,
225    pub(crate) key_expr: KeyExpr<'static>,
226    pub(crate) parameters: Parameters<'static>,
227    pub(crate) reception_mode: ConsolidationMode,
228    pub(crate) replies: Option<HashMap<OwnedKeyExpr, Reply>>,
229    pub(crate) callback: Callback<Reply>,
230}
231
232impl QueryState {
233    pub(crate) fn selector(&self) -> Selector<'_> {
234        Selector::borrowed(&self.key_expr, &self.parameters)
235    }
236}
237/// The kinds of accepted query replies.
238///
239/// The [`Queryable`](crate::query::Queryable) may serve glob-like key expressions.
240/// E.g., the queryable may be declared with the key expression `foo/*`.
241/// At the same time, it may send replies with more specific key expressions, e.g., `foo/bar` or `foo/baz`.
242/// This may cause a situation when the queryable receives a query with the key expression `foo/bar`
243/// and replies to it with the key expression `foo/baz`.
244///
245/// By default, this behavior is not allowed. Calling [`Query::reply`](crate::query::Query::reply) on
246/// a query for `foo/bar` with key expression `foo/baz` will result in an error on the sending side.
247///
248/// But if the query is sent with the [`ReplyKeyExpr::Any`] parameter in [`accept_replies`](crate::session::SessionGetBuilder::accept_replies) (for
249/// [`Session::get`](crate::session::Session::get) or
250/// [`accept_replies`](crate::query::QuerierBuilder::accept_replies) for
251/// [`Querier::get`](crate::query::Querier::get))
252/// then the reply with a disjoint key expression will be accepted for this query.
253///
254/// The [`Queryable`](crate::query::Queryable) may check this parameter with
255/// [`Query::accepts_replies`](crate::query::Query::accepts_replies).
256#[zenoh_macros::unstable]
257#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Deserialize)]
258pub enum ReplyKeyExpr {
259    /// Accept replies whose key expressions may not match the query key expression.
260    Any,
261    #[default]
262    /// Accept replies whose key expressions match the query key expression.
263    MatchingQuery,
264}