flatbuffers_owned/
lib.rs

1//! A Rust crate that enables a more flexible usage of FlatBuffers.
2//!
3//! Using the `flatbuffers_owned!` macro, you can generate wrapper structs for your flatc generated Rust FlatBuffers. \
4//! The generated wrapper structs utilize more flexible lifetimes to access the actual underlying FlatBuffer structure. \
5//! As the lifetimes are more relaxed, the raw FlatBuffer bytes can be owned and moved along, or be referenced with any lifetime available.
6//!
7//! ## Usage
8//! Add this to your `Cargo.toml`:
9//!
10//! ```toml
11//! [dependencies]
12//! flatbuffers-owned = "0.2"
13//! ```
14//!
15//! ## Quickstart
16//! Use the `flatbuffers_owned!` macro on your FlatBuffers to generate the wrapper structs.
17//!
18//! This generates a `RelaxedMessage` wrapper-struct and a `OwnedMessage` type alias for the `Message` FlatBuffer:
19//! ```rust
20//! use flatbuffers_owned::*;
21//!
22//! flatbuffers_owned!(Message);
23//! ```
24//!
25//! Receive a byte slice, create a boxed slice, and initialize the owned flatbuffer:
26//! ```rust
27//! let message_bytes: &[u8] = receive_message_bytes();
28//! let message_bytes: Box<[u8]> = Box::from(message_bytes);
29//!
30//! let owned_message = OwnedMessage::new(message_bytes).unwrap();
31//! ```
32//!
33//! Access the actual FlatBuffer:
34//! ```rust
35//! let message: Message = owned_message.as_actual();
36//!
37//! assert_eq!(message.get_text().unwrap(), "Hello, world!");
38//! ```
39//!
40//! ## Error-Handling
41//! The `new()` constructor always verifies the raw FlatBuffer bytes using the FlatBuffer's built-in `run_verifier()` method.</br>
42//! Since there can always be a faulty byte-slice passed, you need to check the returned Result of the constructor:
43//! ```rust
44//! for id in message_ids {
45//!     let message_bytes = Box::from(receive_message_bytes());
46//!
47//!     let owned_message = OwnedMessage::new(message_bytes);
48//!
49//!     match owned_message {
50//!         Ok(message) => {
51//!             // ... process message
52//!         },
53//!         Err(e) => {
54//!             println!("Failed to parse Message: {}", e);
55//!             // ... handling logic
56//!         }
57//!     }
58//! }
59//! ```
60//!
61//! ## Approach
62//! ### The wrapper struct
63//! The `Relaxed{FLATBUFFER_NAME}` wrapper struct is a Newtype which can wrap any struct that can convert to a byte slice reference. (```where TBuffer: AsRef<[u8]>```) \
64//! This struct can be used with buffers that fully own its memory, or only hold a shared-reference.
65//!
66//! The `Owned{FLATBUFFER_NAME}` type alias generated along the wrapper struct just predefines the `TBuffer` generic. \
67//! For our `Message` example FlatBuffer, the generated type alias code would be the following:
68//! ```rust
69//! pub type OwnedMessage = RelaxedMessage<Box<[u8]>>;
70//! ```
71//!
72//! ### Deref to &[u8]
73//! The `RelaxedFlatBufferTrait` enforces a de-reference to the underlying [u8] byte slice. \
74//! A de-reference to the actual FlatBuffer struct is sadly not possible, since the associated type of the `Deref` trait can not carry a lifetime.
75//!
76//! ## Open to Feedback
77//! 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.
78//!
79//! I will also be happy for any general tips or suggestions given that this is my first (published) library ever. :)
80
81use std::ops::Deref;
82use flatbuffers::{Follow, ForwardsUOffset, InvalidFlatbuffer, Verifiable, Verifier, VerifierOptions};
83
84#[doc(hidden)]
85pub use paste::paste;
86
87/// This trait allows a `.follow()` method that returns a FlatBuffer with the lifetime of the provided byte slice.
88///
89/// # Example trait implementation
90/// ```
91/// use flatbuffers_owned::RelaxedFollowTrait;
92///
93/// impl RelaxedFollowTrait for MyFlatBuffer<'_> {
94///    type Inner<'a> = MyFlatBuffer<'a>;
95/// }
96pub trait RelaxedFollowTrait {
97    type Inner<'a>: Follow<'a>;
98
99    /// # Safety
100    ///
101    /// This function lacks verification. This method could yield a FlatBuffer with undefined behavior on field reads when corrupted FlatBuffer bytes are passed.
102    #[inline(always)]
103    unsafe fn follow(buf: &[u8], loc: usize) -> <<Self as RelaxedFollowTrait>::Inner<'_> as Follow<'_>>::Inner {
104        <ForwardsUOffset<Self::Inner<'_>>>::follow(buf, loc)
105    }
106}
107
108/// This trait serves as the foundation for this crate. \
109/// It allows access to the underlying FlatBuffer using the [as_actual()](RelaxedFlatBufferTrait::as_actual) method. \
110/// The lifetimes used here are more relaxed than those in the flatc generated code. \
111/// For example, it could be implemented for a fully owned `Box<\[u8\]>` new-type or even a reference to memory that has been temporarily pinned following a DB query.
112///
113/// This trait requires the [RelaxedFollowTrait] trait bound on the FlatBuffer type. \
114/// It can be implemented either manually or using the [flatbuffers_owned!](flatbuffers_owned) macro.
115///
116/// # Safety
117/// The default implementation of the [as_actual()](RelaxedFlatBufferTrait::as_actual) method uses the unsafe [.follow()](RelaxedFollowTrait::follow) method do return the actual FlatBuffer. \
118/// [as_actual()](RelaxedFlatBufferTrait::as_actual) does not verify the FlatBuffer. \
119/// If you choose to implement this trait manually, you must ensure that the underlying byte slice is verified and the buffer remains immutable.
120///
121/// # Example trait usage
122/// ```
123/// # use flatbuffers_owned::RelaxedFlatBufferTrait;
124///
125/// fn store_fbs<TBuffer>(flatbuffers: &[impl RelaxedFlatBufferTrait<TBuffer>]) {
126///    for item in flatbuffers {
127///         let bytes: &[u8] = item.deref();
128///         // ... store the raw bytes somewhere.
129///    }
130/// }
131/// ```
132pub unsafe trait RelaxedFlatBufferTrait<TBuffer>
133    where Self: Deref<Target = [u8]> + Sized
134{
135    type FlatBuffer: RelaxedFollowTrait + Verifiable;
136
137    /// Initializes a actual FlatBuffer struct from the byte slice returned by the Self::deref() method.
138    #[inline(always)]
139    fn as_actual(&self) -> <<<Self as RelaxedFlatBufferTrait<TBuffer>>::FlatBuffer as RelaxedFollowTrait>::Inner<'_> as Follow<'_>>::Inner {
140        unsafe { Self::FlatBuffer::follow(self, 0) }
141    }
142
143    /// Verifies the FlatBuffer data.
144    fn verify(data: &[u8]) -> Result<(), InvalidFlatbuffer> {
145        let opts = VerifierOptions::default();
146        let mut v = Verifier::new(&opts, data);
147
148        <ForwardsUOffset<Self::FlatBuffer>>::run_verifier(&mut v, 0)
149    }
150
151    fn new(data: TBuffer) -> Result<Self, InvalidFlatbuffer>;
152}
153
154/// Use this macro on your FlatBuffers to generate the required code to start using this crate.
155///
156/// After invoking the macro, you have two generated types for each of your passed FlatBuffers: \
157/// 1. A generic new-type struct named `Relaxed{FLATBUFFER_NAME}`, which implements [RelaxedFlatBufferTrait] and takes the generic `TBuffer: AsRef<[u8]>`. \
158/// 2. A type alias named `Owned{FLATBUFFER_NAME}, which aliases the `Relaxed{FLATBUFFER_NAME}` struct and sets `TBuffer` to `Box<[u8]>`.
159///
160/// # Usage
161/// ```
162/// use flatbuffers_owned::flatbuffers_owned;
163///
164/// flatbuffers_owned!(MyFirstFlatBuffer, MySecondFlatBuffer);
165/// ```
166#[macro_export]
167macro_rules! flatbuffers_owned {
168    ($struct_name:ident) => {
169        $crate::paste! {
170            impl $crate::RelaxedFollowTrait for $struct_name<'_> {
171                type Inner<'a> = $struct_name<'a>;
172            }
173
174            #[derive(Clone, Debug, PartialEq, Eq, Hash)]
175            pub struct [<Relaxed $struct_name>]<TBuffer: AsRef<[u8]>>(TBuffer);
176
177            unsafe impl <TBuffer: AsRef<[u8]>> RelaxedFlatBufferTrait<TBuffer> for [<Relaxed $struct_name>]<TBuffer> {
178                type FlatBuffer = $struct_name<'static>;
179
180                fn new(data: TBuffer) -> Result<Self, flatbuffers::InvalidFlatbuffer> {
181                    Self::verify(data.as_ref())?;
182
183                    Ok(Self(data))
184                }
185            }
186            
187            impl <TBuffer: AsRef<[u8]>> std::ops::Deref for [<Relaxed $struct_name>]<TBuffer> {
188                type Target = [u8];
189
190                fn deref(&self) -> &Self::Target {
191                    self.0.as_ref()
192                }
193            }
194
195            pub type [<Owned $struct_name>] = [<Relaxed $struct_name>]<Box<[u8]>>;
196        }
197    };
198
199    ($($struct_name:ident),*) => {
200        $(
201            $crate::flatbuffers_owned!($struct_name);
202        )*
203    };
204}