1use std::cmp::Ordering;
2use std::fmt;
3use std::hash::{Hash, Hasher};
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Debug, Serialize, Deserialize)]
14#[cfg_attr(feature = "candid", derive(candid::CandidType))]
15pub struct CustomValue {
16 pub type_tag: String,
18 pub encoded: Vec<u8>,
20 pub display: String,
22}
23
24impl CustomValue {
25 pub fn new<T: crate::dbms::types::CustomDataType>(value: &T) -> Self {
30 Self {
31 type_tag: T::TYPE_TAG.to_string(),
32 encoded: crate::memory::Encode::encode(value).into_owned(),
33 display: value.to_string(),
34 }
35 }
36}
37
38impl PartialEq for CustomValue {
39 fn eq(&self, other: &Self) -> bool {
40 self.type_tag == other.type_tag && self.encoded == other.encoded
41 }
42}
43
44impl Eq for CustomValue {}
45
46impl PartialOrd for CustomValue {
47 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
48 Some(self.cmp(other))
49 }
50}
51
52impl Ord for CustomValue {
53 fn cmp(&self, other: &Self) -> Ordering {
54 self.type_tag
55 .cmp(&other.type_tag)
56 .then_with(|| self.encoded.cmp(&other.encoded))
57 }
58}
59
60impl Hash for CustomValue {
61 fn hash<H: Hasher>(&self, state: &mut H) {
62 self.type_tag.hash(state);
63 self.encoded.hash(state);
64 }
65}
66
67impl fmt::Display for CustomValue {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "{}", self.display)
70 }
71}
72
73#[cfg(test)]
74mod test {
75
76 use std::collections::HashSet;
77
78 use super::*;
79
80 fn make_custom_value(type_tag: &str, encoded: &[u8], display: &str) -> CustomValue {
81 CustomValue {
82 type_tag: type_tag.to_string(),
83 encoded: encoded.to_vec(),
84 display: display.to_string(),
85 }
86 }
87
88 #[test]
89 fn test_should_compare_equal_custom_values() {
90 let a = make_custom_value("color", &[0x01, 0x02], "red");
91 let b = make_custom_value("color", &[0x01, 0x02], "red");
92 assert_eq!(a, b);
93 }
94
95 #[test]
96 fn test_should_not_equal_different_encoded() {
97 let a = make_custom_value("color", &[0x01], "red");
98 let b = make_custom_value("color", &[0x02], "blue");
99 assert_ne!(a, b);
100 }
101
102 #[test]
103 fn test_should_not_equal_different_type_tag() {
104 let a = make_custom_value("color", &[0x01], "red");
105 let b = make_custom_value("size", &[0x01], "red");
106 assert_ne!(a, b);
107 }
108
109 #[test]
110 fn test_should_ignore_display_in_equality() {
111 let a = make_custom_value("color", &[0x01, 0x02], "red");
112 let b = make_custom_value("color", &[0x01, 0x02], "rouge");
113 assert_eq!(a, b);
114 }
115
116 #[test]
117 fn test_should_order_by_type_tag_first() {
118 let alpha = make_custom_value("alpha", &[0xFF], "big");
119 let beta = make_custom_value("beta", &[0x00], "small");
120 assert!(alpha < beta);
121 }
122
123 #[test]
124 fn test_should_order_by_encoded_within_same_tag() {
125 let a = make_custom_value("color", &[0x01], "red");
126 let b = make_custom_value("color", &[0x02], "blue");
127 assert!(a < b);
128 }
129
130 #[test]
131 fn test_should_hash_consistently() {
132 let a = make_custom_value("color", &[0x01, 0x02], "red");
133 let b = make_custom_value("color", &[0x01, 0x02], "rouge");
134
135 let mut set = HashSet::new();
136 set.insert(a.clone());
137
138 assert!(set.contains(&b));
140
141 set.insert(b);
143 assert_eq!(set.len(), 1);
144 }
145
146 #[test]
147 fn test_should_display_cached_string() {
148 let cv = make_custom_value("color", &[0x01], "red");
149 assert_eq!(format!("{cv}"), "red");
150 }
151
152 #[test]
153 #[allow(clippy::clone_on_copy)]
154 fn test_should_clone() {
155 let original = make_custom_value("color", &[0x01, 0x02, 0x03], "red");
156 let cloned = original.clone();
157 assert_eq!(original, cloned);
158 assert_eq!(original.display, cloned.display);
159 }
160
161 #[test]
162 fn test_should_debug() {
163 let cv = make_custom_value("color", &[0x01], "red");
164 let debug_output = format!("{cv:?}");
165 assert!(debug_output.contains("color"));
166 assert!(debug_output.contains("red"));
167 }
168
169 #[test]
170 fn test_should_implement_custom_data_type() {
171 use std::borrow::Cow;
172 use std::fmt;
173
174 use serde::{Deserialize, Serialize};
175
176 use crate::dbms::types::{CustomDataType, DataType};
177 use crate::dbms::value::Value;
178 use crate::memory::{self, DataSize, MSize, MemoryResult, PageOffset};
179
180 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
181 pub enum TestStatus {
182 Active,
183 Inactive,
184 }
185
186 impl memory::Encode for TestStatus {
188 const SIZE: DataSize = DataSize::Fixed(1);
189 const ALIGNMENT: PageOffset = 1;
190
191 fn size(&self) -> MSize {
192 1
193 }
194
195 fn encode(&self) -> Cow<'_, [u8]> {
196 match self {
197 TestStatus::Active => Cow::Borrowed(&[0]),
198 TestStatus::Inactive => Cow::Borrowed(&[1]),
199 }
200 }
201
202 fn decode(data: Cow<[u8]>) -> MemoryResult<Self> {
203 match data.first() {
204 Some(0) => Ok(TestStatus::Active),
205 Some(1) => Ok(TestStatus::Inactive),
206 _ => Err(crate::memory::MemoryError::DecodeError(
207 crate::memory::DecodeError::TooShort,
208 )),
209 }
210 }
211 }
212
213 impl fmt::Display for TestStatus {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 write!(f, "{self:?}")
216 }
217 }
218
219 impl Default for TestStatus {
220 fn default() -> Self {
221 Self::Active
222 }
223 }
224
225 impl From<TestStatus> for Value {
226 fn from(val: TestStatus) -> Value {
227 Value::Custom(CustomValue {
228 type_tag: TestStatus::TYPE_TAG.to_string(),
229 encoded: crate::memory::Encode::encode(&val).into_owned(),
230 display: val.to_string(),
231 })
232 }
233 }
234
235 impl DataType for TestStatus {}
236
237 impl CustomDataType for TestStatus {
238 const TYPE_TAG: &'static str = "test_status";
239 }
240
241 assert_eq!(TestStatus::TYPE_TAG, "test_status");
243
244 let value: Value = TestStatus::Active.into();
246 assert!(matches!(value, Value::Custom(_)));
247
248 let cv = value.as_custom().unwrap();
249 assert_eq!(cv.type_tag, "test_status");
250 assert_eq!(cv.display, "Active");
251
252 let decoded: TestStatus = value.as_custom_type::<TestStatus>().unwrap();
254 assert_eq!(decoded, TestStatus::Active);
255 }
256}