Skip to content

Commit

Permalink
feat(convert): add derive macros for To and FromBytes
Browse files Browse the repository at this point in the history
  • Loading branch information
ModProg committed Jan 27, 2024
1 parent fbae853 commit ea84971
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 2 deletions.
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"
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"] }
100 changes: 100 additions & 0 deletions convert-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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),
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),
},
},
}
}

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,
mut generics,
..
}: DeriveInput,
) -> Result {
let encoding = extract_encoding(&attrs)?;
let convert = convert_path();
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, type_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>;

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(__data: &[u8]) -> Result<Self, #convert::Error> {
<#encoding as #convert::FromBytesOwned>::from_bytes(__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"]}
uuid = { version = "1", features = ["v4"] }
libc = "0.2"

Expand Down

0 comments on commit ea84971

Please sign in to comment.