extism_convert/
encoding.rs

1use crate::*;
2
3use base64::Engine;
4
5/// The `encoding` macro can be used to create newtypes that implement a particular encoding for the
6/// inner value.
7///
8/// For example, the following line creates a new JSON encoding using serde_json:
9///
10/// ```
11/// extism_convert::encoding!(MyJson, serde_json::to_vec, serde_json::from_slice);
12/// ```
13///
14/// This will create a struct `struct MyJson<T>(pub T)` and implement [`ToBytes`] using [`serde_json::to_vec`]
15/// and [`FromBytesOwned`] using [`serde_json::from_slice`]
16#[macro_export]
17macro_rules! encoding {
18    ($pub:vis $name:ident, $to_vec:expr, $from_slice:expr) => {
19        #[doc = concat!(stringify!($name), " encoding")]
20        #[derive(Debug)]
21        $pub struct $name<T>(pub T);
22
23        impl<T> $name<T> {
24            pub fn into_inner(self) -> T {
25                self.0
26            }
27        }
28
29        impl<T> From<T> for $name<T> {
30            fn from(data: T) -> Self {
31                Self(data)
32            }
33        }
34
35        impl<T: serde::de::DeserializeOwned> $crate::FromBytesOwned for $name<T> {
36            fn from_bytes_owned(data: &[u8]) -> std::result::Result<Self, $crate::Error> {
37                let x = $from_slice(data)?;
38                std::result::Result::Ok($name(x))
39            }
40        }
41
42        impl<'a, T: serde::Serialize> $crate::ToBytes<'a> for $name<T> {
43            type Bytes = Vec<u8>;
44
45            fn to_bytes(&self) -> std::result::Result<Self::Bytes, $crate::Error> {
46                let enc = $to_vec(&self.0)?;
47                std::result::Result::Ok(enc)
48            }
49        }
50    };
51}
52
53encoding!(pub Json, serde_json::to_vec, serde_json::from_slice);
54
55#[cfg(feature = "msgpack")]
56encoding!(pub Msgpack, rmp_serde::to_vec, rmp_serde::from_slice);
57
58impl ToBytes<'_> for serde_json::Value {
59    type Bytes = Vec<u8>;
60
61    fn to_bytes(&self) -> Result<Self::Bytes, Error> {
62        Ok(serde_json::to_vec(self)?)
63    }
64}
65
66impl FromBytesOwned for serde_json::Value {
67    fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
68        Ok(serde_json::from_slice(data)?)
69    }
70}
71
72/// Base64 conversion
73///
74/// When using `Base64` with `ToBytes` any type that implement `AsRef<[T]>` may be used as the inner value,
75/// but only `Base64<String>` and `Base64<Vec>` may be used with `FromBytes`
76///
77/// A value wrapped in `Base64` will automatically be encoded/decoded using base64, the inner value should not
78/// already be base64 encoded.
79#[derive(Debug)]
80pub struct Base64<T: AsRef<[u8]>>(pub T);
81
82impl<T: AsRef<[u8]>> From<T> for Base64<T> {
83    fn from(data: T) -> Self {
84        Self(data)
85    }
86}
87
88impl<T: AsRef<[u8]>> ToBytes<'_> for Base64<T> {
89    type Bytes = String;
90
91    fn to_bytes(&self) -> Result<Self::Bytes, Error> {
92        Ok(base64::engine::general_purpose::STANDARD.encode(&self.0))
93    }
94}
95
96impl FromBytesOwned for Base64<Vec<u8>> {
97    fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
98        Ok(Base64(
99            base64::engine::general_purpose::STANDARD.decode(data)?,
100        ))
101    }
102}
103
104impl FromBytesOwned for Base64<String> {
105    fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
106        Ok(Base64(String::from_utf8(
107            base64::engine::general_purpose::STANDARD.decode(data)?,
108        )?))
109    }
110}
111
112/// Protobuf encoding
113///
114/// Allows for `prost` Protobuf messages to be used as arguments to Extism plugin calls
115#[cfg(feature = "prost")]
116#[derive(Debug)]
117pub struct Prost<T: prost::Message>(pub T);
118
119#[cfg(feature = "prost")]
120impl<T: prost::Message> From<T> for Prost<T> {
121    fn from(data: T) -> Self {
122        Self(data)
123    }
124}
125
126#[cfg(feature = "prost")]
127impl<T: prost::Message> ToBytes<'_> for Prost<T> {
128    type Bytes = Vec<u8>;
129
130    fn to_bytes(&self) -> Result<Self::Bytes, Error> {
131        Ok(self.0.encode_to_vec())
132    }
133}
134
135#[cfg(feature = "prost")]
136impl<T: Default + prost::Message> FromBytesOwned for Prost<T> {
137    fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
138        Ok(Prost(T::decode(data)?))
139    }
140}
141
142/// Protobuf encoding
143///
144/// Allows for `rust-protobuf` Protobuf messages to be used as arguments to Extism plugin calls
145#[cfg(feature = "protobuf")]
146pub struct Protobuf<T: protobuf::Message>(pub T);
147
148#[cfg(feature = "protobuf")]
149impl<T: protobuf::Message> ToBytes<'_> for Protobuf<T> {
150    type Bytes = Vec<u8>;
151
152    fn to_bytes(&self) -> Result<Self::Bytes, Error> {
153        Ok(self.0.write_to_bytes()?)
154    }
155}
156
157#[cfg(feature = "protobuf")]
158impl<T: Default + protobuf::Message> FromBytesOwned for Protobuf<T> {
159    fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
160        Ok(Protobuf(T::parse_from_bytes(data)?))
161    }
162}
163
164/// Raw does no conversion, it just copies the memory directly.
165/// Note: This will only work for types that implement [bytemuck::Pod](https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html)
166#[cfg(all(feature = "raw", target_endian = "little"))]
167pub struct Raw<'a, T: bytemuck::Pod>(pub &'a T);
168
169#[cfg(all(feature = "raw", target_endian = "little"))]
170impl<'a, T: bytemuck::Pod> ToBytes<'a> for Raw<'a, T> {
171    type Bytes = &'a [u8];
172
173    fn to_bytes(&self) -> Result<Self::Bytes, Error> {
174        Ok(bytemuck::bytes_of(self.0))
175    }
176}
177
178#[cfg(all(feature = "raw", target_endian = "little"))]
179impl<'a, T: bytemuck::Pod> FromBytes<'a> for Raw<'a, T> {
180    fn from_bytes(data: &'a [u8]) -> Result<Self, Error> {
181        let x = bytemuck::try_from_bytes(data).map_err(|x| Error::msg(x.to_string()))?;
182        Ok(Raw(x))
183    }
184}
185
186#[cfg(all(feature = "raw", target_endian = "big"))]
187compile_error!("The raw feature is only supported on little endian targets");