jam_std_common/util/
bounded_string.rs

1use bounded_collections::{BoundedVec, ConstU32};
2use codec::{Decode, Encode, MaxEncodedLen};
3
4/// A UTF-8 encoded string not exceeding `N` bytes in length.
5#[derive(Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Encode)]
6pub struct BoundedString<const N: u32>(String);
7
8impl<const N: u32> std::fmt::Debug for BoundedString<N> {
9	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
10		self.0.fmt(f)
11	}
12}
13
14impl<const N: u32> std::fmt::Display for BoundedString<N> {
15	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16		self.0.fmt(f)
17	}
18}
19
20impl<const N: u32> std::ops::Deref for BoundedString<N> {
21	type Target = str;
22
23	fn deref(&self) -> &Self::Target {
24		&self.0
25	}
26}
27
28impl<const N: u32> Decode for BoundedString<N> {
29	fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
30		let v = BoundedVec::<u8, ConstU32<N>>::decode(input)?;
31		String::from_utf8(v.into_inner()).map_err(|_| "Invalid UTF-8".into()).map(Self)
32	}
33}
34
35impl<const N: u32> MaxEncodedLen for BoundedString<N> {
36	fn max_encoded_len() -> usize {
37		BoundedVec::<u8, ConstU32<N>>::max_encoded_len()
38	}
39}
40
41pub trait IntoTruncated {
42	/// Convert `self` into a `BoundedString`, truncating if necessary. In the case of truncation,
43	/// the discarded characters are replaced by "...".
44	fn into_truncated<const N: u32>(self) -> BoundedString<N>;
45}
46
47impl IntoTruncated for String {
48	fn into_truncated<const N: u32>(mut self) -> BoundedString<N> {
49		if self.len() > (N as usize) {
50			while self.len() > (N.saturating_sub(3) as usize) {
51				self.pop();
52			}
53			while self.len() < (N as usize) {
54				self.push('.');
55			}
56			debug_assert!(self.len() <= (N as usize));
57		}
58		BoundedString(self)
59	}
60}
61
62pub trait Truncated {
63	/// Convert `self` to a `BoundedString`, truncating if necessary. In the case of truncation,
64	/// the discarded characters are replaced by "...".
65	fn truncated<const N: u32>(&self) -> BoundedString<N>;
66}
67
68impl<T: ToString + ?Sized> Truncated for T {
69	fn truncated<const N: u32>(&self) -> BoundedString<N> {
70		self.to_string().into_truncated()
71	}
72}
73
74#[cfg(test)]
75mod tests {
76	use super::*;
77
78	#[test]
79	fn truncation() {
80		debug_assert_eq!("abc".truncated(), BoundedString::<4>("abc".into()));
81		debug_assert_eq!("abc".truncated(), BoundedString::<3>("abc".into()));
82		debug_assert_eq!("abcd".truncated(), BoundedString::<3>("...".into()));
83		debug_assert_eq!("abc".truncated(), BoundedString::<2>("..".into()));
84		debug_assert_eq!("abc".truncated(), BoundedString::<1>(".".into()));
85		debug_assert_eq!("abc".truncated(), BoundedString::<0>("".into()));
86		debug_assert_eq!("abcdefg".truncated(), BoundedString::<6>("abc...".into()));
87	}
88}