1use crate::impl_storable_bounded;
8use candid::CandidType;
9use derive_more::{Deref, DerefMut, Display};
10use serde::{Deserialize, Serialize};
11use std::convert::TryFrom;
12
13#[derive(
21 CandidType,
22 Clone,
23 Debug,
24 Deref,
25 DerefMut,
26 Deserialize,
27 Display,
28 Eq,
29 Ord,
30 PartialEq,
31 PartialOrd,
32 Serialize,
33)]
34pub struct BoundedString<const N: u32>(pub String);
35
36#[allow(clippy::cast_possible_truncation)]
37impl<const N: u32> BoundedString<N> {
38 #[must_use]
39 pub fn new(s: impl Into<String>) -> Self {
40 let s: String = s.into();
41 let slen = s.len();
42
43 assert!(
44 slen as u32 <= N,
45 "String '{s}' too long for BoundedString<{N}> ({slen} bytes)",
46 );
47
48 Self(s)
49 }
50}
51
52impl<const N: u32> AsRef<str> for BoundedString<N> {
53 fn as_ref(&self) -> &str {
54 &self.0
55 }
56}
57
58pub type BoundedString8 = BoundedString<8>;
59pub type BoundedString16 = BoundedString<16>;
60pub type BoundedString32 = BoundedString<32>;
61pub type BoundedString64 = BoundedString<64>;
62pub type BoundedString128 = BoundedString<128>;
63pub type BoundedString256 = BoundedString<256>;
64
65impl_storable_bounded!(BoundedString8, 8, false);
66impl_storable_bounded!(BoundedString16, 16, false);
67impl_storable_bounded!(BoundedString32, 32, false);
68impl_storable_bounded!(BoundedString64, 64, false);
69impl_storable_bounded!(BoundedString128, 128, false);
70impl_storable_bounded!(BoundedString256, 256, false);
71
72impl<const N: u32> From<BoundedString<N>> for String {
74 fn from(b: BoundedString<N>) -> Self {
75 b.0
76 }
77}
78
79impl<const N: u32> TryFrom<String> for BoundedString<N> {
80 type Error = String;
81
82 #[allow(clippy::cast_possible_truncation)]
83 fn try_from(value: String) -> Result<Self, Self::Error> {
84 if value.len() as u32 <= N {
85 Ok(Self(value))
86 } else {
87 Err(format!("String too long for BoundedString<{N}>"))
88 }
89 }
90}
91
92impl<const N: u32> TryFrom<&str> for BoundedString<N> {
93 type Error = String;
94
95 #[allow(clippy::cast_possible_truncation)]
96 fn try_from(value: &str) -> Result<Self, Self::Error> {
97 if value.len() as u32 <= N {
98 Ok(Self(value.to_string()))
99 } else {
100 Err(format!("String too long for BoundedString<{N}>"))
101 }
102 }
103}
104
105#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::{
113 cdk::structures::Storable,
114 serialize::{deserialize, serialize},
115 };
116
117 #[test]
118 fn create_within_bounds() {
119 let s = "hello".to_string();
120 let b = BoundedString16::new(s.clone());
121 assert_eq!(b.0, s);
122 }
123
124 #[test]
125 fn create_at_exact_limit() {
126 let s = "a".repeat(16);
127 let b = BoundedString16::new(s.clone());
128 assert_eq!(b.0, s);
129 }
130
131 #[test]
132 fn ordering_and_equality() {
133 let a = BoundedString16::new("abc".to_string());
134 let b = BoundedString16::new("abc".to_string());
135 let c = BoundedString16::new("def".to_string());
136
137 assert_eq!(a, b);
138 assert_ne!(a, c);
139 assert!(a < c); }
141
142 #[test]
143 fn serialize_and_deserialize_roundtrip() {
144 let original = BoundedString32::new("roundtrip test".to_string());
145
146 let bytes = serialize(&original).unwrap();
148
149 let decoded: BoundedString32 = deserialize(&bytes).unwrap();
151
152 assert_eq!(original, decoded);
153 }
154
155 #[test]
156 fn storable_impl_to_bytes_and_from_bytes() {
157 let original = BoundedString64::new("hello world".to_string());
158
159 let bytes = serialize(&original).unwrap();
160 let cow = std::borrow::Cow::Owned(bytes);
161
162 let stored = <BoundedString64 as Storable>::from_bytes(cow);
164
165 assert_eq!(original, stored);
166
167 let owned = original.clone().into_bytes();
168 assert_eq!(deserialize::<BoundedString64>(&owned).unwrap(), original);
169 }
170}