1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
//
// Copyright (c) 2021 - 2024 ZettaScale Technology
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
//
// Contributors:
//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
//

use crate::messages::{Data, DeserializerFn, LinkMessage};

use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Arc;

use anyhow::{anyhow, bail};
use flume::TryRecvError;
use uhlc::Timestamp;
use zenoh_flow_commons::{PortId, Result};

/// The `Inputs` structure contains all the inputs created for a [Sink](crate::prelude::Sink) or an
/// [Operator](crate::prelude::Operator).
///
/// Each input is indexed by its **port identifier**: the name that was indicated in the descriptor
/// of the node. These names are _case sensitive_ and should be an exact match to what was written
/// in the descriptor.
///
/// Zenoh-Flow provides two flavours of input: [InputRaw] and [`Input<T>`]. An [`Input<T>`]
/// conveniently exposes instances of `T` while an [InputRaw] exposes messages, allowing to
/// disregard the contained data.
///
/// The main way to interact with `Inputs` is through the `take` method.
///
/// # Example
///
/// ```no_run
/// # use zenoh_flow_nodes::prelude::*;
/// # let mut inputs = Inputs::default();
/// let input_raw = inputs.take("test raw")
///     .expect("No input name 'test raw' found")
///     .raw();
///
/// let input: Input<u64> = inputs.take("test typed")
///     .expect("No input name 'test typed' found")
///     .typed(
///         |bytes| serde_json::from_slice(bytes).map_err(|e| anyhow!(e))
///     );
/// ```
#[derive(Default)]
pub struct Inputs {
    pub(crate) hmap: HashMap<PortId, flume::Receiver<LinkMessage>>,
}

// Dereferencing on the internal `HashMap` allows users to call all the methods implemented on it: `keys()` for one.
impl Deref for Inputs {
    type Target = HashMap<PortId, flume::Receiver<LinkMessage>>;

    fn deref(&self) -> &Self::Target {
        &self.hmap
    }
}

impl Inputs {
    /// Insert the `flume::Receiver` in the [Inputs], creating the entry if needed in the internal `HashMap`.
    pub fn insert(&mut self, port_id: PortId, rx: flume::Receiver<LinkMessage>) {
        self.hmap.entry(port_id).or_insert(rx);
    }

    /// Returns an Input builder for the provided `port_id`, if an input was declared with this exact name in the
    /// descriptor of the node, otherwise returns `None`.
    ///
    /// # Usage
    ///
    /// This builder can either produce a, typed, [`Input<T>`](Input) or an [InputRaw]. The main difference between both
    /// is the type of data they expose: an [`Input<T>`](Input) automatically tries to downcast or deserialize the data
    /// contained in the message to expose `&T`, while an [InputRaw] simply exposes a [LinkMessage].
    ///
    /// ## Typed
    ///
    /// To obtain an [`Input<T>`](Input) one must call `typed` and provide a deserialiser function. In the example below
    /// we rely on the `serde_json` crate to do the deserialisation.
    ///
    /// ```no_run
    /// # use zenoh_flow_nodes::prelude::*;
    /// # let mut inputs = Inputs::default();
    /// let input: Input<u64> = inputs.take("test typed")
    ///     .expect("No input name 'test typed' found")
    ///     .typed(
    ///         |bytes| serde_json::from_slice(bytes).map_err(|e| anyhow!(e))
    ///     );
    /// ```
    ///
    /// ## Raw
    ///
    /// To obtain an [InputRaw] one must call `raw`.
    ///
    /// ```no_run
    /// # use zenoh_flow_nodes::prelude::*;
    /// # let mut inputs = Inputs::default();
    /// let input_raw = inputs.take("test raw")
    ///     .expect("No input name 'test raw' found")
    ///     .raw();
    /// ```
    pub fn take(&mut self, port_id: impl AsRef<str>) -> Option<InputBuilder> {
        self.hmap
            .remove(&port_id.as_ref().into())
            .map(|receiver| InputBuilder {
                port_id: port_id.as_ref().into(),
                receiver,
            })
    }
}

/// An `InputBuilder` is the intermediate structure to obtain either an [`Input<T>`](Input) or an [InputRaw].
///
/// The main difference between both is the type of data they expose: an [`Input<T>`] automatically
/// tries to downcast or deserialize the data contained in the message to expose `&T`, while an
/// [InputRaw] simply exposes a [LinkMessage].
///
/// # Planned evolution
///
/// Zenoh-Flow will allow tweaking the behaviour of the underlying channels. For now, the
/// `receivers` channels are _unbounded_ and do not implement a dropping policy, which could lead to
/// issues.
pub struct InputBuilder {
    pub(crate) port_id: PortId,
    pub(crate) receiver: flume::Receiver<LinkMessage>,
}

impl InputBuilder {
    /// Consume the `InputBuilder` to produce an [InputRaw].
    ///
    /// An [InputRaw] exposes the [LinkMessage] it receives, without trying to perform any
    /// conversion on the data.
    ///
    /// The [InputRaw] was designed for use cases such as load-balancing or rate-limiting. In these
    /// scenarios, the node does not need to access the underlying data.
    ///
    /// # `InputRaw` vs `Input<T>`
    ///
    /// If the node needs access to the data to perform computations, an [`Input<T>`] should be
    /// favoured as it performs the conversion automatically.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use zenoh_flow_nodes::prelude::*;
    /// # let mut inputs = Inputs::default();
    /// let input_raw = inputs.take("test raw")
    ///     .expect("No input name 'test raw' found")
    ///     .raw();
    /// ```
    pub fn raw(self) -> InputRaw {
        InputRaw {
            port_id: self.port_id,
            receiver: self.receiver,
        }
    }

    /// Consume the `InputBuilder` to produce an [`Input<T>`].
    ///
    /// An [`Input<T>`] tries to automatically convert the data contained in the [LinkMessage] in
    /// order to expose `&T`. Depending on if the data is received serialised or not, to perform
    /// this conversion either the `deserialiser` is called or a downcast is attempted.
    ///
    /// # `Input<T>` vs `InputRaw`
    ///
    /// If the node does need to access the data contained in the [LinkMessage], an [InputRaw]
    /// should be favoured as it does not try to perform the extra conversion steps.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use zenoh_flow_nodes::prelude::*;
    /// # let mut inputs = Inputs::default();
    /// let input: Input<u64> = inputs.take("test typed")
    ///     .expect("No input name 'test typed' found")
    ///     .typed(
    ///         |bytes| serde_json::from_slice(bytes).map_err(|e| anyhow!(e))
    ///     );
    /// ```
    pub fn typed<T>(
        self,
        deserializer: impl Fn(&[u8]) -> anyhow::Result<T> + Send + Sync + 'static,
    ) -> Input<T> {
        Input {
            input_raw: self.raw(),
            deserializer: Arc::new(deserializer),
        }
    }
}

/// An `InputRaw` receives "raw" [LinkMessage].
///
/// As opposed to a typed [`Input<T>`](Input), an `InputRaw` will not perform any operation on the data it receives.
/// This behaviour is useful when access to the underlying data is either irrelevant (e.g. for rate-limiting purposes)
/// or when Zenoh-Flow should not attempt to interpret the contained [Payload](crate::prelude::Payload) (e.g. for
/// bindings).
#[derive(Clone, Debug)]
pub struct InputRaw {
    pub(crate) port_id: PortId,
    pub(crate) receiver: flume::Receiver<LinkMessage>,
}

impl InputRaw {
    pub fn port_id(&self) -> &PortId {
        &self.port_id
    }

    /// Returns the number of channels associated with this Input.
    pub fn channels_count(&self) -> usize {
        self.receiver.len()
    }

    /// Returns the first queued [LinkMessage] or [None] if there is no queued message.
    ///
    /// # Asynchronous alternative: `recv`
    ///
    /// This method is a synchronous fail-fast alternative to it's asynchronous counterpart: `recv`.  Although
    /// synchronous, this method will not block the thread on which it is executed.
    ///
    /// # Errors
    ///
    /// An error is returned if the associated channel is disconnected.
    pub fn try_recv(&self) -> Result<Option<LinkMessage>> {
        match self.receiver.try_recv() {
            Ok(message) => Ok(Some(message)),
            Err(e) => match e {
                TryRecvError::Empty => Ok(None),
                TryRecvError::Disconnected => {
                    tracing::error!("Link disconnected: {}", self.port_id);
                    bail!("Disconnected");
                }
            },
        }
    }

    /// Returns the first [LinkMessage] that was received, *asynchronously*, on any of the channels associated with this
    /// Input.
    ///
    /// If several [LinkMessage] are received at the same time, one is *randomly* selected.
    ///
    /// # Errors
    ///
    /// An error is returned if a channel was disconnected.
    pub async fn recv(&self) -> Result<LinkMessage> {
        self.receiver.recv_async().await.map_err(|_| {
            tracing::error!("Link disconnected: {}", self.port_id);
            anyhow!("Disconnected")
        })
    }
}

/// A typed `Input` receiving [`Data<T>`](Data).
///
/// An `Input` will automatically try to downcast or deserialise the [Payload](crate::prelude::Payload) it receives,
/// exposing a [`Data<T>`](Data).
///
/// The type of conversion performed depends on whether the upstream node resides on the same Zenoh-Flow runtime
/// (downcast) or on another runtime (deserialisation).
///
/// # Performance
///
/// If the data is received serialised from the upstream node, an allocation is performed to host the deserialised `T`.
pub struct Input<T> {
    pub(crate) input_raw: InputRaw,
    pub(crate) deserializer: Arc<DeserializerFn<T>>,
}

// Dereferencing to the [InputRaw] allows to directly call methods on it with a typed [Input].
impl<T: Send + Sync + 'static> Deref for Input<T> {
    type Target = InputRaw;

    fn deref(&self) -> &Self::Target {
        &self.input_raw
    }
}

impl<T: Send + Sync + 'static> Input<T> {
    /// Returns the first [`Data<T>`](Data) that was received, *asynchronously*, on any of the channels
    /// associated with this Input.
    ///
    /// If several [`Data<T>`](Data) are received at the same time, one is *randomly* selected.
    ///
    /// This method interprets the data to the type associated with this [`Input<T>`](Input).
    ///
    /// # Performance
    ///
    /// As this method interprets the data received, additional operations are performed:
    /// - data received serialised is deserialised (an allocation is performed to store an instance of `T`),
    /// - data received "typed" are checked against the type associated to this [`Input<T>`](Input).
    ///
    /// # Synchronous alternative: `try_recv`
    ///
    /// This method is an asynchronous alternative to it's synchronous fail-fast counterpart: `try_recv`.
    ///
    /// # Errors
    ///
    /// Several errors can occur:
    /// - a channel was disconnected,
    /// - Zenoh-Flow failed at interpreting the received data as an instance of `T`.
    pub async fn recv(&self) -> Result<(Data<T>, Timestamp)> {
        let LinkMessage { payload, timestamp } = self.input_raw.recv().await?;
        Ok((
            Data::try_from_payload(payload, self.deserializer.clone())?,
            timestamp,
        ))
    }

    /// Returns the first [`Data<T>`](Data) that was received on any of the channels associated with this Input,
    /// or [None] if all the channels are empty.
    ///
    /// # Performance
    ///
    /// As this method interprets the data received, additional operations are performed:
    /// - data received serialised is deserialised (an allocation is performed to store an instance of `T`),
    /// - data received "typed" are checked against the type associated to this [`Input<T>`](Input).
    ///
    /// # Asynchronous alternative: `recv`
    ///
    /// This method is a synchronous fail-fast alternative to it's asynchronous counterpart: `recv`.  Although
    /// synchronous, this method will not block the thread on which it is executed.
    ///
    /// # Errors
    ///
    /// Several errors can occur:
    /// - a channel was disconnected,
    /// - Zenoh-Flow failed at interpreting the received data as an instance of `T`.
    ///
    /// Note that if some channels are disconnected, for each of such channel an error is logged.
    pub fn try_recv(&self) -> Result<Option<(Data<T>, Timestamp)>> {
        if let Some(LinkMessage { payload, timestamp }) = self.input_raw.try_recv()? {
            return Ok(Some((
                Data::try_from_payload(payload, self.deserializer.clone())?,
                timestamp,
            )));
        }

        Ok(None)
    }
}

#[cfg(test)]
#[path = "./tests/input-tests.rs"]
mod tests;