use arkhe_kernel::abi::TypeCode;
use arrayvec::ArrayString;
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
pub trait ArkheComponent:
crate::__sealed::__Sealed + Serialize + for<'de> Deserialize<'de> + 'static
{
const TYPE_CODE: u32;
const SCHEMA_VERSION: u16;
fn type_code() -> TypeCode {
TypeCode(Self::TYPE_CODE)
}
fn approx_size(&self) -> usize {
core::mem::size_of::<Self>()
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct BoundedString<const N: usize>(ArrayString<N>);
impl<const N: usize> BoundedString<N> {
pub fn new(s: &str) -> Result<Self, BoundedStringError> {
ArrayString::from(s)
.map(Self)
.map_err(|_| BoundedStringError::Overflow {
len: s.len(),
cap: N,
})
}
#[inline]
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub const CAP: usize = N;
}
impl<const N: usize> Serialize for BoundedString<N> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.0.as_str())
}
}
impl<'de, const N: usize> Deserialize<'de> for BoundedString<N> {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s: &str = <&str>::deserialize(d)?;
Self::new(s).map_err(D::Error::custom)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, thiserror::Error, Eq, PartialEq)]
pub enum BoundedStringError {
#[error("BoundedString overflow: len {len} > cap {cap}")]
Overflow {
len: usize,
cap: usize,
},
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn bounded_string_accepts_within_cap() {
let s = BoundedString::<8>::new("abcd").unwrap();
assert_eq!(s.as_str(), "abcd");
assert_eq!(BoundedString::<8>::CAP, 8);
}
#[test]
fn bounded_string_rejects_over_cap() {
let e = BoundedString::<4>::new("hello").unwrap_err();
match e {
BoundedStringError::Overflow { len, cap } => {
assert_eq!(len, 5);
assert_eq!(cap, 4);
}
}
}
#[test]
fn bounded_string_serde_roundtrip_postcard() {
let s = BoundedString::<16>::new("hello").unwrap();
let bytes = postcard::to_stdvec(&s).unwrap();
let back: BoundedString<16> = postcard::from_bytes(&bytes).unwrap();
assert_eq!(s, back);
}
#[test]
fn bounded_string_deserialize_rejects_over_cap_at_runtime() {
let big = BoundedString::<16>::new("0123456789abcdef").unwrap();
let bytes = postcard::to_stdvec(&big).unwrap();
let res: Result<BoundedString<8>, _> = postcard::from_bytes(&bytes);
assert!(res.is_err());
}
}