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}