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