Skip to content

Commit

Permalink
feat(serde): proper serde and json schema support (#29)
Browse files Browse the repository at this point in the history
* draft serde fixes

* proper serde support

* not sure what root cause of this fail, just added type
  • Loading branch information
dzmitry-lahoda authored Jan 17, 2025
1 parent 7a0a1aa commit ad03a20
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 4 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ serde = { version = "1.0.123", default-features = false, features = [
"alloc",
"derive",
], optional = true }
schemars = { version = ">=0.8,<1", default-features = false, optional = true }
thiserror = { version = "2", default-features = false }
proptest = { version = "1.0.0", optional = true }

[features]
serde = ["dep:serde"]
schema = ["serde", "dep:schemars"]
arbitrary = ["proptest"]

[dev-dependencies]
Expand Down
76 changes: 72 additions & 4 deletions src/bounded_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ use alloc::vec;
use alloc::vec::Vec;
use core::convert::{TryFrom, TryInto};
use core::slice::{Iter, IterMut};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use thiserror::Error;

/// Non-empty Vec bounded with minimal (L - lower bound) and maximal (U - upper bound) items quantity
#[derive(PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
pub struct BoundedVec<T, const L: usize, const U: usize>
// enable when feature(const_evaluatable_checked) is stable
// where
Expand Down Expand Up @@ -335,7 +332,7 @@ impl<T, const L: usize, const U: usize> BoundedVec<T, L, U> {
///
/// let opt_bv_none = BoundedVec::<u8, 2, 8>::opt_empty_vec(vec![]).unwrap();
/// assert!(opt_bv_none.is_none());
/// assert_eq!(opt_bv_none.to_vec(), vec![]);
/// assert_eq!(opt_bv_none.to_vec(), Vec::<u8>::new());
/// let opt_bv_some = BoundedVec::<u8, 2, 8>::opt_empty_vec(vec![0u8, 2]).unwrap();
/// assert!(opt_bv_some.is_some());
/// assert_eq!(opt_bv_some.to_vec(), vec![0u8, 2]);
Expand Down Expand Up @@ -461,6 +458,77 @@ mod arbitrary {
}
}

#[cfg(feature = "serde")]
mod serde_impl {
use super::*;
use serde::{Deserialize, Serialize};

// direct impl to unify serde in one place instead of doing attribute on declaration and deserialize here
impl<T: Serialize, const L: usize, const U: usize> Serialize for BoundedVec<T, L, U> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.inner.serialize(serializer)
}
}

impl<'de, T: Deserialize<'de>, const L: usize, const U: usize> Deserialize<'de>
for BoundedVec<T, L, U>
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let inner = Vec::<T>::deserialize(deserializer)?;
if inner.len() < L {
return Err(serde::de::Error::custom(alloc::format!(
"Lower bound violation: got {} (expected >= {})",
inner.len(),
L
)));
} else if inner.len() > U {
return Err(serde::de::Error::custom(alloc::format!(
"Upper bound violation: got {} (expected <= {})",
inner.len(),
U
)));
};
Ok(BoundedVec { inner })
}
}

#[cfg(feature = "schema")]
mod schema {
use super::*;
use schemars::schema::{InstanceType, SchemaObject};
use schemars::JsonSchema;

// we cannot use attributes, because the do not work with `const`, only numeric literals supported
impl<T: JsonSchema, const L: usize, const U: usize> JsonSchema for BoundedVec<T, L, U> {
fn schema_name() -> alloc::string::String {
alloc::format!("BoundedVec{}Min{}Max{}", T::schema_name(), L, U)
}

fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
SchemaObject {
instance_type: Some(InstanceType::Array.into()),
array: Some(alloc::boxed::Box::new(schemars::schema::ArrayValidation {
items: Some(schemars::schema::SingleOrVec::Single(
T::json_schema(gen).into(),
)),
min_items: Some(L as u32),
max_items: Some(U as u32),
..Default::default()
})),
..Default::default()
}
.into()
}
}
}
}

#[allow(clippy::unwrap_used)]
#[cfg(test)]
mod tests {
Expand Down

0 comments on commit ad03a20

Please sign in to comment.