use std::time::Instant;
use nu_engine::CallExt;
use nu_protocol::{
engine::{Call, Command, EngineState, Stack},
record,
shell_error::generic::GenericError,
IntoValue, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
use zenoh::{config::WhatAmIMatcher, scouting::Hello, Config, Wait};
use crate::{
call_ext2::CallExt2, conv, interruptible_channel::InterruptibleChannel,
signature_ext::SignatureExt, State,
};
#[derive(Clone)]
pub(crate) struct Scout {
_state: State,
}
impl Scout {
pub(crate) fn new(state: State) -> Self {
Self { _state: state }
}
}
impl Command for Scout {
fn name(&self) -> &str {
"zenoh scout"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.named("timeout", SyntaxShape::Duration, "Scouting timeout", None)
.zenoh_category()
.config()
.input_output_type(Type::Nothing, Type::list(Type::record()))
}
fn description(&self) -> &str {
"Scout the Zenoh network"
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let span = call.head;
const SCOUT_CHANNEL_SIZE: usize = 256;
let (tx, rx) = flume::bounded(SCOUT_CHANNEL_SIZE);
let config_record = call.opt::<Value>(engine_state, stack, 0)?;
let config = match config_record {
Some(val @ Value::Record { .. }) => {
let json_value = conv::value_to_json_value(engine_state, &val, call.head, false)?;
zenoh::Config::from_json5(&json_value.to_string()).map_err(|e| {
nu_protocol::LabeledError::new("Failed to parse config record")
.with_label(format!("Could not parse config record: {e}"), call.head)
})?
}
Some(_) => {
return Err(ShellError::Generic(
GenericError::new("Invalid config type", "Config must be a record", call.head)
.with_help("Provide a record with Zenoh configuration options"),
));
}
None => Config::default(),
};
let scout = zenoh::scout(WhatAmIMatcher::empty().client().peer().router(), config)
.callback(move |scout| {
let _ = tx.send(scout);
})
.wait()
.map_err(|e| {
nu_protocol::LabeledError::new("Scout operation failed")
.with_label(format!("Zenoh scout failed: {e}"), call.head)
})?;
fn hello_to_value(hello: &Hello, span: Span) -> Value {
record!(
"zid" => hello.zid().to_string().into_value(span),
"whatami" => hello.whatami().to_string().into_value(span),
"locators" => hello.locators().iter().map(|l| l.to_string().into_value(span)).collect::<Vec<_>>().into_value(span),
)
.into_value(span)
}
if let Some(timeout) = call.timeout(engine_state, stack)? {
let deadline = Instant::now() + timeout;
let mut values = Vec::new();
while let Ok(hello) = rx.recv_deadline(deadline) {
values.push(hello_to_value(&hello, span))
}
Ok(PipelineData::Value(Value::list(values, span), None))
} else {
let iter = InterruptibleChannel::with_data(rx, engine_state.signals().clone(), scout)
.map(move |hello| hello_to_value(&hello, span));
Ok(ListStream::new(iter, span, engine_state.signals().clone()).into())
}
}
}