nu_plugin_engine/plugin_custom_value_with_source/
mod.rs

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