From b0adad98489ee19dfa6cd4f2306dad0e8313d287 Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Thu, 8 May 2025 20:17:25 +0100 Subject: [PATCH] feat: compile time range validation sanitation to docs clippy docs on usage of prove --- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- README.md | 2 ++ src/bounded_vec.rs | 64 ++++++++++++++++++++++++++++------------------ 4 files changed, 44 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de2e16b..9185acc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +- prevent out of bound construction at compile time + ## [0.7.1] - 2022-08-01 ### Added - fix `Abrbitrary` impl to honor upper(U) and lower(L) bounds; diff --git a/Cargo.toml b/Cargo.toml index aca58e4..5ac0615 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "bounded-vec" version = "0.7.1" license = "CC0-1.0" authors = ["Denys Zadorozhnyi "] -edition = "2018" +edition = "2021" description = "Non-empty rust Vec wrapper with type guarantees on lower and upper bounds for items quantity." repository = "https://github.com/ergoplatform/bounded-vec" diff --git a/README.md b/README.md index b6731a9..42a27c2 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ ## bounded-vec `BoundedVec` - Non-empty rust `std::vec::Vec` wrapper with type guarantees on lower(`L`) and upper(`U`) bounds for items quantity. Inspired by [vec1](https://github.com/rustonaut/vec1). +This crate is `#![no_std]` compatible with `alloc`. ## Example @@ -21,6 +22,7 @@ assert_eq!(data, [2u8,4].into()); ## Crate features - optional(non-default) `serde` feature that adds serialization to `BoundedVec`. +- optional(non-default) `schema` feature that adds JSON schema support via `schemars` (requires `serde`). - optional(non-default) `arbitrary` feature that adds `proptest::Arbitrary` implementation to `BoundedVec`. ## Changelog diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index 9a31aad..436d096 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -6,20 +6,10 @@ 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)] -pub struct BoundedVec -// enable when feature(const_evaluatable_checked) is stable -// where -// Assert<{ L > 0 }>: IsTrue, -{ +pub struct BoundedVec { inner: Vec, } -// enum Assert {} - -// trait IsTrue {} - -// impl IsTrue for Assert {} - /// BoundedVec errors #[derive(Error, PartialEq, Eq, Debug, Clone)] pub enum BoundedVecOutOfBounds { @@ -41,18 +31,42 @@ pub enum BoundedVecOutOfBounds { }, } +/// Compile-time proof of valid bounds. Must be consturcted with same bounds to instantiate `BoundedVec`. +#[derive(Clone, Copy)] +pub struct Proof; + +/// Type a compile-time proof of valid bounds +pub const fn prove() -> Proof { + if L == 0 { + panic!("L must be greater than 0") + } + if L > U { + panic!("L must be less than or equal to U") + } + + Proof:: +} + impl BoundedVec { /// Creates new BoundedVec or returns error if items count is out of bounds /// + /// # Parameters + /// + /// * `items` - vector of items within bounds + /// * `proof` - compile-time proof of valid bounds create via `prove::()` function call + /// + /// # Errors + /// + /// * `LowerBoundError` - if `items`` len is less than L (lower bound) + /// * `UpperBoundError` - if `items`` len is more than U (upper bound) + /// /// # Example /// ``` /// use bounded_vec::BoundedVec; - /// let data: BoundedVec<_, 2, 8> = BoundedVec::from_vec(vec![1u8, 2]).unwrap(); + /// use bounded_vec::prove; + /// let data: BoundedVec<_, 2, 8> = BoundedVec::from_vec(vec![1u8, 2], prove::<2, 8>()).unwrap(); /// ``` - pub fn from_vec(items: Vec) -> Result { - // remove when feature(const_evaluatable_checked) is stable - // and this requirement is encoded in type sig - assert!(L > 0); + pub fn from_vec(items: Vec, _: Proof) -> Result { let len = items.len(); if len < L { Err(BoundedVecOutOfBounds::LowerBoundError { @@ -248,7 +262,7 @@ impl BoundedVec { out.push(map_fn(element)?); } #[allow(clippy::unwrap_used)] - Ok(BoundedVec::from_vec(out).unwrap()) + Ok(BoundedVec::from_vec(out, prove::()).unwrap()) } /// Create a new `BoundedVec` by mapping references of `self` elements @@ -280,7 +294,7 @@ impl BoundedVec { out.push(map_fn(element)?); } #[allow(clippy::unwrap_used)] - Ok(BoundedVec::from_vec(out).unwrap()) + Ok(BoundedVec::from_vec(out, prove::()).unwrap()) } /// Returns a reference for an element at index or `None` if out of bounds @@ -341,7 +355,7 @@ impl BoundedVec { if v.is_empty() { Ok(None) } else { - Ok(Some(BoundedVec::from_vec(v)?)) + Ok(Some(BoundedVec::from_vec(v, prove::())?)) } } } @@ -353,7 +367,7 @@ impl TryFrom> for BoundedVec type Error = BoundedVecOutOfBounds; fn try_from(value: Vec) -> Result { - BoundedVec::from_vec(value) + BoundedVec::from_vec(value, prove::()) } } @@ -452,7 +466,7 @@ mod arbitrary { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { vec(any::(), L..=U) - .prop_map(|items| BoundedVec::from_vec(items).unwrap()) + .prop_map(|items| BoundedVec::from_vec(items, prove::()).unwrap()) .boxed() } } @@ -538,10 +552,10 @@ mod tests { #[test] fn from_vec() { - assert!(BoundedVec::::from_vec(vec![1, 2]).is_ok()); - assert!(BoundedVec::::from_vec(vec![]).is_err()); - assert!(BoundedVec::::from_vec(vec![1, 2]).is_err()); - assert!(BoundedVec::::from_vec(vec![1, 2, 3]).is_err()); + assert!(BoundedVec::::from_vec(vec![1, 2], prove::<2, 8>()).is_ok()); + assert!(BoundedVec::::from_vec(vec![], prove::<2, 8>()).is_err()); + assert!(BoundedVec::::from_vec(vec![1, 2], prove::<3, 8>()).is_err()); + assert!(BoundedVec::::from_vec(vec![1, 2, 3], prove::<1, 2>()).is_err()); } #[test]