Skip to main content

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