Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(convert): add derive macros for To and FromBytes #667

Merged
merged 8 commits into from
Feb 5, 2024
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["extism-maturin", "manifest", "runtime", "libextism", "convert"]
members = ["extism-maturin", "manifest", "runtime", "libextism", "convert", "convert-macros"]
exclude = ["kernel"]

[workspace.package]
Expand All @@ -14,4 +14,5 @@ version = "0.0.0+replaced-by-ci"
[workspace.dependencies]
extism = { path = "./runtime", version = "0.0.0+replaced-by-ci" }
extism-convert = { path = "./convert", version = "0.0.0+replaced-by-ci" }
extism-convert-macros = { path = "./convert-macros", version = "0.0.0+replaced-by-ci" }
extism-manifest = { path = "./manifest", version = "0.0.0+replaced-by-ci" }
25 changes: 25 additions & 0 deletions convert-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "extism-convert-macros"
readme = "./README.md"
edition.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
version.workspace = true
description = "Macros to remove boilerplate with Extism"

[lib]
proc-macro = true

[features]
extism-path = []
extism-pdk-path = []

[dependencies]
# manyhow = "0.10.4"
ModProg marked this conversation as resolved.
Show resolved Hide resolved
manyhow.version = "0.11.0"
proc-macro-crate = "3.1.0"
proc-macro2 = "1.0.78"
quote = "1.0.35"
syn = { version = "2.0.48", features = ["derive"] }
105 changes: 105 additions & 0 deletions convert-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use std::iter;

use manyhow::{ensure, manyhow, Result};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::{format_ident, quote, ToTokens};
use syn::{parse_quote, Attribute, DeriveInput, Path};

/// Tries to resolve the path to `extism_convert` dynamically, falling back to feature flags when unsuccessful.
fn convert_path() -> Path {
match crate_name("extism") {
Ok(FoundCrate::Name(name)) => {
let ident = format_ident!("{name}");
parse_quote!(::#ident::convert)
}
Ok(FoundCrate::Itself) => parse_quote!(crate::core),
ModProg marked this conversation as resolved.
Show resolved Hide resolved
Err(_) => match crate_name("extism-convert").or_else(|_| crate_name("extism_pdk")) {
Ok(FoundCrate::Name(name)) => {
let ident = format_ident!("{name}");
parse_quote!(::#ident)
}
Ok(FoundCrate::Itself) => parse_quote!(crate),
Err(_) => match () {
() if cfg!(feature = "extism-path") => parse_quote!(::extism::convert),
() if cfg!(feature = "extism-pdk-path") => parse_quote!(::extism_pdk),
_ => parse_quote!(::extism_convert),
},
},
}
}
ModProg marked this conversation as resolved.
Show resolved Hide resolved

fn extract_encoding(attrs: &[Attribute]) -> Result<Path> {
let encodings: Vec<_> = attrs
.iter()
.filter(|attr| attr.path().is_ident("encoding"))
.collect();
ensure!(!encodings.is_empty(), "encoding needs to be specified"; try = "`#[encoding(ToJson)]`");
ensure!(encodings.len() < 2, encodings[1], "only one encoding can be specified"; try = "remove `{}`", encodings[1].to_token_stream());

Ok(encodings[0].parse_args()?)
}

#[manyhow]
#[proc_macro_derive(ToBytes, attributes(encoding))]
pub fn to_bytes(
DeriveInput {
attrs,
ident,
generics,
..
}: DeriveInput,
) -> Result {
let encoding = extract_encoding(&attrs)?;
let convert = convert_path();

let (_, type_generics, _) = generics.split_for_impl();

let mut generics = generics.clone();
generics.make_where_clause().predicates.push(
parse_quote!(for<'__to_bytes_b> #encoding<&'__to_bytes_b Self>: #convert::ToBytes<'__to_bytes_b>)
);
generics.params = iter::once(parse_quote!('__to_bytes_a))
.chain(generics.params)
.collect();
let (impl_generics, _, where_clause) = generics.split_for_impl();

Ok(quote! {
impl #impl_generics #convert::ToBytes<'__to_bytes_a> for #ident #type_generics #where_clause
{
type Bytes = ::std::vec::Vec<u8>;
ModProg marked this conversation as resolved.
Show resolved Hide resolved

fn to_bytes(&self) -> Result<Self::Bytes, #convert::Error> {
#convert::ToBytes::to_bytes(&#encoding(self)).map(|__bytes| __bytes.as_ref().to_vec())
}
}

})
}

#[manyhow]
#[proc_macro_derive(FromBytes, attributes(encoding))]
pub fn from_bytes(
DeriveInput {
attrs,
ident,
mut generics,
..
}: DeriveInput,
) -> Result {
let encoding = extract_encoding(&attrs)?;
let convert = convert_path();
generics
.make_where_clause()
.predicates
.push(parse_quote!(#encoding<Self>: #convert::FromBytesOwned));
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
Ok(quote! {
impl #impl_generics #convert::FromBytesOwned for #ident #type_generics #where_clause
{
fn from_bytes_owned(__data: &[u8]) -> Result<Self, #convert::Error> {
<#encoding<Self> as #convert::FromBytesOwned>::from_bytes_owned(__data).map(|__encoding| __encoding.0)
}
}

})
}
3 changes: 3 additions & 0 deletions convert/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ protobuf = { version = "3.2.0", optional = true }
rmp-serde = { version = "1.1.2", optional = true }
serde = "1.0.186"
serde_json = "1.0.105"
extism-convert-macros.workspace = true

[dev-dependencies]
serde = { version = "1.0.186", features = ["derive"] }
Expand All @@ -26,3 +27,5 @@ serde = { version = "1.0.186", features = ["derive"] }
default = ["msgpack", "prost", "raw"]
msgpack = ["rmp-serde"]
raw = ["bytemuck"]
extism-path = ["extism-convert-macros/extism-path"]
extism-pdk-path = ["extism-convert-macros/extism-pdk-path"]
2 changes: 2 additions & 0 deletions convert/src/from_bytes.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::*;

pub use extism_convert_macros::FromBytes;

/// `FromBytes` is used to define how a type should be decoded when working with
/// Extism memory. It is used for plugin output and host function input.
pub trait FromBytes<'a>: Sized {
Expand Down
2 changes: 2 additions & 0 deletions convert/src/to_bytes.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::*;

pub use extism_convert_macros::ToBytes;

/// `ToBytes` is used to define how a type should be encoded when working with
/// Extism memory. It is used for plugin input and host function output.
pub trait ToBytes<'a> {
Expand Down
2 changes: 1 addition & 1 deletion runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ url = "2"
glob = "0.3"
ureq = {version = "2.5", optional=true}
extism-manifest = { workspace = true }
extism-convert = { workspace = true }
extism-convert = { workspace = true, features = ["extism-path"]}
ModProg marked this conversation as resolved.
Show resolved Hide resolved
uuid = { version = "1", features = ["v4"] }
libc = "0.2"

Expand Down
Loading