1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
//! This small Rust crate provides a wrapper struct for generated Rust FlatBuffers that allows them to be used as owned types.</br>
//! A owned FlatBuffer does not reference its source data and can therefore be easily moved into another thread.
//!
//! ## Quickstart
//! Use the `flatbuffers_owned!` convenience macro on your FlatBuffers to implement the required trait and introduce a type alias for each owned FlatBuffer.
//!
//! Generate the `OwnedMessage` type alias for the `Message` FlatBuffer:
//! ```rust
//! flatbuffers_owned!(Message);
//! ```
//!
//! Receive a byte slice, create a boxed slice, and initialize the owned flatbuffer:
//! ```rust
//! let message_bytes: &[u8] = receive_message_bytes();
//! let message_bytes: Box<[u8]> = Box::from(message_bytes);
//!
//! let owned_message = OwnedMessage::new(message_bytes).unwrap();
//! ```
//!
//! Access the actual FlatBuffer:
//! ```rust
//! let message: Message = owned_message.as_actual();
//!
//! assert_eq!(message.get_text().unwrap(), "Hello, world!");
//! ```
//!
//! ## Error-Handling
//! The new() constructor always verifies the raw FlatBuffer bytes using the FlatBuffer's built-in run_verifier() method.</br>
//! Since there can always be a faulty byte-slice passed, you need to check the returned Result of the constructor:
//! ```rust
//! for id in message_ids {
//! let message_bytes = Box::from(receive_message_bytes());
//!
//! let owned_message = OwnedMessage::new(message_bytes);
//!
//! match owned_message {
//! Ok(message) => {
//! // ... process message
//! },
//! Err(e) => {
//! println!("Failed to parse Message: {}", e);
//! // ... handling logic
//! },
//! }
//! }
//! ```
//!
//! ## Approach
//! ### The wrapper struct
//! The wrapper struct is a newtype for a Box<[u8]> that accepts a FlatBuffer as the generic type.</br>
//! With the `flatbuffers_owned!` convenience macro we get a type alias that just masks this wrapper struct.
//!
//! ```rust
//! pub type OwnedMessage = OwnedFlatBuffer<Message<'static>>;
//! ```
//!
//! So instead of `OwnedMessage`, we can just as well use `OwnedFlatBuffer<Message<'static>>`.
//!
//! ```rust
//! let owned_message = OwnedFlatBuffer::<Message<'static>>::new(message_bytes).unwrap();
//! ```
//!
//! As you may have noticed, the `'static` lifetime is then always present when working with the OwnedFlatBuffer.</br>
//! However, this can be misleading, because the OwnedFlatBuffer does not actually reference anything in the `'static` lifetime.</br>
//! The lifetime is only required by the FlatBuffer struct.</br>
//! So to make the code more readable, we have the type alias.</br>
//!
//! ### Deref to &[u8]
//! The OwnedFlatBuffer struct de-references itself to its underlying bytes slice.</br>
//! A Deref to the actual FlatBuffer struct is sadly not possible, since the associated type of the Deref trait can not carry a lifetime.
//!
//! ## Open to Feedback
//! If you have any ideas for improvements or would like to contribute to this project, please feel free to open an issue or pull request on GitHub.</br>
//! I will also be happy for any general tips or suggestions given that this is my first (published) library ever. :)
use std::ops::Deref;
use flatbuffers::{Follow, ForwardsUOffset, InvalidFlatbuffer, Verifiable, Verifier, VerifierOptions};
#[doc(hidden)]
pub use paste::paste;
/// This trait allows a `.follow()` method that returns a FlatBuffer with the lifetime of the provided byte slice.
///
/// # Example trait implementation
/// ```
/// use flatbuffers_owned::RelaxedFollow;
///
/// impl RelaxedFollow for MyStruct<'_> {
/// type Inner<'a> = MyFlatBuffer<'a>;
/// }
pub trait RelaxedFollow {
type Inner<'a>: Follow<'a>;
fn follow(buf: &[u8], loc: usize) -> <<Self as RelaxedFollow>::Inner<'_> as Follow<'_>>::Inner {
unsafe { <ForwardsUOffset<Self::Inner<'_>>>::follow(buf, loc) }
}
}
/// The trait for owned FlatBuffers.
///
/// This trait requires the [`RelaxedFollow`] trait bound on the FlatBuffer type.
/// It can be either implemented manually or by using the `flatbuffer_owned!` macro.
///
/// # Example trait usage
/// ```
/// # use flatbuffers_owned::OwnedFlatBuffer;
///
/// fn process_fbs(flatbuffers: &[impl OwnedFlatBufferTrait]) {
/// for item in flatbuffers {
/// let bytes: &[u8] = &*item;
/// // ... do something with the raw bytes
/// }
/// }
/// ```
pub trait OwnedFlatBufferTrait: Deref<Target = [u8]> + Sized {
type FlatBuffer: RelaxedFollow + Verifiable;
/// Initializes a actual FlatBuffer struct that references the owned data.
fn as_actual(&self) -> <<<Self as OwnedFlatBufferTrait>::FlatBuffer as RelaxedFollow>::Inner<'_> as Follow<'_>>::Inner;
/// Create a new owned FlatBuffer from the provided data.
/// This method calls the verifier of the FlatBuffer and returns an error result if the data is invalid.
fn new(data: Box<[u8]>) -> Result<Self, InvalidFlatbuffer>;
}
/// This is the FlatBuffer wrapper struct.
/// It is a newtype for a Box<[u8]> that accepts a FlatBuffer as the generic type.
/// The lifetime parameter of the FlatBuffer type is nowhere used and can be safely set to `'static`.
///
/// To access a actual FlatBuffer struct, use the `.as_actual()` method.
/// The returned FlatBuffer has the lifetime of the `OwnedFlatBuffer` struct.
///
/// # Example usage
/// ```
/// # use flatbuffers_owned::OwnedFlatBuffer;
///
/// let owned_message;
/// {
/// let message_bytes: &[u8] = receive_message_bytes();
/// let message_bytes: Box<[u8]> = Box::from(message_bytes);
///
/// owned_message = OwnedFlatBuffer::<Message<'static>>::new(message_bytes).expect("Failed to parse message");
/// }
///
/// let message = owned_message.as_actual();
///
/// assert_eq!(message.get_text().unwrap(), "Hello, world!");
/// ```
pub struct OwnedFlatBuffer<T>(Box<[u8]>, std::marker::PhantomData<T>)
where T: RelaxedFollow + Verifiable;
/// I would really like to implement a deref to the actual FlatBuffer struct here, but the associated type does not allow lifetime parameters.
impl<T> Deref for OwnedFlatBuffer<T>
where T: RelaxedFollow + Verifiable
{
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl <T> OwnedFlatBufferTrait for OwnedFlatBuffer<T>
where T: RelaxedFollow + Verifiable
{
type FlatBuffer = T;
fn as_actual(&self) -> <<<Self as OwnedFlatBufferTrait>::FlatBuffer as RelaxedFollow>::Inner<'_> as Follow<'_>>::Inner {
Self::FlatBuffer::follow(self, 0)
}
fn new(data: Box<[u8]>) -> Result<Self, InvalidFlatbuffer> {
let opts = VerifierOptions::default();
let mut v = Verifier::new(&opts, &data);
<ForwardsUOffset<Self::FlatBuffer>>::run_verifier(&mut v, 0)?;
Ok(Self(data, std::marker::PhantomData))
}
}
/// This macro implements the [`RelaxedFollow`] trait for your FlatBuffer and creates a type alias for the corresponding [`OwnedFlatBuffer`] type.
/// This is the go-to macro for creating an owned FlatBuffer type.
///
///
/// # Example usage
/// ```
/// # use flatbuffers_owned::flatbuffers_owned;
///
/// flatbuffers_owned!(MyFirstFlatBuffer, MySecondFlatBuffer);
/// ```
///
/// The above macro call expands to:
/// ```
/// # use flatbuffers_owned::{RelaxedFollow, OwnedFlatBuffer};
///
/// impl RelaxedFollow for MyFirstFlatBuffer<'_> {
/// type Inner<'a> = MyFirstFlatBuffer<'a>;
/// }
///
/// pub type OwnedMyFirstFlatBuffer = OwnedFlatBuffer<MyFirstFlatBuffer<'static>>;
///
/// // ... and the same for MySecondFlatBuffer
///
#[macro_export]
macro_rules! flatbuffers_owned {
($struct_name:ident) => {
$crate::paste! {
impl $crate::RelaxedFollow for $struct_name<'_> {
type Inner<'a> = $struct_name<'a>;
}
pub type [<Owned $struct_name>] = $crate::OwnedFlatBuffer<$struct_name<'static>>;
}
};
($($struct_name:ident),*) => {
$(
$crate::flatbuffers_owned!($struct_name);
)*
};
}