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