# Description This breaks `nu-plugin` up into four crates: - `nu-plugin-protocol`: just the type definitions for the protocol, no I/O. If someone wanted to wire up something more bare metal, maybe for async I/O, they could use this. - `nu-plugin-core`: the shared stuff between engine/plugin. Less stable interface. - `nu-plugin-engine`: everything required for the engine to talk to plugins. Less stable interface. - `nu-plugin`: everything required for the plugin to talk to the engine, what plugin developers use. Should be the most stable interface. No changes are made to the interface exposed by `nu-plugin` - it should all still be there. Re-exports from `nu-plugin-protocol` or `nu-plugin-core` are used as required. Plugins shouldn't ever have to use those crates directly. This should be somewhat faster to compile as `nu-plugin-engine` and `nu-plugin` can compile in parallel, and the engine doesn't need `nu-plugin` and plugins don't need `nu-plugin-engine` (except for test support), so that should reduce what needs to be compiled too. The only significant change here other than splitting stuff up was to break the `source` out of `PluginCustomValue` and create a new `PluginCustomValueWithSource` type that contains that instead. One bonus of that is we get rid of the option and it's now more type-safe, but it also means that the logic for that stuff (actually running the plugin for custom value ops) can live entirely within the `nu-plugin-engine` crate. # User-Facing Changes - New crates. - Added `local-socket` feature for `nu` to try to make it possible to compile without that support if needed. # Tests + Formatting - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib`
260 lines
8.7 KiB
Rust
260 lines
8.7 KiB
Rust
use crate::test_util::{expected_test_custom_value, test_plugin_custom_value, TestCustomValue};
|
|
|
|
use super::PluginCustomValue;
|
|
use nu_protocol::{engine::Closure, record, CustomValue, ShellError, Span, Value};
|
|
|
|
fn check_record_custom_values(
|
|
val: &Value,
|
|
keys: &[&str],
|
|
mut f: impl FnMut(&str, &dyn CustomValue) -> Result<(), ShellError>,
|
|
) -> Result<(), ShellError> {
|
|
let record = val.as_record()?;
|
|
for key in keys {
|
|
let val = record
|
|
.get(key)
|
|
.unwrap_or_else(|| panic!("record does not contain '{key}'"));
|
|
let custom_value = val
|
|
.as_custom_value()
|
|
.unwrap_or_else(|_| panic!("'{key}' not custom value"));
|
|
f(key, custom_value)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn check_list_custom_values(
|
|
val: &Value,
|
|
indices: impl IntoIterator<Item = usize>,
|
|
mut f: impl FnMut(usize, &dyn CustomValue) -> Result<(), ShellError>,
|
|
) -> Result<(), ShellError> {
|
|
let list = val.as_list()?;
|
|
for index in indices {
|
|
let val = list
|
|
.get(index)
|
|
.unwrap_or_else(|| panic!("[{index}] not present in list"));
|
|
let custom_value = val
|
|
.as_custom_value()
|
|
.unwrap_or_else(|_| panic!("[{index}] not custom value"));
|
|
f(index, custom_value)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn check_closure_custom_values(
|
|
val: &Value,
|
|
indices: impl IntoIterator<Item = usize>,
|
|
mut f: impl FnMut(usize, &dyn CustomValue) -> Result<(), ShellError>,
|
|
) -> Result<(), ShellError> {
|
|
let closure = val.as_closure()?;
|
|
for index in indices {
|
|
let val = closure
|
|
.captures
|
|
.get(index)
|
|
.unwrap_or_else(|| panic!("[{index}] not present in closure"));
|
|
let custom_value = val
|
|
.1
|
|
.as_custom_value()
|
|
.unwrap_or_else(|_| panic!("[{index}] not custom value"));
|
|
f(index, custom_value)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn serialize_deserialize() -> Result<(), ShellError> {
|
|
let original_value = TestCustomValue(32);
|
|
let span = Span::test_data();
|
|
let serialized = PluginCustomValue::serialize_from_custom_value(&original_value, span)?;
|
|
assert_eq!(original_value.type_name(), serialized.name());
|
|
let deserialized = serialized.deserialize_to_custom_value(span)?;
|
|
let downcasted = deserialized
|
|
.as_any()
|
|
.downcast_ref::<TestCustomValue>()
|
|
.expect("failed to downcast: not TestCustomValue");
|
|
assert_eq!(original_value, *downcasted);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn expected_serialize_output() -> Result<(), ShellError> {
|
|
let original_value = expected_test_custom_value();
|
|
let span = Span::test_data();
|
|
let serialized = PluginCustomValue::serialize_from_custom_value(&original_value, span)?;
|
|
assert_eq!(
|
|
test_plugin_custom_value().data(),
|
|
serialized.data(),
|
|
"The bincode configuration is probably different from what we expected. \
|
|
Fix test_plugin_custom_value() to match it"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn serialize_in_root() -> Result<(), ShellError> {
|
|
let span = Span::new(4, 10);
|
|
let mut val = Value::custom(Box::new(expected_test_custom_value()), span);
|
|
PluginCustomValue::serialize_custom_values_in(&mut val)?;
|
|
|
|
assert_eq!(span, val.span());
|
|
|
|
let custom_value = val.as_custom_value()?;
|
|
if let Some(plugin_custom_value) = custom_value.as_any().downcast_ref::<PluginCustomValue>() {
|
|
assert_eq!("TestCustomValue", plugin_custom_value.name());
|
|
assert_eq!(
|
|
test_plugin_custom_value().data(),
|
|
plugin_custom_value.data()
|
|
);
|
|
} else {
|
|
panic!("Failed to downcast to PluginCustomValue");
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn serialize_in_record() -> Result<(), ShellError> {
|
|
let orig_custom_val = Value::test_custom_value(Box::new(TestCustomValue(32)));
|
|
let mut val = Value::test_record(record! {
|
|
"foo" => orig_custom_val.clone(),
|
|
"bar" => orig_custom_val.clone(),
|
|
});
|
|
PluginCustomValue::serialize_custom_values_in(&mut val)?;
|
|
|
|
check_record_custom_values(&val, &["foo", "bar"], |key, custom_value| {
|
|
let plugin_custom_value: &PluginCustomValue = custom_value
|
|
.as_any()
|
|
.downcast_ref()
|
|
.unwrap_or_else(|| panic!("'{key}' not PluginCustomValue"));
|
|
assert_eq!(
|
|
"TestCustomValue",
|
|
plugin_custom_value.name(),
|
|
"'{key}' name not set correctly"
|
|
);
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn serialize_in_list() -> Result<(), ShellError> {
|
|
let orig_custom_val = Value::test_custom_value(Box::new(TestCustomValue(24)));
|
|
let mut val = Value::test_list(vec![orig_custom_val.clone(), orig_custom_val.clone()]);
|
|
PluginCustomValue::serialize_custom_values_in(&mut val)?;
|
|
|
|
check_list_custom_values(&val, 0..=1, |index, custom_value| {
|
|
let plugin_custom_value: &PluginCustomValue = custom_value
|
|
.as_any()
|
|
.downcast_ref()
|
|
.unwrap_or_else(|| panic!("[{index}] not PluginCustomValue"));
|
|
assert_eq!(
|
|
"TestCustomValue",
|
|
plugin_custom_value.name(),
|
|
"[{index}] name not set correctly"
|
|
);
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn serialize_in_closure() -> Result<(), ShellError> {
|
|
let orig_custom_val = Value::test_custom_value(Box::new(TestCustomValue(24)));
|
|
let mut val = Value::test_closure(Closure {
|
|
block_id: 0,
|
|
captures: vec![(0, orig_custom_val.clone()), (1, orig_custom_val.clone())],
|
|
});
|
|
PluginCustomValue::serialize_custom_values_in(&mut val)?;
|
|
|
|
check_closure_custom_values(&val, 0..=1, |index, custom_value| {
|
|
let plugin_custom_value: &PluginCustomValue = custom_value
|
|
.as_any()
|
|
.downcast_ref()
|
|
.unwrap_or_else(|| panic!("[{index}] not PluginCustomValue"));
|
|
assert_eq!(
|
|
"TestCustomValue",
|
|
plugin_custom_value.name(),
|
|
"[{index}] name not set correctly"
|
|
);
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn deserialize_in_root() -> Result<(), ShellError> {
|
|
let span = Span::new(4, 10);
|
|
let mut val = Value::custom(Box::new(test_plugin_custom_value()), span);
|
|
PluginCustomValue::deserialize_custom_values_in(&mut val)?;
|
|
|
|
assert_eq!(span, val.span());
|
|
|
|
let custom_value = val.as_custom_value()?;
|
|
if let Some(test_custom_value) = custom_value.as_any().downcast_ref::<TestCustomValue>() {
|
|
assert_eq!(expected_test_custom_value(), *test_custom_value);
|
|
} else {
|
|
panic!("Failed to downcast to TestCustomValue");
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn deserialize_in_record() -> Result<(), ShellError> {
|
|
let orig_custom_val = Value::test_custom_value(Box::new(test_plugin_custom_value()));
|
|
let mut val = Value::test_record(record! {
|
|
"foo" => orig_custom_val.clone(),
|
|
"bar" => orig_custom_val.clone(),
|
|
});
|
|
PluginCustomValue::deserialize_custom_values_in(&mut val)?;
|
|
|
|
check_record_custom_values(&val, &["foo", "bar"], |key, custom_value| {
|
|
let test_custom_value: &TestCustomValue = custom_value
|
|
.as_any()
|
|
.downcast_ref()
|
|
.unwrap_or_else(|| panic!("'{key}' not TestCustomValue"));
|
|
assert_eq!(
|
|
expected_test_custom_value(),
|
|
*test_custom_value,
|
|
"{key} not deserialized correctly"
|
|
);
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn deserialize_in_list() -> Result<(), ShellError> {
|
|
let orig_custom_val = Value::test_custom_value(Box::new(test_plugin_custom_value()));
|
|
let mut val = Value::test_list(vec![orig_custom_val.clone(), orig_custom_val.clone()]);
|
|
PluginCustomValue::deserialize_custom_values_in(&mut val)?;
|
|
|
|
check_list_custom_values(&val, 0..=1, |index, custom_value| {
|
|
let test_custom_value: &TestCustomValue = custom_value
|
|
.as_any()
|
|
.downcast_ref()
|
|
.unwrap_or_else(|| panic!("[{index}] not TestCustomValue"));
|
|
assert_eq!(
|
|
expected_test_custom_value(),
|
|
*test_custom_value,
|
|
"[{index}] name not deserialized correctly"
|
|
);
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn deserialize_in_closure() -> Result<(), ShellError> {
|
|
let orig_custom_val = Value::test_custom_value(Box::new(test_plugin_custom_value()));
|
|
let mut val = Value::test_closure(Closure {
|
|
block_id: 0,
|
|
captures: vec![(0, orig_custom_val.clone()), (1, orig_custom_val.clone())],
|
|
});
|
|
PluginCustomValue::deserialize_custom_values_in(&mut val)?;
|
|
|
|
check_closure_custom_values(&val, 0..=1, |index, custom_value| {
|
|
let test_custom_value: &TestCustomValue = custom_value
|
|
.as_any()
|
|
.downcast_ref()
|
|
.unwrap_or_else(|| panic!("[{index}] not TestCustomValue"));
|
|
assert_eq!(
|
|
expected_test_custom_value(),
|
|
*test_custom_value,
|
|
"[{index}] name not deserialized correctly"
|
|
);
|
|
Ok(())
|
|
})
|
|
}
|