Files
extism/python
zach cb55e52506 feat: Add extism-convert crate and use it for input/output to plugin calls (#443)
- Adds `extism-convert` crate with `ToBytes`, `FromBytes` and
`FromBytesOwned` traits
- This serves as a single interface for reading/writing rich types from
WebAssembly linear memory.
- Supports `Json` and `Msgpack` and `Protobuf` encodings out-of-the-box
- Updates `Plugin::call` to take `ToBytes` as the input argument and
return a `FromBytes` value
- Adds `host_fn!` macro to simplify host function creation
- Cleans up generated documentation a little
- PR for the Rust PDK: https://github.com/extism/rust-pdk/pull/31
- Adds a `typed_plugin!` macro to implement type-safe wrappers around
`Plugin`
- After this we should focus on adding similar type-conversion helpers
to the SDKs and other PDKs to make it easier to use across languages.
For example, a Python host running a Rust plugin using Msgpack encoded
types.

## Examples

### Calling a function

Instead of the untyped, bytes-only `call` function:

```rust
let output = plugin.call("func_name", "my data").unwrap();
let output: MyType = serde_json::from_slice(&output).unwrap();
```
We can now use richer types to encode/decode our values directly when
using `call`:

```rust
let Json(output) = plugin.call::<_, Json<MyType>>("func_name", "my data").unwrap();
```

### Allocating inside of a host function

The same interface works for host functions, so instead of:

```rust
fn hello_world(
    plugin: &mut CurrentPlugin,
    inputs: &[Val],
    outputs: &mut [Val],
    _user_data: UserData,
) -> Result<(), Error> {
    let handle = plugin.memory_handle_val(&inputs[0])?;
    let input = plugin.memory_read_str(handle)?;
    let output = plugin.memory_alloc_bytes(&input).unwrap();
    outputs[0] = output.into();
    Ok(())
}
```

Becomes:

```rust
fn hello_world(
    plugin: &mut CurrentPlugin,
    inputs: &[Val],
    outputs: &mut [Val],
    _user_data: UserData,
) -> Result<(), Error> {
    let my_value: String = plugin.memory_get_val(&inputs[0])?;
    let output = plugin.memory_new(&my_value)?;
    outputs[0] = plugin.memory_to_val(output);
    Ok(())
}
```

Although this isn't much of an improvement, using the `host_fn` macro,
we can really begin to see how the above function is really just an
identity function:
```rust
host_fn!(hello_world(a: String) -> String {
    a
});
```

### typed_plugin!

`typed_plugin!` is used to make a typed wrapper around a Plugin:

```rust
/// Create the typed plugin
typed_plugin!(Testing {
    count_vowels(&str) -> Json<Count>
});

/// Create the `Plugin` and convert it to `Testing` wrapper
let mut plugin: Testing = Plugin::new(WASM, [f], true).unwrap().into();

/// Call the `count_vowels` function:
let Json(output0): Json<Count> = plugin.count_vowels("abc123")?;
```

It could make sense to convert `host_fn` and/or `typed_plugin` to
proc-macros at some point, but for now they work and provide some
flexibility in experimenting with the interfaces. Another future update
could be to figure out a nice way to make it so input can be written in
multiple chunks, so the entire input doesn't have to get copied into
memory at once.
2023-09-14 12:32:38 -07:00
..
2022-09-12 13:32:06 -06:00