Skip to main content

ic_wire/bounded/
mod.rs

1mod borrowed;
2mod packed_i16;
3pub use borrowed::{BytesRef, TextRef};
4pub use packed_i16::PackedI16;
5
6use crate::Error;
7use alloc::{string::String, vec::Vec};
8use core::fmt;
9use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
10
11/// A sequence with a compile-time element bound.
12#[derive(Clone, Debug, Eq, PartialEq)]
13pub struct List<T, const N: usize>(Vec<T>);
14impl<T, const N: usize> List<T, N> {
15    /// Admit an owned sequence without copying.
16    pub fn new(values: Vec<T>) -> Result<Self, Error> {
17        if values.len() > N {
18            Err(Error::Limit)
19        } else {
20            Ok(Self(values))
21        }
22    }
23    /// Borrow the elements.
24    pub fn as_slice(&self) -> &[T] {
25        &self.0
26    }
27    /// Recover the admitted sequence.
28    pub fn into_vec(self) -> Vec<T> {
29        self.0
30    }
31}
32impl<T: Serialize, const N: usize> Serialize for List<T, N> {
33    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
34        self.0.serialize(s)
35    }
36}
37impl<'de, T: Deserialize<'de>, const N: usize> Deserialize<'de> for List<T, N> {
38    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
39        d.deserialize_seq(ListVisitor::<T, N>(core::marker::PhantomData))
40            .map(Self)
41    }
42}
43
44struct ListVisitor<T, const N: usize>(core::marker::PhantomData<T>);
45impl<'de, T: Deserialize<'de>, const N: usize> de::Visitor<'de> for ListVisitor<T, N> {
46    type Value = Vec<T>;
47    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        f.write_str("bounded sequence")
49    }
50    fn visit_seq<A: de::SeqAccess<'de>>(self, seq: A) -> Result<Self::Value, A::Error> {
51        // Constant bounds preserve loop elimination for tiny/zero-sized lists.
52        // Other lists share growth code across bounds for each element type.
53        // Both paths use one algorithm and identical visited sizes/checks:
54        // bytes, element-decoder order and budget charges are unchanged.
55        if size_of::<T>() == 0 || N <= 1 {
56            read_list(seq, N)
57        } else {
58            read_list_shared(seq, N)
59        }
60    }
61}
62
63#[inline(never)]
64fn read_list_shared<'de, T: Deserialize<'de>, A: de::SeqAccess<'de>>(
65    seq: A,
66    limit: usize,
67) -> Result<Vec<T>, A::Error> {
68    read_list(seq, limit)
69}
70
71#[inline(always)]
72fn read_list<'de, T: Deserialize<'de>, A: de::SeqAccess<'de>>(
73    mut seq: A,
74    limit: usize,
75) -> Result<Vec<T>, A::Error> {
76    // Postcard hides its count when remaining bytes are fewer than
77    // elements (including legitimate unit values). Bound both paths.
78    let count = seq.size_hint();
79    if count.is_some_and(|count| count > limit) {
80        return Err(de::Error::custom("element limit"));
81    }
82    let mut values = Vec::new();
83    values
84        .try_reserve_exact(count.unwrap_or(0))
85        .map_err(|_| de::Error::custom("allocation"))?;
86    for _ in 0..limit {
87        let Some(value) = seq.next_element()? else {
88            return Ok(values);
89        };
90        if values.len() == values.capacity() {
91            // Hidden lengths need amortized growth, capped by the
92            // declared element bound rather than Vec's growth policy.
93            let next = values.len().saturating_mul(2).max(1).min(limit);
94            values
95                .try_reserve_exact(next - values.len())
96                .map_err(|_| de::Error::custom("allocation"))?;
97        }
98        values.push(value);
99    }
100    // Detect an excess element without invoking its decoder.
101    struct Reject;
102    impl<'de> de::DeserializeSeed<'de> for Reject {
103        type Value = ();
104        fn deserialize<D: Deserializer<'de>>(self, _: D) -> Result<(), D::Error> {
105            Err(de::Error::custom("element limit"))
106        }
107    }
108    seq.next_element_seed(Reject)?;
109    Ok(values)
110}
111
112/// Bounded opaque bytes; consumers validate principal bytes using their existing
113/// principal type. IC Wire defines no new identity type.
114#[derive(Clone, Debug, Eq, PartialEq)]
115pub struct Bytes<const N: usize>(Vec<u8>);
116impl<const N: usize> Bytes<N> {
117    /// Admit bytes without copying.
118    pub fn new(bytes: Vec<u8>) -> Result<Self, Error> {
119        if bytes.len() > N {
120            Err(Error::Limit)
121        } else {
122            Ok(Self(bytes))
123        }
124    }
125    /// Borrow the bytes.
126    pub fn as_slice(&self) -> &[u8] {
127        &self.0
128    }
129    /// Borrow this bounded value without copying or repeating admission.
130    pub fn as_borrowed(&self) -> BytesRef<'_, N> {
131        BytesRef::from_owned(self)
132    }
133    /// Recover the admitted bytes without copying or allocating.
134    pub fn into_vec(self) -> Vec<u8> {
135        self.0
136    }
137}
138impl<const N: usize> Serialize for Bytes<N> {
139    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
140        s.serialize_bytes(&self.0)
141    }
142}
143impl<'de, const N: usize> Deserialize<'de> for Bytes<N> {
144    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
145        d.deserialize_bytes(BytesVisitor { limit: N }).map(Self)
146    }
147}
148
149// Erase the field bound from the allocation/copy body, as for PackedI16.
150// Vec<u8> and Bytes<N> have the same visited size. Check the same limit before
151// allocation; bytes, traversal charges and malformed-input errors stay unchanged.
152struct BytesVisitor {
153    limit: usize,
154}
155impl de::Visitor<'_> for BytesVisitor {
156    type Value = Vec<u8>;
157    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        f.write_str("bounded bytes")
159    }
160    #[inline(never)]
161    fn visit_bytes<E: de::Error>(self, bytes: &[u8]) -> Result<Self::Value, E> {
162        if bytes.len() > self.limit {
163            return Err(E::custom("byte limit"));
164        }
165        let mut out = Vec::new();
166        out.try_reserve_exact(bytes.len())
167            .map_err(|_| E::custom("allocation"))?;
168        out.extend_from_slice(bytes);
169        Ok(out)
170    }
171}
172
173/// UTF-8 text bounded in bytes.
174#[derive(Clone, Debug, Eq, PartialEq)]
175pub struct Text<const N: usize>(String);
176impl<const N: usize> Text<N> {
177    /// Admit owned text without copying.
178    pub fn new(text: String) -> Result<Self, Error> {
179        if text.len() > N {
180            Err(Error::Limit)
181        } else {
182            Ok(Self(text))
183        }
184    }
185    /// Borrow the text.
186    pub fn as_str(&self) -> &str {
187        &self.0
188    }
189    /// Borrow this bounded value without copying or repeating admission.
190    pub fn as_borrowed(&self) -> TextRef<'_, N> {
191        TextRef::from_owned(self)
192    }
193    /// Recover the admitted text without copying or allocating.
194    pub fn into_string(self) -> String {
195        self.0
196    }
197}
198impl<const N: usize> Serialize for Text<N> {
199    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
200        s.serialize_str(&self.0)
201    }
202}
203impl<'de, const N: usize> Deserialize<'de> for Text<N> {
204    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
205        let bytes = Bytes::<N>::deserialize(d)?;
206        let text = String::from_utf8(bytes.0).map_err(|_| de::Error::custom("invalid UTF-8"))?;
207        Ok(Self(text))
208    }
209}