Skip to main content

ic_wire/bounded/
mod.rs

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