nu_plugin_protocol/
lib.rs

1//! Type definitions, including full `Serialize` and `Deserialize` implementations, for the protocol
2//! used for communication between the engine and a plugin.
3//!
4//! See the [plugin protocol reference](https://www.nushell.sh/contributor-book/plugin_protocol_reference.html)
5//! for more details on what exactly is being specified here.
6//!
7//! Plugins accept messages of [`PluginInput`] and send messages back of [`PluginOutput`]. This
8//! crate explicitly avoids implementing any functionality that depends on I/O, so the exact
9//! byte-level encoding scheme is not implemented here. See the protocol ref or `nu_plugin_core` for
10//! more details on how that works.
11
12mod evaluated_call;
13mod plugin_custom_value;
14mod protocol_info;
15
16#[cfg(test)]
17mod tests;
18
19/// Things that can help with protocol-related tests. Not part of the public API, just used by other
20/// nushell crates.
21#[doc(hidden)]
22pub mod test_util;
23
24use nu_protocol::{
25    ByteStreamType, Config, DeclId, LabeledError, PipelineData, PipelineMetadata, PluginMetadata,
26    PluginSignature, ShellError, SignalAction, Span, Spanned, Value, ast::Operator,
27    engine::Closure,
28};
29use nu_utils::SharedCow;
30use serde::{Deserialize, Serialize};
31use std::collections::HashMap;
32
33pub use evaluated_call::EvaluatedCall;
34pub use plugin_custom_value::PluginCustomValue;
35#[allow(unused_imports)] // may be unused by compile flags
36pub use protocol_info::{Feature, Protocol, ProtocolInfo};
37
38/// A sequential identifier for a stream
39pub type StreamId = usize;
40
41/// A sequential identifier for a [`PluginCall`]
42pub type PluginCallId = usize;
43
44/// A sequential identifier for an [`EngineCall`]
45pub type EngineCallId = usize;
46
47/// Information about a plugin command invocation. This includes an [`EvaluatedCall`] as a
48/// serializable representation of [`nu_protocol::ast::Call`]. The type parameter determines
49/// the input type.
50#[derive(Serialize, Deserialize, Debug, Clone)]
51pub struct CallInfo<D> {
52    /// The name of the command to be run
53    pub name: String,
54    /// Information about the invocation, including arguments
55    pub call: EvaluatedCall,
56    /// Pipeline input. This is usually [`nu_protocol::PipelineData`] or [`PipelineDataHeader`]
57    pub input: D,
58}
59
60impl<D> CallInfo<D> {
61    /// Convert the type of `input` from `D` to `T`.
62    pub fn map_data<T>(
63        self,
64        f: impl FnOnce(D) -> Result<T, ShellError>,
65    ) -> Result<CallInfo<T>, ShellError> {
66        Ok(CallInfo {
67            name: self.name,
68            call: self.call,
69            input: f(self.input)?,
70        })
71    }
72}
73
74/// The initial (and perhaps only) part of any [`nu_protocol::PipelineData`] sent over the wire.
75///
76/// This may contain a single value, or may initiate a stream with a [`StreamId`].
77#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
78pub enum PipelineDataHeader {
79    /// No input
80    Empty,
81    /// A single value
82    Value(Value, Option<PipelineMetadata>),
83    /// Initiate [`nu_protocol::PipelineData::ListStream`].
84    ///
85    /// Items are sent via [`StreamData`]
86    ListStream(ListStreamInfo),
87    /// Initiate [`nu_protocol::PipelineData::byte_stream`].
88    ///
89    /// Items are sent via [`StreamData`]
90    ByteStream(ByteStreamInfo),
91}
92
93impl PipelineDataHeader {
94    /// Return the stream ID, if any, embedded in the header
95    pub fn stream_id(&self) -> Option<StreamId> {
96        match self {
97            PipelineDataHeader::Empty => None,
98            PipelineDataHeader::Value(_, _) => None,
99            PipelineDataHeader::ListStream(info) => Some(info.id),
100            PipelineDataHeader::ByteStream(info) => Some(info.id),
101        }
102    }
103
104    pub fn value(value: Value) -> Self {
105        PipelineDataHeader::Value(value, None)
106    }
107
108    pub fn list_stream(info: ListStreamInfo) -> Self {
109        PipelineDataHeader::ListStream(info)
110    }
111
112    pub fn byte_stream(info: ByteStreamInfo) -> Self {
113        PipelineDataHeader::ByteStream(info)
114    }
115}
116
117/// Additional information about list (value) streams
118#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
119pub struct ListStreamInfo {
120    pub id: StreamId,
121    pub span: Span,
122    pub metadata: Option<PipelineMetadata>,
123}
124
125impl ListStreamInfo {
126    /// Create a new `ListStreamInfo` with a unique ID
127    pub fn new(id: StreamId, span: Span) -> Self {
128        ListStreamInfo {
129            id,
130            span,
131            metadata: None,
132        }
133    }
134}
135
136/// Additional information about byte streams
137#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
138pub struct ByteStreamInfo {
139    pub id: StreamId,
140    pub span: Span,
141    #[serde(rename = "type")]
142    pub type_: ByteStreamType,
143    pub metadata: Option<PipelineMetadata>,
144}
145
146impl ByteStreamInfo {
147    /// Create a new `ByteStreamInfo` with a unique ID
148    pub fn new(id: StreamId, span: Span, type_: ByteStreamType) -> Self {
149        ByteStreamInfo {
150            id,
151            span,
152            type_,
153            metadata: None,
154        }
155    }
156}
157
158/// Calls that a plugin can execute. The type parameter determines the input type.
159#[derive(Serialize, Deserialize, Debug, Clone)]
160pub enum PluginCall<D> {
161    Metadata,
162    Signature,
163    Run(CallInfo<D>),
164    CustomValueOp(Spanned<PluginCustomValue>, CustomValueOp),
165}
166
167impl<D> PluginCall<D> {
168    /// Convert the data type from `D` to `T`. The function will not be called if the variant does
169    /// not contain data.
170    pub fn map_data<T>(
171        self,
172        f: impl FnOnce(D) -> Result<T, ShellError>,
173    ) -> Result<PluginCall<T>, ShellError> {
174        Ok(match self {
175            PluginCall::Metadata => PluginCall::Metadata,
176            PluginCall::Signature => PluginCall::Signature,
177            PluginCall::Run(call) => PluginCall::Run(call.map_data(f)?),
178            PluginCall::CustomValueOp(custom_value, op) => {
179                PluginCall::CustomValueOp(custom_value, op)
180            }
181        })
182    }
183
184    /// The span associated with the call.
185    pub fn span(&self) -> Option<Span> {
186        match self {
187            PluginCall::Metadata => None,
188            PluginCall::Signature => None,
189            PluginCall::Run(CallInfo { call, .. }) => Some(call.head),
190            PluginCall::CustomValueOp(val, _) => Some(val.span),
191        }
192    }
193}
194
195/// Operations supported for custom values.
196#[derive(Serialize, Deserialize, Debug, Clone)]
197pub enum CustomValueOp {
198    /// [`to_base_value()`](nu_protocol::CustomValue::to_base_value)
199    ToBaseValue,
200    /// [`follow_path_int()`](nu_protocol::CustomValue::follow_path_int)
201    FollowPathInt(Spanned<usize>),
202    /// [`follow_path_string()`](nu_protocol::CustomValue::follow_path_string)
203    FollowPathString(Spanned<String>),
204    /// [`partial_cmp()`](nu_protocol::CustomValue::partial_cmp)
205    PartialCmp(Value),
206    /// [`operation()`](nu_protocol::CustomValue::operation)
207    Operation(Spanned<Operator>, Value),
208    /// Notify that the custom value has been dropped, if
209    /// [`notify_plugin_on_drop()`](nu_protocol::CustomValue::notify_plugin_on_drop) is true
210    Dropped,
211}
212
213impl CustomValueOp {
214    /// Get the name of the op, for error messages.
215    pub fn name(&self) -> &'static str {
216        match self {
217            CustomValueOp::ToBaseValue => "to_base_value",
218            CustomValueOp::FollowPathInt(_) => "follow_path_int",
219            CustomValueOp::FollowPathString(_) => "follow_path_string",
220            CustomValueOp::PartialCmp(_) => "partial_cmp",
221            CustomValueOp::Operation(_, _) => "operation",
222            CustomValueOp::Dropped => "dropped",
223        }
224    }
225}
226
227/// Any data sent to the plugin
228#[derive(Serialize, Deserialize, Debug, Clone)]
229pub enum PluginInput {
230    /// This must be the first message. Indicates supported protocol
231    Hello(ProtocolInfo),
232    /// Execute a [`PluginCall`], such as `Run` or `Signature`. The ID should not have been used
233    /// before.
234    Call(PluginCallId, PluginCall<PipelineDataHeader>),
235    /// Don't expect any more plugin calls. Exit after all currently executing plugin calls are
236    /// finished.
237    Goodbye,
238    /// Response to an [`EngineCall`]. The ID should be the same one sent with the engine call this
239    /// is responding to
240    EngineCallResponse(EngineCallId, EngineCallResponse<PipelineDataHeader>),
241    /// See [`StreamMessage::Data`].
242    Data(StreamId, StreamData),
243    /// See [`StreamMessage::End`].
244    End(StreamId),
245    /// See [`StreamMessage::Drop`].
246    Drop(StreamId),
247    /// See [`StreamMessage::Ack`].
248    Ack(StreamId),
249    /// Relay signals to the plugin
250    Signal(SignalAction),
251}
252
253impl TryFrom<PluginInput> for StreamMessage {
254    type Error = PluginInput;
255
256    fn try_from(msg: PluginInput) -> Result<StreamMessage, PluginInput> {
257        match msg {
258            PluginInput::Data(id, data) => Ok(StreamMessage::Data(id, data)),
259            PluginInput::End(id) => Ok(StreamMessage::End(id)),
260            PluginInput::Drop(id) => Ok(StreamMessage::Drop(id)),
261            PluginInput::Ack(id) => Ok(StreamMessage::Ack(id)),
262            _ => Err(msg),
263        }
264    }
265}
266
267impl From<StreamMessage> for PluginInput {
268    fn from(stream_msg: StreamMessage) -> PluginInput {
269        match stream_msg {
270            StreamMessage::Data(id, data) => PluginInput::Data(id, data),
271            StreamMessage::End(id) => PluginInput::End(id),
272            StreamMessage::Drop(id) => PluginInput::Drop(id),
273            StreamMessage::Ack(id) => PluginInput::Ack(id),
274        }
275    }
276}
277
278/// A single item of stream data for a stream.
279#[derive(Serialize, Deserialize, Debug, Clone)]
280pub enum StreamData {
281    List(Value),
282    Raw(Result<Vec<u8>, LabeledError>),
283}
284
285impl From<Value> for StreamData {
286    fn from(value: Value) -> Self {
287        StreamData::List(value)
288    }
289}
290
291impl From<Result<Vec<u8>, LabeledError>> for StreamData {
292    fn from(value: Result<Vec<u8>, LabeledError>) -> Self {
293        StreamData::Raw(value)
294    }
295}
296
297impl From<Result<Vec<u8>, ShellError>> for StreamData {
298    fn from(value: Result<Vec<u8>, ShellError>) -> Self {
299        value.map_err(LabeledError::from).into()
300    }
301}
302
303impl TryFrom<StreamData> for Value {
304    type Error = ShellError;
305
306    fn try_from(data: StreamData) -> Result<Value, ShellError> {
307        match data {
308            StreamData::List(value) => Ok(value),
309            StreamData::Raw(_) => Err(ShellError::PluginFailedToDecode {
310                msg: "expected list stream data, found raw data".into(),
311            }),
312        }
313    }
314}
315
316impl TryFrom<StreamData> for Result<Vec<u8>, LabeledError> {
317    type Error = ShellError;
318
319    fn try_from(data: StreamData) -> Result<Result<Vec<u8>, LabeledError>, ShellError> {
320        match data {
321            StreamData::Raw(value) => Ok(value),
322            StreamData::List(_) => Err(ShellError::PluginFailedToDecode {
323                msg: "expected raw stream data, found list data".into(),
324            }),
325        }
326    }
327}
328
329impl TryFrom<StreamData> for Result<Vec<u8>, ShellError> {
330    type Error = ShellError;
331
332    fn try_from(value: StreamData) -> Result<Result<Vec<u8>, ShellError>, ShellError> {
333        Result::<Vec<u8>, LabeledError>::try_from(value).map(|res| res.map_err(ShellError::from))
334    }
335}
336
337/// A stream control or data message.
338#[derive(Serialize, Deserialize, Debug, Clone)]
339pub enum StreamMessage {
340    /// Append data to the stream. Sent by the stream producer.
341    Data(StreamId, StreamData),
342    /// End of stream. Sent by the stream producer.
343    End(StreamId),
344    /// Notify that the read end of the stream has closed, and further messages should not be
345    /// sent. Sent by the stream consumer.
346    Drop(StreamId),
347    /// Acknowledge that a message has been consumed. This is used to implement flow control by
348    /// the stream producer. Sent by the stream consumer.
349    Ack(StreamId),
350}
351
352/// Response to a [`PluginCall`]. The type parameter determines the output type for pipeline data.
353#[derive(Serialize, Deserialize, Debug, Clone)]
354pub enum PluginCallResponse<D> {
355    Error(LabeledError),
356    Metadata(PluginMetadata),
357    Signature(Vec<PluginSignature>),
358    Ordering(Option<Ordering>),
359    PipelineData(D),
360}
361
362impl<D> PluginCallResponse<D> {
363    /// Convert the data type from `D` to `T`. The function will not be called if the variant does
364    /// not contain data.
365    pub fn map_data<T>(
366        self,
367        f: impl FnOnce(D) -> Result<T, ShellError>,
368    ) -> Result<PluginCallResponse<T>, ShellError> {
369        Ok(match self {
370            PluginCallResponse::Error(err) => PluginCallResponse::Error(err),
371            PluginCallResponse::Metadata(meta) => PluginCallResponse::Metadata(meta),
372            PluginCallResponse::Signature(sigs) => PluginCallResponse::Signature(sigs),
373            PluginCallResponse::Ordering(ordering) => PluginCallResponse::Ordering(ordering),
374            PluginCallResponse::PipelineData(input) => PluginCallResponse::PipelineData(f(input)?),
375        })
376    }
377}
378
379impl PluginCallResponse<PipelineDataHeader> {
380    /// Construct a plugin call response with a single value
381    pub fn value(value: Value) -> PluginCallResponse<PipelineDataHeader> {
382        if value.is_nothing() {
383            PluginCallResponse::PipelineData(PipelineDataHeader::Empty)
384        } else {
385            PluginCallResponse::PipelineData(PipelineDataHeader::value(value))
386        }
387    }
388}
389
390impl PluginCallResponse<PipelineData> {
391    /// Does this response have a stream?
392    pub fn has_stream(&self) -> bool {
393        match self {
394            PluginCallResponse::PipelineData(data) => match data {
395                PipelineData::Empty => false,
396                PipelineData::Value(..) => false,
397                PipelineData::ListStream(..) => true,
398                PipelineData::ByteStream(..) => true,
399            },
400            _ => false,
401        }
402    }
403}
404
405/// Options that can be changed to affect how the engine treats the plugin
406#[derive(Serialize, Deserialize, Debug, Clone)]
407pub enum PluginOption {
408    /// Send `GcDisabled(true)` to stop the plugin from being automatically garbage collected, or
409    /// `GcDisabled(false)` to enable it again.
410    ///
411    /// See `EngineInterface::set_gc_disabled()` in `nu-plugin` for more information.
412    GcDisabled(bool),
413}
414
415/// This is just a serializable version of [`std::cmp::Ordering`], and can be converted 1:1
416#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
417pub enum Ordering {
418    Less,
419    Equal,
420    Greater,
421}
422
423impl From<std::cmp::Ordering> for Ordering {
424    fn from(value: std::cmp::Ordering) -> Self {
425        match value {
426            std::cmp::Ordering::Less => Ordering::Less,
427            std::cmp::Ordering::Equal => Ordering::Equal,
428            std::cmp::Ordering::Greater => Ordering::Greater,
429        }
430    }
431}
432
433impl From<Ordering> for std::cmp::Ordering {
434    fn from(value: Ordering) -> Self {
435        match value {
436            Ordering::Less => std::cmp::Ordering::Less,
437            Ordering::Equal => std::cmp::Ordering::Equal,
438            Ordering::Greater => std::cmp::Ordering::Greater,
439        }
440    }
441}
442
443/// Information received from the plugin
444#[derive(Serialize, Deserialize, Debug, Clone)]
445pub enum PluginOutput {
446    /// This must be the first message. Indicates supported protocol
447    Hello(ProtocolInfo),
448    /// Set option. No response expected
449    Option(PluginOption),
450    /// A response to a [`PluginCall`]. The ID should be the same sent with the plugin call this
451    /// is a response to
452    CallResponse(PluginCallId, PluginCallResponse<PipelineDataHeader>),
453    /// Execute an [`EngineCall`]. Engine calls must be executed within the `context` of a plugin
454    /// call, and the `id` should not have been used before
455    EngineCall {
456        /// The plugin call (by ID) to execute in the context of
457        context: PluginCallId,
458        /// A new identifier for this engine call. The response will reference this ID
459        id: EngineCallId,
460        call: EngineCall<PipelineDataHeader>,
461    },
462    /// See [`StreamMessage::Data`].
463    Data(StreamId, StreamData),
464    /// See [`StreamMessage::End`].
465    End(StreamId),
466    /// See [`StreamMessage::Drop`].
467    Drop(StreamId),
468    /// See [`StreamMessage::Ack`].
469    Ack(StreamId),
470}
471
472impl TryFrom<PluginOutput> for StreamMessage {
473    type Error = PluginOutput;
474
475    fn try_from(msg: PluginOutput) -> Result<StreamMessage, PluginOutput> {
476        match msg {
477            PluginOutput::Data(id, data) => Ok(StreamMessage::Data(id, data)),
478            PluginOutput::End(id) => Ok(StreamMessage::End(id)),
479            PluginOutput::Drop(id) => Ok(StreamMessage::Drop(id)),
480            PluginOutput::Ack(id) => Ok(StreamMessage::Ack(id)),
481            _ => Err(msg),
482        }
483    }
484}
485
486impl From<StreamMessage> for PluginOutput {
487    fn from(stream_msg: StreamMessage) -> PluginOutput {
488        match stream_msg {
489            StreamMessage::Data(id, data) => PluginOutput::Data(id, data),
490            StreamMessage::End(id) => PluginOutput::End(id),
491            StreamMessage::Drop(id) => PluginOutput::Drop(id),
492            StreamMessage::Ack(id) => PluginOutput::Ack(id),
493        }
494    }
495}
496
497/// A remote call back to the engine during the plugin's execution.
498///
499/// The type parameter determines the input type, for calls that take pipeline data.
500#[derive(Serialize, Deserialize, Debug, Clone)]
501pub enum EngineCall<D> {
502    /// Get the full engine configuration
503    GetConfig,
504    /// Get the plugin-specific configuration (`$env.config.plugins.NAME`)
505    GetPluginConfig,
506    /// Get an environment variable
507    GetEnvVar(String),
508    /// Get all environment variables
509    GetEnvVars,
510    /// Get current working directory
511    GetCurrentDir,
512    /// Set an environment variable in the caller's scope
513    AddEnvVar(String, Value),
514    /// Get help for the current command
515    GetHelp,
516    /// Move the plugin into the foreground for terminal interaction
517    EnterForeground,
518    /// Move the plugin out of the foreground once terminal interaction has finished
519    LeaveForeground,
520    /// Get the contents of a span. Response is a binary which may not parse to UTF-8
521    GetSpanContents(Span),
522    /// Evaluate a closure with stream input/output
523    EvalClosure {
524        /// The closure to call.
525        ///
526        /// This may come from a [`Value::Closure`] passed in as an argument to the plugin.
527        closure: Spanned<Closure>,
528        /// Positional arguments to add to the closure call
529        positional: Vec<Value>,
530        /// Input to the closure
531        input: D,
532        /// Whether to redirect stdout from external commands
533        redirect_stdout: bool,
534        /// Whether to redirect stderr from external commands
535        redirect_stderr: bool,
536    },
537    /// Find a declaration by name
538    FindDecl(String),
539    /// Call a declaration with args
540    CallDecl {
541        /// The id of the declaration to be called (can be found with `FindDecl`)
542        decl_id: DeclId,
543        /// Information about the call (head span, arguments, etc.)
544        call: EvaluatedCall,
545        /// Pipeline input to the call
546        input: D,
547        /// Whether to redirect stdout from external commands
548        redirect_stdout: bool,
549        /// Whether to redirect stderr from external commands
550        redirect_stderr: bool,
551    },
552}
553
554impl<D> EngineCall<D> {
555    /// Get the name of the engine call so it can be embedded in things like error messages
556    pub fn name(&self) -> &'static str {
557        match self {
558            EngineCall::GetConfig => "GetConfig",
559            EngineCall::GetPluginConfig => "GetPluginConfig",
560            EngineCall::GetEnvVar(_) => "GetEnv",
561            EngineCall::GetEnvVars => "GetEnvs",
562            EngineCall::GetCurrentDir => "GetCurrentDir",
563            EngineCall::AddEnvVar(..) => "AddEnvVar",
564            EngineCall::GetHelp => "GetHelp",
565            EngineCall::EnterForeground => "EnterForeground",
566            EngineCall::LeaveForeground => "LeaveForeground",
567            EngineCall::GetSpanContents(_) => "GetSpanContents",
568            EngineCall::EvalClosure { .. } => "EvalClosure",
569            EngineCall::FindDecl(_) => "FindDecl",
570            EngineCall::CallDecl { .. } => "CallDecl",
571        }
572    }
573
574    /// Convert the data type from `D` to `T`. The function will not be called if the variant does
575    /// not contain data.
576    pub fn map_data<T>(
577        self,
578        f: impl FnOnce(D) -> Result<T, ShellError>,
579    ) -> Result<EngineCall<T>, ShellError> {
580        Ok(match self {
581            EngineCall::GetConfig => EngineCall::GetConfig,
582            EngineCall::GetPluginConfig => EngineCall::GetPluginConfig,
583            EngineCall::GetEnvVar(name) => EngineCall::GetEnvVar(name),
584            EngineCall::GetEnvVars => EngineCall::GetEnvVars,
585            EngineCall::GetCurrentDir => EngineCall::GetCurrentDir,
586            EngineCall::AddEnvVar(name, value) => EngineCall::AddEnvVar(name, value),
587            EngineCall::GetHelp => EngineCall::GetHelp,
588            EngineCall::EnterForeground => EngineCall::EnterForeground,
589            EngineCall::LeaveForeground => EngineCall::LeaveForeground,
590            EngineCall::GetSpanContents(span) => EngineCall::GetSpanContents(span),
591            EngineCall::EvalClosure {
592                closure,
593                positional,
594                input,
595                redirect_stdout,
596                redirect_stderr,
597            } => EngineCall::EvalClosure {
598                closure,
599                positional,
600                input: f(input)?,
601                redirect_stdout,
602                redirect_stderr,
603            },
604            EngineCall::FindDecl(name) => EngineCall::FindDecl(name),
605            EngineCall::CallDecl {
606                decl_id,
607                call,
608                input,
609                redirect_stdout,
610                redirect_stderr,
611            } => EngineCall::CallDecl {
612                decl_id,
613                call,
614                input: f(input)?,
615                redirect_stdout,
616                redirect_stderr,
617            },
618        })
619    }
620}
621
622/// The response to an [`EngineCall`]. The type parameter determines the output type for pipeline
623/// data.
624#[derive(Serialize, Deserialize, Debug, Clone)]
625pub enum EngineCallResponse<D> {
626    Error(ShellError),
627    PipelineData(D),
628    Config(SharedCow<Config>),
629    ValueMap(HashMap<String, Value>),
630    Identifier(DeclId),
631}
632
633impl<D> EngineCallResponse<D> {
634    /// Convert the data type from `D` to `T`. The function will not be called if the variant does
635    /// not contain data.
636    pub fn map_data<T>(
637        self,
638        f: impl FnOnce(D) -> Result<T, ShellError>,
639    ) -> Result<EngineCallResponse<T>, ShellError> {
640        Ok(match self {
641            EngineCallResponse::Error(err) => EngineCallResponse::Error(err),
642            EngineCallResponse::PipelineData(data) => EngineCallResponse::PipelineData(f(data)?),
643            EngineCallResponse::Config(config) => EngineCallResponse::Config(config),
644            EngineCallResponse::ValueMap(map) => EngineCallResponse::ValueMap(map),
645            EngineCallResponse::Identifier(id) => EngineCallResponse::Identifier(id),
646        })
647    }
648}
649
650impl EngineCallResponse<PipelineData> {
651    /// Build an [`EngineCallResponse::PipelineData`] from a [`Value`]
652    pub fn value(value: Value) -> EngineCallResponse<PipelineData> {
653        EngineCallResponse::PipelineData(PipelineData::value(value, None))
654    }
655
656    /// An [`EngineCallResponse::PipelineData`] with [`PipelineData::empty()`]
657    pub const fn empty() -> EngineCallResponse<PipelineData> {
658        EngineCallResponse::PipelineData(PipelineData::empty())
659    }
660}