Keep in mind that the engine will not validate that the parameters of a call made by the plugin actually matches the signature of the command being called, so care must be taken when designing the plugin to try to match the documented signature. There is not currently a way to look up the signature of a command before running it, but we may add that in the future to make it easier to ensure a plugin call behaves as expected. As performance is a priority for plugins, we do not intend to validate call arguments from plugins at this time.
There is some overhead when making calls from plugins back to the engine, and it may be difficult to construct some of the arguments for commands - for example, it's not possible to create new closures from within plugins. We recommend trying to implement functionality within the plugin if possible, and falling back to command calls only when necessary. It is virtually guaranteed that a script that chains multiple commands together will be more performant than trying to put pipelines together from within a plugin, so you may want to provide a companion script with your plugins, or expect your users to compose pipelines made up of simple commands [rather than providing lots of different options](https://www.nushell.sh/contributor-book/philosophy_0_80.html#command-philosophy).
## Testing plugins
Rust-based plugins can use the [`nu-plugin-test-support`](https://docs.rs/nu-plugin-test-support/) crate to write tests. Examples can be tested automatically:
```rust
use nu_protocol::{Example, ShellError, Value};
use nu_plugin::PluginCommand;
struct FibPlugin;
struct Fib;
// ...
impl PluginCommand for Fib {
type Plugin = FibPlugin;
fn name(&self) -> &str {
"fib"
}
// ...
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "fib 20",
description: "Compute the 20th Fibonacci number",
result: Some(Value::test_int(6765))
},
]
}
// ...
}
#[test]
fn test_examples() -> Result<(), ShellError> {
use nu_plugin_test_support::PluginTest;
PluginTest::new("fib", FibPlugin.into())?.test_examples(&Fib)
}
```
Manual tests, including with input, can also be created, via `.eval()` and `.eval_with()`:
```rust
#[test]
fn test_fib_on_input() -> Result<(), ShellError> {
use nu_plugin_test_support::PluginTest;
use nu_protocol::{IntoPipelineData, Span};
// this would be identical to `20 | fib`, but anything can be passed,
// including a stream
let result = PluginTest::new("fib", FibPlugin.into())?
.eval_with("fib", Value::test_int(20).into_pipeline_data())?
.into_value(Span::test_data());
assert_eq!(Value::test_int(6765), result);
}
```
The Nu context within tests is very basic and mostly only contains the plugin commands themselves, as well as all of the core language keywords from [`nu-cmd-lang`](https://docs.rs/nu-cmd-lang/). If you need to test your plugin with other commands, you can include those crates and then use `.add_decl()` to include them in the context:
```rust
#[test]
fn test_fib_with_sequence() -> Result<(), ShellError> {
use nu_command::Seq;
use nu_plugin_test_support::PluginTest;
let result = PluginTest::new("fib", FibPlugin.into())?
.add_decl(Box::new(Seq))?
.eval("seq 1 10 | fib")?;
assert_eq!(10, result.into_iter().count());
}
```
Keep in mind that this will increase the compilation time of your tests, so it's generally preferred to do your other test logic within Rust if you can.
Tests on custom values are fully supported as well, but they will be serialized and deserialized to ensure that they are able to pass through the serialization that happens to custom values between the plugin and the engine safely.
## Under the hood
Writing Nu plugins in Rust is convenient because we can make use of the `nu-plugin` and `nu-protocol` crates, which are part of Nu itself and define the interface protocol. To write a plugin in another language you will need to implement that protocol yourself. If you're goal is to write Nu plugins in Rust you can stop here. If you'd like to explore the low level plugin interface or write plugins in other languages such as Python, keep reading.