Skip to main content

ic_wire/bounded/
mod.rs

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