nu_plugin_engine/plugin_custom_value_with_source/
mod.rs

1use std::{cmp::Ordering, sync::Arc};
2
3use nu_plugin_core::util::with_custom_values_in;
4use nu_plugin_protocol::PluginCustomValue;
5use nu_protocol::{CustomValue, IntoSpanned, ShellError, Span, Spanned, Value, ast::Operator};
6use serde::Serialize;
7
8use crate::{PluginInterface, PluginSource};
9
10#[cfg(test)]
11mod tests;
12
13/// Wraps a [`PluginCustomValue`] together with its [`PluginSource`], so that the [`CustomValue`]
14/// methods can be implemented by calling the plugin, and to ensure that any custom values sent to a
15/// plugin came from it originally.
16#[derive(Debug, Clone)]
17pub struct PluginCustomValueWithSource {
18    inner: PluginCustomValue,
19
20    /// Which plugin the custom value came from. This is not sent over the serialization boundary.
21    source: Arc<PluginSource>,
22}
23
24impl PluginCustomValueWithSource {
25    /// Wrap a [`PluginCustomValue`] together with its source.
26    pub fn new(inner: PluginCustomValue, source: Arc<PluginSource>) -> PluginCustomValueWithSource {
27        PluginCustomValueWithSource { inner, source }
28    }
29
30    /// Create a [`Value`] containing this custom value.
31    pub fn into_value(self, span: Span) -> Value {
32        Value::custom(Box::new(self), span)
33    }
34
35    /// Which plugin the custom value came from. This provides a direct reference to be able to get
36    /// a plugin interface in order to make a call, when needed.
37    pub fn source(&self) -> &Arc<PluginSource> {
38        &self.source
39    }
40
41    /// Unwrap the [`PluginCustomValueWithSource`], discarding the source.
42    pub fn without_source(self) -> PluginCustomValue {
43        // Because of the `Drop` implementation, we can't destructure this.
44        self.inner.clone()
45    }
46
47    /// Helper to get the plugin to implement an op
48    fn get_plugin(&self, span: Option<Span>, for_op: &str) -> Result<PluginInterface, ShellError> {
49        let wrap_err = |err: ShellError| ShellError::GenericError {
50            error: format!(
51                "Unable to spawn plugin `{}` to {for_op}",
52                self.source.name()
53            ),
54            msg: err.to_string(),
55            span,
56            help: None,
57            inner: vec![err],
58        };
59
60        self.source
61            .clone()
62            .persistent(span)
63            .and_then(|p| p.get_plugin(None))
64            .map_err(wrap_err)
65    }
66
67    /// Add a [`PluginSource`] to the given [`CustomValue`] if it is a [`PluginCustomValue`].
68    pub fn add_source(value: &mut Box<dyn CustomValue>, source: &Arc<PluginSource>) {
69        if let Some(custom_value) = value.as_any().downcast_ref::<PluginCustomValue>() {
70            *value = Box::new(custom_value.clone().with_source(source.clone()));
71        }
72    }
73
74    /// Add a [`PluginSource`] to all [`PluginCustomValue`]s within the value, recursively.
75    pub fn add_source_in(value: &mut Value, source: &Arc<PluginSource>) -> Result<(), ShellError> {
76        with_custom_values_in(value, |custom_value| {
77            Self::add_source(custom_value.item, source);
78            Ok::<_, ShellError>(())
79        })
80    }
81
82    /// Remove a [`PluginSource`] from the given [`CustomValue`] if it is a
83    /// [`PluginCustomValueWithSource`]. This will turn it back into a [`PluginCustomValue`].
84    pub fn remove_source(value: &mut Box<dyn CustomValue>) {
85        if let Some(custom_value) = value.as_any().downcast_ref::<PluginCustomValueWithSource>() {
86            *value = Box::new(custom_value.clone().without_source());
87        }
88    }
89
90    /// Remove the [`PluginSource`] from all [`PluginCustomValue`]s within the value, recursively.
91    pub fn remove_source_in(value: &mut Value) -> Result<(), ShellError> {
92        with_custom_values_in(value, |custom_value| {
93            Self::remove_source(custom_value.item);
94            Ok::<_, ShellError>(())
95        })
96    }
97
98    /// Check that `self` came from the given `source`, and return an `error` if not.
99    pub fn verify_source(&self, span: Span, source: &PluginSource) -> Result<(), ShellError> {
100        if self.source.is_compatible(source) {
101            Ok(())
102        } else {
103            Err(ShellError::CustomValueIncorrectForPlugin {
104                name: self.name().to_owned(),
105                span,
106                dest_plugin: source.name().to_owned(),
107                src_plugin: Some(self.source.name().to_owned()),
108            })
109        }
110    }
111
112    /// Check that a [`CustomValue`] is a [`PluginCustomValueWithSource`] that came from the given
113    /// `source`, and return an error if not.
114    pub fn verify_source_of_custom_value(
115        value: Spanned<&dyn CustomValue>,
116        source: &PluginSource,
117    ) -> Result<(), ShellError> {
118        if let Some(custom_value) = value
119            .item
120            .as_any()
121            .downcast_ref::<PluginCustomValueWithSource>()
122        {
123            custom_value.verify_source(value.span, source)
124        } else {
125            // Only PluginCustomValueWithSource can be sent
126            Err(ShellError::CustomValueIncorrectForPlugin {
127                name: value.item.type_name(),
128                span: value.span,
129                dest_plugin: source.name().to_owned(),
130                src_plugin: None,
131            })
132        }
133    }
134}
135
136impl std::ops::Deref for PluginCustomValueWithSource {
137    type Target = PluginCustomValue;
138
139    fn deref(&self) -> &PluginCustomValue {
140        &self.inner
141    }
142}
143
144/// This `Serialize` implementation always produces an error. Strip the source before sending.
145impl Serialize for PluginCustomValueWithSource {
146    fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
147    where
148        S: serde::Serializer,
149    {
150        use serde::ser::Error;
151        Err(Error::custom(
152            "can't serialize PluginCustomValueWithSource, remove the source first",
153        ))
154    }
155}
156
157impl CustomValue for PluginCustomValueWithSource {
158    fn clone_value(&self, span: Span) -> Value {
159        self.clone().into_value(span)
160    }
161
162    fn type_name(&self) -> String {
163        self.name().to_owned()
164    }
165
166    fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
167        self.get_plugin(Some(span), "get base value")?
168            .custom_value_to_base_value(self.clone().into_spanned(span))
169    }
170
171    fn follow_path_int(
172        &self,
173        self_span: Span,
174        index: usize,
175        path_span: Span,
176    ) -> Result<Value, ShellError> {
177        self.get_plugin(Some(self_span), "follow cell path")?
178            .custom_value_follow_path_int(
179                self.clone().into_spanned(self_span),
180                index.into_spanned(path_span),
181            )
182    }
183
184    fn follow_path_string(
185        &self,
186        self_span: Span,
187        column_name: String,
188        path_span: Span,
189    ) -> Result<Value, ShellError> {
190        self.get_plugin(Some(self_span), "follow cell path")?
191            .custom_value_follow_path_string(
192                self.clone().into_spanned(self_span),
193                column_name.into_spanned(path_span),
194            )
195    }
196
197    fn partial_cmp(&self, other: &Value) -> Option<Ordering> {
198        self.get_plugin(Some(other.span()), "perform comparison")
199            .and_then(|plugin| {
200                // We're passing Span::unknown() here because we don't have one, and it probably
201                // shouldn't matter here and is just a consequence of the API
202                plugin.custom_value_partial_cmp(self.clone(), other.clone())
203            })
204            .unwrap_or_else(|err| {
205                // We can't do anything with the error other than log it.
206                log::warn!(
207                    "Error in partial_cmp on plugin custom value (source={source:?}): {err}",
208                    source = self.source
209                );
210                None
211            })
212            .map(|ordering| ordering.into())
213    }
214
215    fn operation(
216        &self,
217        lhs_span: Span,
218        operator: Operator,
219        op_span: Span,
220        right: &Value,
221    ) -> Result<Value, ShellError> {
222        self.get_plugin(Some(lhs_span), "invoke operator")?
223            .custom_value_operation(
224                self.clone().into_spanned(lhs_span),
225                operator.into_spanned(op_span),
226                right.clone(),
227            )
228    }
229
230    fn as_any(&self) -> &dyn std::any::Any {
231        self
232    }
233
234    fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
235        self
236    }
237
238    #[doc(hidden)]
239    fn typetag_name(&self) -> &'static str {
240        "PluginCustomValueWithSource"
241    }
242
243    #[doc(hidden)]
244    fn typetag_deserialize(&self) {}
245}
246
247impl Drop for PluginCustomValueWithSource {
248    fn drop(&mut self) {
249        // If the custom value specifies notify_on_drop and this is the last copy, we need to let
250        // the plugin know about it if we can.
251        if self.notify_on_drop() && self.inner.ref_count() == 1 {
252            self.get_plugin(None, "drop")
253                // While notifying drop, we don't need a copy of the source
254                .and_then(|plugin| plugin.custom_value_dropped(self.inner.clone()))
255                .unwrap_or_else(|err| {
256                    // We shouldn't do anything with the error except log it
257                    let name = self.name();
258                    log::warn!("Failed to notify drop of custom value ({name}): {err}")
259                });
260        }
261    }
262}
263
264/// Helper trait for adding a source to a [`PluginCustomValue`]
265pub trait WithSource {
266    /// Add a source to a plugin custom value
267    fn with_source(self, source: Arc<PluginSource>) -> PluginCustomValueWithSource;
268}
269
270impl WithSource for PluginCustomValue {
271    fn with_source(self, source: Arc<PluginSource>) -> PluginCustomValueWithSource {
272        PluginCustomValueWithSource::new(self, source)
273    }
274}