Skip to main content

rapira/
lib.rs

1// #![feature(trace_macros)]
2// #![feature(log_syntax)]
3#![cfg_attr(not(feature = "std"), no_std)]
4
5mod allocated;
6pub mod error;
7mod from_u8;
8pub mod funcs;
9mod implements;
10#[cfg(feature = "std")]
11mod macros;
12pub mod max_cap;
13mod primitive;
14
15pub use error::{RapiraError, Result};
16pub use from_u8::{EnumFromU8Error, FromU8};
17#[cfg(feature = "postcard")]
18pub use implements::postcard;
19#[cfg(feature = "zerocopy")]
20pub use implements::zero;
21pub use primitive::{byte_rapira, bytes_rapira, str_rapira};
22
23#[cfg(feature = "alloc")]
24extern crate alloc;
25
26pub use funcs::{
27    check_bytes, deser_unchecked, deser_unsafe, deserialize, deserialize_ctx,
28    deserialize_versioned, size, size_ctx,
29};
30#[cfg(feature = "alloc")]
31pub use funcs::{extend_vec, serialize, serialize_ctx};
32pub use rapira_derive::{FromU8, PrimitiveFromEnum, Rapira};
33
34/// Bitflags for context-aware serialization.
35/// Bits 0–7 are reserved for rapira. External crates use bits 8+.
36#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
37pub struct RapiraFlags(pub u64);
38
39impl RapiraFlags {
40    pub const NONE: Self = Self(0);
41
42    #[inline]
43    pub const fn new(flags: u64) -> Self {
44        Self(flags)
45    }
46
47    #[inline]
48    pub const fn has(self, flag: u64) -> bool {
49        self.0 & flag != 0
50    }
51
52    #[inline]
53    pub const fn with(self, flag: u64) -> Self {
54        Self(self.0 | flag)
55    }
56}
57
58pub trait Rapira {
59    const STATIC_SIZE: Option<usize> = None;
60    const MIN_SIZE: usize;
61
62    /// size of bytes for serialize
63    fn size(&self) -> usize;
64
65    /// check bytes, collections len, check utf-8, NonZero, f32 and others...
66    fn check_bytes(slice: &mut &[u8]) -> Result<()>;
67
68    /// this is safe, but not check collections capacity!
69    /// recommend use only for safe data (example: from DB), not external data.
70    fn from_slice(slice: &mut &[u8]) -> Result<Self>
71    where
72        Self: Sized;
73
74    #[inline]
75    fn debug_from_slice(slice: &mut &[u8]) -> Result<Self>
76    where
77        Self: Sized + std::fmt::Debug,
78    {
79        let len = slice.len();
80        Self::from_slice(slice)
81            .inspect(|item| {
82                println!("len: {len}, item: {item:?}");
83            })
84            .inspect_err(|err| {
85                println!("len: {len}, err: {err:?}");
86            })
87    }
88
89    /// # Safety
90    ///
91    /// this mean not unsafe, but unchecked
92    /// utf-8 strings, NonZero, float numbers not check
93    #[inline]
94    unsafe fn from_slice_unchecked(slice: &mut &[u8]) -> Result<Self>
95    where
96        Self: Sized,
97    {
98        Self::from_slice(slice)
99    }
100
101    /// # Safety
102    ///
103    /// This is unsafe, but maybe safe after check_bytes fn
104    unsafe fn from_slice_unsafe(slice: &mut &[u8]) -> Result<Self>
105    where
106        Self: Sized,
107    {
108        Self::from_slice(slice)
109    }
110
111    /// Deserialize with schema version awareness.
112    ///
113    /// Enables backward-compatible deserialization: new code can read old data
114    /// that is missing fields added in later versions. The version number is
115    /// stored **externally** (e.g. in DB metadata), not inside the serialized bytes.
116    ///
117    /// Default implementation delegates to [`from_slice`](Rapira::from_slice).
118    /// The derive macro generates an override when `#[rapira(version = N)]` is
119    /// placed on a struct — fields annotated with `#[rapira(since = M)]` are
120    /// only read from the slice when `version >= M`, otherwise
121    /// [`Default::default()`] is used.
122    ///
123    /// # Derive usage
124    ///
125    /// ```rust,ignore
126    /// #[derive(Rapira)]
127    /// #[rapira(version = 2)]
128    /// struct User {
129    ///     name: String,           // present since v1 (no attribute needed)
130    ///     age: u32,               // present since v1
131    ///     #[rapira(since = 2)]
132    ///     email: Option<String>,  // added in v2, defaults to None for v1 data
133    /// }
134    /// ```
135    ///
136    /// # Reading old data
137    ///
138    /// ```rust,ignore
139    /// // version comes from DB metadata, not from the bytes themselves
140    /// let user: User = rapira::deserialize_versioned(&bytes, schema_version)?;
141    /// ```
142    ///
143    /// # Rules
144    ///
145    /// - Serialization (`convert_to_bytes`, `size`) always writes **all** fields
146    ///   (current version). Only deserialization is affected.
147    /// - `from_slice` always reads all fields regardless of version (use for
148    ///   current-version data).
149    /// - Fields with `#[rapira(since = M)]` must implement [`Default`].
150    /// - `since = 0` is invalid (versions start at 1).
151    /// - `since` value must not exceed the struct's `version`.
152    /// - `#[rapira(since)]` requires `#[rapira(version)]` on the struct.
153    /// - `#[rapira(since)]` and `#[rapira(skip)]` cannot be combined.
154    /// - Structs without `#[rapira(version)]` use the default impl (delegates
155    ///   to `from_slice`), so existing code is unaffected.
156    /// - Version is propagated through collections (`Vec<T>`, `Option<T>`,
157    ///   `Box<T>`, `BTreeMap`, tuples, arrays, `SmallVec`, `ArrayVec`).
158    #[inline]
159    fn from_slice_versioned(slice: &mut &[u8], _version: u8) -> Result<Self>
160    where
161        Self: Sized,
162    {
163        Self::from_slice(slice)
164    }
165
166    #[inline]
167    fn try_convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) -> Result<()> {
168        self.convert_to_bytes(slice, cursor);
169        Ok(())
170    }
171
172    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize);
173
174    /// Context-aware serialization. Default: delegates to `convert_to_bytes`.
175    #[inline]
176    fn convert_to_bytes_ctx(&self, slice: &mut [u8], cursor: &mut usize, _flags: RapiraFlags) {
177        self.convert_to_bytes(slice, cursor)
178    }
179
180    /// Context-aware deserialization. Default: delegates to `from_slice`.
181    #[inline]
182    fn from_slice_ctx(slice: &mut &[u8], _flags: RapiraFlags) -> Result<Self>
183    where
184        Self: Sized,
185    {
186        Self::from_slice(slice)
187    }
188
189    /// Context-aware size calculation. Default: delegates to `size`.
190    #[inline]
191    fn size_ctx(&self, _flags: RapiraFlags) -> usize {
192        self.size()
193    }
194}
195
196pub const LEN_SIZE: usize = 4;
197
198#[inline]
199pub fn push(slice: &mut [u8], cursor: &mut usize, item: u8) {
200    let s = slice.get_mut(*cursor).unwrap();
201    *s = item;
202    *cursor += 1;
203}
204
205#[inline]
206pub fn try_push(slice: &mut [u8], cursor: &mut usize, item: u8) -> Result<()> {
207    let s = slice.get_mut(*cursor).ok_or(RapiraError::SliceLen)?;
208    *s = item;
209    *cursor += 1;
210
211    Ok(())
212}
213
214#[inline]
215pub fn extend(slice: &mut [u8], cursor: &mut usize, items: &[u8]) {
216    let end = *cursor + items.len();
217    let s = slice.get_mut(*cursor..end).unwrap();
218    s.copy_from_slice(items);
219    *cursor = end;
220}
221
222#[inline]
223pub fn try_extend(slice: &mut [u8], cursor: &mut usize, items: &[u8]) -> Result<()> {
224    let end = *cursor + items.len();
225    let s = slice.get_mut(*cursor..end).ok_or(RapiraError::SliceLen)?;
226    s.copy_from_slice(items);
227    *cursor = end;
228
229    Ok(())
230}
231
232pub const fn static_size<const N: usize>(arr: [Option<usize>; N]) -> Option<usize> {
233    let mut i = 0;
234    let mut size = 0;
235    while i < arr.len() {
236        let item = arr[i];
237        match item {
238            Some(s) => {
239                size += s;
240            }
241            None => {
242                return None;
243            }
244        }
245        i += 1;
246    }
247    Some(size)
248}
249
250pub const fn enum_size<const N: usize>(arr: [Option<usize>; N]) -> Option<usize> {
251    let mut i = 0;
252    let mut size = 0;
253    let mut is_init = false;
254    while i < arr.len() {
255        let item = arr[i];
256        match item {
257            Some(s) => {
258                if !is_init {
259                    size = s;
260                    is_init = true;
261                } else if s != size {
262                    return None;
263                }
264            }
265            None => {
266                return None;
267            }
268        }
269        i += 1;
270    }
271    Some(size + 1)
272}
273
274pub const fn min_size(arr: &'static [usize]) -> usize {
275    let mut i = 0;
276    let mut size = 0;
277    while i < arr.len() {
278        let item = arr[i];
279        size += item;
280        i += 1;
281    }
282    size
283}
284
285pub const fn enum_min_size(arr: &'static [usize]) -> usize {
286    let mut i = 0;
287    let mut size = 0;
288    let mut is_init = false;
289    while i < arr.len() {
290        let item = arr[i];
291        if !is_init {
292            size = item;
293            is_init = true;
294        } else if size < item {
295            size = item;
296        }
297        i += 1;
298    }
299    size + 1
300}