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
37#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
44pub struct BoundedString<const N: usize>(ArrayString<N>);
45
46impl<const N: usize> BoundedString<N> {
47 pub fn new(s: &str) -> Result<Self, BoundedStringError> {
49 ArrayString::from(s)
50 .map(Self)
51 .map_err(|_| BoundedStringError::Overflow {
52 len: s.len(),
53 cap: N,
54 })
55 }
56
57 #[inline]
59 #[must_use]
60 pub fn as_str(&self) -> &str {
61 self.0.as_str()
62 }
63
64 pub const CAP: usize = N;
66}
67
68impl<const N: usize> Serialize for BoundedString<N> {
69 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
70 s.serialize_str(self.0.as_str())
71 }
72}
73
74impl<'de, const N: usize> Deserialize<'de> for BoundedString<N> {
75 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
76 let s: &str = <&str>::deserialize(d)?;
77 Self::new(s).map_err(D::Error::custom)
78 }
79}
80
81#[non_exhaustive]
83#[derive(Debug, Clone, thiserror::Error, Eq, PartialEq)]
84pub enum BoundedStringError {
85 #[error("BoundedString overflow: len {len} > cap {cap}")]
87 Overflow {
88 len: usize,
90 cap: usize,
92 },
93}
94
95#[cfg(test)]
96#[allow(clippy::unwrap_used, clippy::expect_used)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn bounded_string_accepts_within_cap() {
102 let s = BoundedString::<8>::new("abcd").unwrap();
103 assert_eq!(s.as_str(), "abcd");
104 assert_eq!(BoundedString::<8>::CAP, 8);
105 }
106
107 #[test]
108 fn bounded_string_rejects_over_cap() {
109 let e = BoundedString::<4>::new("hello").unwrap_err();
110 match e {
111 BoundedStringError::Overflow { len, cap } => {
112 assert_eq!(len, 5);
113 assert_eq!(cap, 4);
114 }
115 }
116 }
117
118 #[test]
119 fn bounded_string_serde_roundtrip_postcard() {
120 let s = BoundedString::<16>::new("hello").unwrap();
121 let bytes = postcard::to_stdvec(&s).unwrap();
122 let back: BoundedString<16> = postcard::from_bytes(&bytes).unwrap();
123 assert_eq!(s, back);
124 }
125
126 #[test]
127 fn bounded_string_deserialize_rejects_over_cap_at_runtime() {
128 let big = BoundedString::<16>::new("0123456789abcdef").unwrap();
129 let bytes = postcard::to_stdvec(&big).unwrap();
130 let res: Result<BoundedString<8>, _> = postcard::from_bytes(&bytes);
131 assert!(res.is_err());
132 }
133}