Skip to main content

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