arkhe_forge_core/
component.rs1use arkhe_kernel::abi::TypeCode;
13use arrayvec::ArrayString;
14use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
15
16pub trait ArkheComponent:
22 crate::__sealed::__Sealed + Serialize + for<'de> Deserialize<'de> + 'static
23{
24 const TYPE_CODE: u32;
26
27 const SCHEMA_VERSION: u16;
30
31 fn type_code() -> TypeCode {
33 TypeCode(Self::TYPE_CODE)
34 }
35
36 fn approx_size(&self) -> usize {
39 core::mem::size_of::<Self>()
40 }
41}
42
43#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
50pub struct BoundedString<const N: usize>(ArrayString<N>);
51
52impl<const N: usize> BoundedString<N> {
53 pub fn new(s: &str) -> Result<Self, BoundedStringError> {
55 ArrayString::from(s)
56 .map(Self)
57 .map_err(|_| BoundedStringError::Overflow {
58 len: s.len(),
59 cap: N,
60 })
61 }
62
63 #[inline]
65 #[must_use]
66 pub fn as_str(&self) -> &str {
67 self.0.as_str()
68 }
69
70 pub const CAP: usize = N;
72}
73
74impl<const N: usize> Serialize for BoundedString<N> {
75 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
76 s.serialize_str(self.0.as_str())
77 }
78}
79
80impl<'de, const N: usize> Deserialize<'de> for BoundedString<N> {
81 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
82 let s: &str = <&str>::deserialize(d)?;
83 Self::new(s).map_err(D::Error::custom)
84 }
85}
86
87#[non_exhaustive]
89#[derive(Debug, Clone, thiserror::Error, Eq, PartialEq)]
90pub enum BoundedStringError {
91 #[error("BoundedString overflow: len {len} > cap {cap}")]
93 Overflow {
94 len: usize,
96 cap: usize,
98 },
99}
100
101#[cfg(test)]
102#[allow(clippy::unwrap_used, clippy::expect_used)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn bounded_string_accepts_within_cap() {
108 let s = BoundedString::<8>::new("abcd").unwrap();
109 assert_eq!(s.as_str(), "abcd");
110 assert_eq!(BoundedString::<8>::CAP, 8);
111 }
112
113 #[test]
114 fn bounded_string_rejects_over_cap() {
115 let e = BoundedString::<4>::new("hello").unwrap_err();
116 match e {
117 BoundedStringError::Overflow { len, cap } => {
118 assert_eq!(len, 5);
119 assert_eq!(cap, 4);
120 }
121 }
122 }
123
124 #[test]
125 fn bounded_string_serde_roundtrip_postcard() {
126 let s = BoundedString::<16>::new("hello").unwrap();
127 let bytes = postcard::to_stdvec(&s).unwrap();
128 let back: BoundedString<16> = postcard::from_bytes(&bytes).unwrap();
129 assert_eq!(s, back);
130 }
131
132 #[test]
133 fn bounded_string_deserialize_rejects_over_cap_at_runtime() {
134 let big = BoundedString::<16>::new("0123456789abcdef").unwrap();
135 let bytes = postcard::to_stdvec(&big).unwrap();
136 let res: Result<BoundedString<8>, _> = postcard::from_bytes(&bytes);
137 assert!(res.is_err());
138 }
139}