Skip to main content

xdr_common/
vecm.rs

1#![allow(unused_imports)]
2#![allow(dead_code)]
3
4use crate::cfg::*;
5pub use crate::error::*;
6
7use core::fmt;
8use core::marker::PhantomData;
9use core::ops::Deref;
10
11/// A vector with a maximum length constraint.
12///
13/// `VecM<T, MAX>` is a wrapper around `Vec<T>` that enforces a maximum length
14/// of `MAX` elements. Attempting to create a `VecM` from a `Vec` that exceeds
15/// this length will result in an [`Error::LengthExceedsMax`].
16///
17/// This type corresponds to XDR variable-length arrays as defined in
18/// [RFC 4506 Section 4.13](https://www.rfc-editor.org/rfc/rfc4506#section-4.13):
19/// ```text
20/// type-name identifier<m>;
21/// type-name identifier<>;
22/// ```
23///
24/// # Feature Variants
25///
26/// - **With `alloc` feature**: `Vec<T>` is heap-allocated, supporting dynamic data
27/// - **Without `alloc` feature**: `Vec<T>` is `&'static [T]`, for static data only (requires `T: 'static`)
28///
29/// # Type Parameters
30///
31/// - `T` - The element type
32/// - `MAX` - Maximum number of elements (defaults to `u32::MAX`)
33///
34/// # Schema note — `VecM<u8>` vs `BytesM`
35///
36/// `VecM<u8, MAX>` and `BytesM<MAX>` share the same XDR wire format
37/// (variable-length opaque) and the same `xdr-repr` metadata, but their
38/// `schemars` JSON schemas differ: `BytesM` advertises a hex-encoded string
39/// while `VecM<u8>` advertises a JSON array of integers. This is intentional —
40/// each schema faithfully describes its own serde JSON shape. If you need the
41/// hex-string schema, use `BytesM` directly.
42///
43/// The same applies to `VecM<Vec<u8>, MAX>`: the inner `Vec<u8>` element
44/// schema is the standard schemars array-of-integers, not the XDR opaque
45/// representation. Prefer `VecM<BytesM<INNER>, MAX>` when the JSON schema
46/// must reflect opaque byte semantics.
47///
48/// # Examples
49///
50/// ```ignore
51/// use xdr_common::VecM;
52///
53/// // Create a vector with max 5 elements (e.g., "int values<5>")
54/// let v: VecM<u32, 5> = vec![1, 2, 3].try_into()?;
55/// assert_eq!(v.len(), 3);
56/// assert_eq!(v.max_len(), 5);
57///
58/// // This would fail because it exceeds MAX
59/// let too_long: Result<VecM<u32, 5>, _> = vec![1, 2, 3, 4, 5, 6].try_into();
60/// assert!(too_long.is_err());
61/// ```
62#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
63#[allow(private_bounds)]
64pub struct VecM<T, const MAX: u32 = { u32::MAX }>(pub Vec<T>)
65where
66    T: StaticIfNoAlloc;
67
68/// Helper trait bound on [`VecM`] elements that requires `'static` only
69/// in no-`alloc` builds.
70///
71/// With feature `alloc` this is implemented for all types; without it,
72/// only for `'static` types. It is not meant to be implemented manually.
73#[cfg(feature = "alloc")]
74pub trait StaticIfNoAlloc {}
75#[cfg(feature = "alloc")]
76impl<T> StaticIfNoAlloc for T {}
77
78/// Helper trait bound on [`VecM`] elements that requires `'static` only
79/// in no-`alloc` builds.
80///
81/// With feature `alloc` this is implemented for all types; without it,
82/// only for `'static` types. It is not meant to be implemented manually.
83#[cfg(not(feature = "alloc"))]
84pub trait StaticIfNoAlloc: 'static {}
85#[cfg(not(feature = "alloc"))]
86impl<T: 'static> StaticIfNoAlloc for T {}
87
88#[cfg(all(feature = "serde", feature = "alloc"))]
89#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", feature = "alloc"))))]
90impl<T, const MAX: u32> Serialize for VecM<T, MAX>
91where
92    T: Serialize,
93{
94    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
95    where
96        S: Serializer,
97    {
98        if self.0.len() > Self::MAX_LEN {
99            return Err(<S::Error as SerError>::custom(Error::LengthExceedsMax));
100        }
101        self.0.serialize(ser)
102    }
103}
104
105#[cfg(all(feature = "serde", feature = "alloc"))]
106#[cfg_attr(docsrs, doc(cfg(all(feature = "serde", feature = "alloc"))))]
107impl<'de, T, const MAX: u32> Deserialize<'de> for VecM<T, MAX>
108where
109    T: Deserialize<'de>,
110{
111    fn deserialize<D>(de: D) -> Result<Self, D::Error>
112    where
113        D: Deserializer<'de>,
114    {
115        struct VecMVisitor<T, const MAX: u32>(PhantomData<T>);
116
117        impl<'de, T, const MAX: u32> Visitor<'de> for VecMVisitor<T, MAX>
118        where
119            T: Deserialize<'de>,
120        {
121            type Value = VecM<T, MAX>;
122
123            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
124                write!(f, "a sequence with length at most {}", MAX)
125            }
126
127            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
128            where
129                A: SeqAccess<'de>,
130            {
131                let mut buf = Vec::new();
132                if let Some(len) = seq.size_hint() {
133                    if len > VecM::<T, MAX>::MAX_LEN {
134                        return Err(<A::Error as DeError>::invalid_length(len, &self));
135                    }
136                    let cap = len.min(crate::MAX_PREALLOC_BYTES / core::mem::size_of::<T>().max(1));
137                    buf.reserve(cap);
138                }
139
140                while buf.len() < VecM::<T, MAX>::MAX_LEN {
141                    match seq.next_element::<T>()? {
142                        Some(value) => buf.push(value),
143                        None => return Ok(VecM(buf)),
144                    }
145                }
146                if seq.next_element::<IgnoredAny>()?.is_some() {
147                    return Err(match buf.len().checked_add(1) {
148                        Some(overlimit) => <A::Error as DeError>::invalid_length(overlimit, &self),
149                        None => DeError::custom(format_args!(
150                            "sequence length exceeds maximum of {MAX}"
151                        )),
152                    });
153                }
154                Ok(VecM(buf))
155            }
156        }
157
158        de.deserialize_seq(VecMVisitor::<T, MAX>(PhantomData))
159    }
160}
161
162#[cfg(feature = "schemars")]
163#[cfg_attr(docsrs, doc(cfg(feature = "schemars")))]
164impl<T: schemars::JsonSchema, const MAX: u32> schemars::JsonSchema for VecM<T, MAX> {
165    fn schema_name() -> alloc::borrow::Cow<'static, str> {
166        alloc::format!("VecM<{}, {}>", T::schema_name(), MAX).into()
167    }
168
169    fn schema_id() -> alloc::borrow::Cow<'static, str> {
170        alloc::format!("xdr_common::VecM<{}, {}>", T::schema_id(), MAX).into()
171    }
172
173    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
174        let items = generator.subschema_for::<T>();
175        schemars::json_schema!({
176            "type": "array",
177            "items": items,
178            "maxItems": MAX,
179        })
180    }
181}
182
183impl<T: StaticIfNoAlloc, const MAX: u32> Deref for VecM<T, MAX> {
184    type Target = Vec<T>;
185
186    fn deref(&self) -> &Self::Target {
187        &self.0
188    }
189}
190
191impl<T: StaticIfNoAlloc, const MAX: u32> Default for VecM<T, MAX> {
192    fn default() -> Self {
193        Self(Vec::default())
194    }
195}
196
197#[allow(private_bounds)]
198impl<T: StaticIfNoAlloc, const MAX: u32> VecM<T, MAX> {
199    /// The maximum length as a compile-time constant.
200    pub const MAX_LEN: usize = { MAX as usize };
201
202    /// Returns the maximum allowed length for this vector.
203    #[must_use]
204    #[allow(clippy::unused_self)]
205    pub fn max_len(&self) -> usize {
206        Self::MAX_LEN
207    }
208
209    /// Returns a reference to the underlying vector.
210    #[must_use]
211    pub fn as_vec(&self) -> &Vec<T> {
212        self.as_ref()
213    }
214
215    /// Returns the elements as a slice.
216    ///
217    /// Usable in `const` contexts.
218    #[must_use]
219    pub const fn as_slice(&self) -> &[T] {
220        #[cfg(feature = "alloc")]
221        let s: &[T] = self.0.as_slice();
222        #[cfg(not(feature = "alloc"))]
223        let s: &[T] = self.0;
224        s
225    }
226}
227
228#[allow(private_bounds)]
229impl<T: Clone + StaticIfNoAlloc, const MAX: u32> VecM<T, MAX> {
230    /// Clones the vector and returns it as a standard `Vec<T>`.
231    ///
232    /// Requires the `alloc` feature.
233    #[must_use]
234    #[cfg(feature = "alloc")]
235    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
236    pub fn to_vec(&self) -> Vec<T> {
237        self.into()
238    }
239
240    /// Consumes `self` and returns the underlying vector.
241    #[must_use]
242    pub fn into_vec(self) -> Vec<T> {
243        self.into()
244    }
245}
246
247#[allow(private_bounds)]
248impl<T: Clone + StaticIfNoAlloc> VecM<T, 1> {
249    /// Converts a `VecM<T, 1>` to `Option<T>`.
250    ///
251    /// Returns `Some(T)` if the vector contains one element, `None` otherwise.
252    /// This is useful for optional XDR fields.
253    #[must_use]
254    pub fn to_option(&self) -> Option<T> {
255        self.0.first().cloned()
256    }
257}
258
259#[cfg(not(feature = "alloc"))]
260#[cfg_attr(docsrs, doc(cfg(not(feature = "alloc"))))]
261impl<T: Clone + 'static> From<VecM<T, 1>> for Option<T> {
262    fn from(v: VecM<T, 1>) -> Self {
263        v.to_option()
264    }
265}
266
267#[cfg(feature = "alloc")]
268impl<T> VecM<T, 1> {
269    /// Consumes `self` and converts it to `Option<T>`.
270    ///
271    /// Returns `Some(T)` if the vector contains one element, `None` otherwise.
272    /// This is useful for optional XDR fields.
273    ///
274    /// Requires the `alloc` feature.
275    #[must_use]
276    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
277    pub fn into_option(mut self) -> Option<T> {
278        self.0.drain(..).next()
279    }
280}
281
282#[cfg(feature = "alloc")]
283#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
284impl<T> From<VecM<T, 1>> for Option<T> {
285    fn from(v: VecM<T, 1>) -> Self {
286        v.into_option()
287    }
288}
289
290impl<T: StaticIfNoAlloc, const MAX: u32> TryFrom<Vec<T>> for VecM<T, MAX> {
291    type Error = Error;
292
293    fn try_from(v: Vec<T>) -> Result<Self, Error> {
294        let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
295        if len <= MAX {
296            Ok(VecM(v))
297        } else {
298            Err(Error::LengthExceedsMax)
299        }
300    }
301}
302
303impl<T: StaticIfNoAlloc, const MAX: u32> From<VecM<T, MAX>> for Vec<T> {
304    fn from(v: VecM<T, MAX>) -> Self {
305        v.0
306    }
307}
308
309#[cfg(feature = "alloc")]
310#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
311impl<T: Clone, const MAX: u32> From<&VecM<T, MAX>> for Vec<T> {
312    fn from(v: &VecM<T, MAX>) -> Self {
313        v.0.clone()
314    }
315}
316
317impl<T: StaticIfNoAlloc, const MAX: u32> AsRef<Vec<T>> for VecM<T, MAX> {
318    fn as_ref(&self) -> &Vec<T> {
319        &self.0
320    }
321}
322
323#[cfg(feature = "alloc")]
324#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
325impl<T: Clone, const MAX: u32> TryFrom<&Vec<T>> for VecM<T, MAX> {
326    type Error = Error;
327
328    fn try_from(v: &Vec<T>) -> Result<Self, Error> {
329        v.as_slice().try_into()
330    }
331}
332
333#[cfg(feature = "alloc")]
334#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
335impl<T: Clone, const MAX: u32> TryFrom<&[T]> for VecM<T, MAX> {
336    type Error = Error;
337
338    fn try_from(v: &[T]) -> Result<Self, Error> {
339        let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
340        if len <= MAX {
341            Ok(VecM(v.to_vec()))
342        } else {
343            Err(Error::LengthExceedsMax)
344        }
345    }
346}
347
348impl<T: StaticIfNoAlloc, const MAX: u32> AsRef<[T]> for VecM<T, MAX> {
349    #[cfg(feature = "alloc")]
350    fn as_ref(&self) -> &[T] {
351        self.0.as_ref()
352    }
353    #[cfg(not(feature = "alloc"))]
354    fn as_ref(&self) -> &[T] {
355        self.0
356    }
357}
358
359#[cfg(feature = "alloc")]
360#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
361impl<T: Clone, const N: usize, const MAX: u32> TryFrom<[T; N]> for VecM<T, MAX> {
362    type Error = Error;
363
364    fn try_from(v: [T; N]) -> Result<Self, Error> {
365        let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
366        if len <= MAX {
367            Ok(VecM(v.to_vec()))
368        } else {
369            Err(Error::LengthExceedsMax)
370        }
371    }
372}
373
374#[cfg(feature = "alloc")]
375#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
376impl<T: Clone, const N: usize, const MAX: u32> TryFrom<VecM<T, MAX>> for [T; N] {
377    type Error = VecM<T, MAX>;
378
379    fn try_from(v: VecM<T, MAX>) -> core::result::Result<Self, Self::Error> {
380        let s: [T; N] = v.0.try_into().map_err(|v: Vec<T>| VecM::<T, MAX>(v))?;
381        Ok(s)
382    }
383}
384
385#[cfg(feature = "alloc")]
386#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
387impl<T: Clone, const N: usize, const MAX: u32> TryFrom<&[T; N]> for VecM<T, MAX> {
388    type Error = Error;
389
390    fn try_from(v: &[T; N]) -> Result<Self, Error> {
391        let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
392        if len <= MAX {
393            Ok(VecM(v.to_vec()))
394        } else {
395            Err(Error::LengthExceedsMax)
396        }
397    }
398}
399
400#[cfg(not(feature = "alloc"))]
401#[cfg_attr(docsrs, doc(cfg(not(feature = "alloc"))))]
402impl<T: Clone, const N: usize, const MAX: u32> TryFrom<&'static [T; N]> for VecM<T, MAX> {
403    type Error = Error;
404
405    fn try_from(v: &'static [T; N]) -> Result<Self, Error> {
406        let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
407        if len <= MAX {
408            Ok(VecM(v))
409        } else {
410            Err(Error::LengthExceedsMax)
411        }
412    }
413}
414
415#[cfg(feature = "alloc")]
416#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
417impl<const MAX: u32> TryFrom<&String> for VecM<u8, MAX> {
418    type Error = Error;
419
420    fn try_from(v: &String) -> Result<Self, Error> {
421        let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
422        if len <= MAX {
423            Ok(VecM(v.as_bytes().to_vec()))
424        } else {
425            Err(Error::LengthExceedsMax)
426        }
427    }
428}
429
430#[cfg(feature = "alloc")]
431#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
432impl<const MAX: u32> TryFrom<String> for VecM<u8, MAX> {
433    type Error = Error;
434
435    fn try_from(v: String) -> Result<Self, Error> {
436        let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
437        if len <= MAX {
438            Ok(VecM(v.into()))
439        } else {
440            Err(Error::LengthExceedsMax)
441        }
442    }
443}
444
445#[cfg(feature = "alloc")]
446#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
447impl<const MAX: u32> TryFrom<&str> for VecM<u8, MAX> {
448    type Error = Error;
449
450    fn try_from(v: &str) -> Result<Self, Error> {
451        let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
452        if len <= MAX {
453            Ok(VecM(v.into()))
454        } else {
455            Err(Error::LengthExceedsMax)
456        }
457    }
458}
459
460#[cfg(not(feature = "alloc"))]
461#[cfg_attr(docsrs, doc(cfg(not(feature = "alloc"))))]
462impl<const MAX: u32> TryFrom<&'static str> for VecM<u8, MAX> {
463    type Error = Error;
464
465    fn try_from(v: &'static str) -> Result<Self, Error> {
466        let len: u32 = v.len().try_into().map_err(|_| Error::LengthExceedsMax)?;
467        if len <= MAX {
468            Ok(VecM(v.as_bytes()))
469        } else {
470            Err(Error::LengthExceedsMax)
471        }
472    }
473}
474
475#[cfg(test)]
476mod tests {
477    use super::*;
478
479    #[cfg(feature = "alloc")]
480    extern crate alloc;
481    #[cfg(feature = "alloc")]
482    use alloc::vec;
483
484    #[test]
485    #[cfg(feature = "alloc")]
486    fn test_as_slice_const() {
487        // Compile check that as_slice is const-callable. With alloc, VecM
488        // values cannot be used inside const eval (Vec's destructor is not
489        // const), so the const fn is exercised at runtime.
490        const fn as_slice_in_const<const M: u32>(v: &VecM<u32, M>) -> &[u32] {
491            v.as_slice()
492        }
493        let v: VecM<u32, 3> = vec![1, 2].try_into().unwrap();
494        assert_eq!(as_slice_in_const(&v), &[1, 2]);
495    }
496
497    #[test]
498    #[cfg(not(feature = "alloc"))]
499    fn test_as_slice_const() {
500        const V: VecM<u32, 3> = VecM(&[1, 2]);
501        const SLICE: &[u32] = V.as_slice();
502        assert_eq!(SLICE, &[1, 2]);
503    }
504
505    #[test]
506    #[cfg(feature = "alloc")]
507    fn test_vecm_alloc_basic() {
508        // Test basic creation with alloc feature
509        let v: VecM<u32, 10> = vec![1, 2, 3].try_into().unwrap();
510        assert_eq!(v.len(), 3);
511        assert_eq!(v.max_len(), 10);
512        assert_eq!(v[0], 1);
513
514        // Test length exceeds max
515        let too_long: Result<VecM<u32, 5>, _> = vec![1, 2, 3, 4, 5, 6].try_into();
516        assert!(too_long.is_err());
517    }
518
519    #[test]
520    #[cfg(all(feature = "serde", feature = "alloc"))]
521    fn test_vecm_serde_deser_rejects_overlimit() {
522        let ok: VecM<u32, 3> = serde_json::from_str("[1,2,3]").unwrap();
523        assert_eq!(ok.as_vec(), &vec![1, 2, 3]);
524
525        let under: VecM<u32, 5> = serde_json::from_str("[1]").unwrap();
526        assert_eq!(under.as_vec(), &vec![1]);
527
528        let empty: VecM<u32, 0> = serde_json::from_str("[]").unwrap();
529        assert!(empty.is_empty());
530
531        let err = serde_json::from_str::<VecM<u32, 2>>("[1,2,3]").unwrap_err();
532        let msg = alloc::format!("{err}");
533        assert!(msg.contains("length at most 2"), "{msg}");
534
535        let err = serde_json::from_str::<VecM<u32, 0>>("[1]").unwrap_err();
536        let msg = alloc::format!("{err}");
537        assert!(msg.contains("length at most 0"), "{msg}");
538    }
539
540    #[test]
541    #[cfg(all(feature = "serde", feature = "alloc"))]
542    fn test_vecm_serde_serialize_rejects_overlimit() {
543        let value = VecM::<u32, 1>(vec![1, 2]);
544        let err = serde_json::to_value(&value).unwrap_err();
545        let msg = alloc::format!("{err}");
546        assert!(msg.contains("max length exceeded"), "{msg}");
547    }
548
549    #[test]
550    #[cfg(all(feature = "serde", feature = "alloc"))]
551    fn test_vecm_serde_serialize_accepts_at_max() {
552        let value = VecM::<u32, 2>(vec![1, 2]);
553        let json = serde_json::to_value(&value).unwrap();
554        assert_eq!(json, serde_json::json!([1, 2]));
555    }
556
557    #[test]
558    #[cfg(not(feature = "alloc"))]
559    fn test_vecm_noalloc_basic() {
560        // Test basic creation without alloc feature (static data only)
561        static DATA: [u32; 3] = [1, 2, 3];
562        let v: VecM<u32, 10> = (&DATA).try_into().unwrap();
563        assert_eq!(v.len(), 3);
564        assert_eq!(v.max_len(), 10);
565        assert_eq!(v[0], 1);
566
567        // Test length exceeds max
568        static LONG_DATA: [u32; 6] = [1, 2, 3, 4, 5, 6];
569        let too_long: Result<VecM<u32, 5>, _> = (&LONG_DATA).try_into();
570        assert!(too_long.is_err());
571    }
572}