1use crate::{
2 property::PropertyError,
3 value::{Value, ValueType},
4};
5use ankurah_proto::EntityId;
6
7#[derive(Debug, Clone, PartialEq)]
8pub enum CastError {
9 IncompatibleTypes { from: ValueType, to: ValueType },
11 InvalidFormat { value: String, target_type: ValueType },
13 NumericOverflow { value: String, target_type: ValueType },
15}
16
17impl std::fmt::Display for CastError {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 match self {
20 CastError::IncompatibleTypes { from, to } => {
21 write!(f, "Cannot cast from {:?} to {:?}", from, to)
22 }
23 CastError::InvalidFormat { value, target_type } => {
24 write!(f, "Invalid format '{}' for type {:?}", value, target_type)
25 }
26 CastError::NumericOverflow { value, target_type } => {
27 write!(f, "Numeric overflow: '{}' cannot fit in {:?}", value, target_type)
28 }
29 }
30 }
31}
32
33impl From<CastError> for PropertyError {
34 fn from(err: CastError) -> Self { PropertyError::CastError(err) }
35}
36
37impl std::error::Error for CastError {}
38
39impl Value {
40 pub fn cast_to(&self, target_type: ValueType) -> Result<Value, CastError> {
42 let source_type = ValueType::of(self);
43
44 if source_type == target_type {
46 return Ok(self.clone());
47 }
48
49 match (self, target_type) {
50 (Value::String(s), ValueType::EntityId) => match EntityId::from_base64(s) {
52 Ok(entity_id) => Ok(Value::EntityId(entity_id)),
53 Err(_) => Err(CastError::InvalidFormat { value: s.clone(), target_type: ValueType::EntityId }),
54 },
55
56 (Value::EntityId(entity_id), ValueType::String) => Ok(Value::String(entity_id.to_base64())),
58
59 (Value::I16(n), ValueType::I32) => Ok(Value::I32(*n as i32)),
61 (Value::I16(n), ValueType::I64) => Ok(Value::I64(*n as i64)),
62 (Value::I16(n), ValueType::F64) => Ok(Value::F64(*n as f64)),
63
64 (Value::I32(n), ValueType::I16) => {
65 if *n >= i16::MIN as i32 && *n <= i16::MAX as i32 {
66 Ok(Value::I16(*n as i16))
67 } else {
68 Err(CastError::NumericOverflow { value: n.to_string(), target_type: ValueType::I16 })
69 }
70 }
71 (Value::I32(n), ValueType::I64) => Ok(Value::I64(*n as i64)),
72 (Value::I32(n), ValueType::F64) => Ok(Value::F64(*n as f64)),
73
74 (Value::I64(n), ValueType::I16) => {
75 if *n >= i16::MIN as i64 && *n <= i16::MAX as i64 {
76 Ok(Value::I16(*n as i16))
77 } else {
78 Err(CastError::NumericOverflow { value: n.to_string(), target_type: ValueType::I16 })
79 }
80 }
81 (Value::I64(n), ValueType::I32) => {
82 if *n >= i32::MIN as i64 && *n <= i32::MAX as i64 {
83 Ok(Value::I32(*n as i32))
84 } else {
85 Err(CastError::NumericOverflow { value: n.to_string(), target_type: ValueType::I32 })
86 }
87 }
88 (Value::I64(n), ValueType::F64) => Ok(Value::F64(*n as f64)),
89
90 (Value::F64(n), ValueType::I16) => {
91 if n.is_finite() && *n >= i16::MIN as f64 && *n <= i16::MAX as f64 {
92 Ok(Value::I16(*n as i16))
93 } else {
94 Err(CastError::NumericOverflow { value: n.to_string(), target_type: ValueType::I16 })
95 }
96 }
97 (Value::F64(n), ValueType::I32) => {
98 if n.is_finite() && *n >= i32::MIN as f64 && *n <= i32::MAX as f64 {
99 Ok(Value::I32(*n as i32))
100 } else {
101 Err(CastError::NumericOverflow { value: n.to_string(), target_type: ValueType::I32 })
102 }
103 }
104 (Value::F64(n), ValueType::I64) => {
105 if n.is_finite() && *n >= i64::MIN as f64 && *n <= i64::MAX as f64 {
106 Ok(Value::I64(*n as i64))
107 } else {
108 Err(CastError::NumericOverflow { value: n.to_string(), target_type: ValueType::I64 })
109 }
110 }
111
112 (Value::String(s), ValueType::I16) => match s.parse::<i16>() {
114 Ok(n) => Ok(Value::I16(n)),
115 Err(_) => Err(CastError::InvalidFormat { value: s.clone(), target_type: ValueType::I16 }),
116 },
117 (Value::String(s), ValueType::I32) => match s.parse::<i32>() {
118 Ok(n) => Ok(Value::I32(n)),
119 Err(_) => Err(CastError::InvalidFormat { value: s.clone(), target_type: ValueType::I32 }),
120 },
121 (Value::String(s), ValueType::I64) => match s.parse::<i64>() {
122 Ok(n) => Ok(Value::I64(n)),
123 Err(_) => Err(CastError::InvalidFormat { value: s.clone(), target_type: ValueType::I64 }),
124 },
125 (Value::String(s), ValueType::F64) => match s.parse::<f64>() {
126 Ok(n) => Ok(Value::F64(n)),
127 Err(_) => Err(CastError::InvalidFormat { value: s.clone(), target_type: ValueType::F64 }),
128 },
129 (Value::String(s), ValueType::Bool) => match s.to_lowercase().as_str() {
130 "true" | "1" | "yes" | "on" => Ok(Value::Bool(true)),
131 "false" | "0" | "no" | "off" => Ok(Value::Bool(false)),
132 _ => Err(CastError::InvalidFormat { value: s.clone(), target_type: ValueType::Bool }),
133 },
134
135 (Value::I16(n), ValueType::String) => Ok(Value::String(n.to_string())),
137 (Value::I32(n), ValueType::String) => Ok(Value::String(n.to_string())),
138 (Value::I64(n), ValueType::String) => Ok(Value::String(n.to_string())),
139 (Value::F64(n), ValueType::String) => Ok(Value::String(n.to_string())),
140 (Value::Bool(b), ValueType::String) => Ok(Value::String(b.to_string())),
141
142 (Value::Bool(b), ValueType::I16) => Ok(Value::I16(if *b { 1 } else { 0 })),
144 (Value::Bool(b), ValueType::I32) => Ok(Value::I32(if *b { 1 } else { 0 })),
145 (Value::Bool(b), ValueType::I64) => Ok(Value::I64(if *b { 1 } else { 0 })),
146 (Value::Bool(b), ValueType::F64) => Ok(Value::F64(if *b { 1.0 } else { 0.0 })),
147
148 (Value::I16(n), ValueType::Bool) => Ok(Value::Bool(*n != 0)),
150 (Value::I32(n), ValueType::Bool) => Ok(Value::Bool(*n != 0)),
151 (Value::I64(n), ValueType::Bool) => Ok(Value::Bool(*n != 0)),
152 (Value::F64(f), ValueType::Bool) => Ok(Value::Bool(*f != 0.0)),
153
154 (Value::String(s), ValueType::Json) => Ok(Value::Json(serde_json::Value::String(s.clone()))),
157 (Value::I64(n), ValueType::Json) => Ok(Value::Json(serde_json::json!(*n))),
158 (Value::I32(n), ValueType::Json) => Ok(Value::Json(serde_json::json!(*n as i64))),
159 (Value::I16(n), ValueType::Json) => Ok(Value::Json(serde_json::json!(*n as i64))),
160 (Value::F64(n), ValueType::Json) => Ok(Value::Json(serde_json::json!(*n))),
161 (Value::Bool(b), ValueType::Json) => Ok(Value::Json(serde_json::Value::Bool(*b))),
162
163 (Value::Json(json), ValueType::String) => match json {
165 serde_json::Value::String(s) => Ok(Value::String(s.clone())),
166 _ => Err(CastError::IncompatibleTypes { from: source_type, to: target_type }),
167 },
168 (Value::Json(json), ValueType::I64) => match json {
169 serde_json::Value::Number(n) if n.is_i64() => Ok(Value::I64(n.as_i64().unwrap())),
170 _ => Err(CastError::IncompatibleTypes { from: source_type, to: target_type }),
171 },
172 (Value::Json(json), ValueType::I32) => match json {
173 serde_json::Value::Number(n) if n.is_i64() => {
174 let i = n.as_i64().unwrap();
175 if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
176 Ok(Value::I32(i as i32))
177 } else {
178 Err(CastError::NumericOverflow { value: i.to_string(), target_type: ValueType::I32 })
179 }
180 }
181 _ => Err(CastError::IncompatibleTypes { from: source_type, to: target_type }),
182 },
183 (Value::Json(json), ValueType::I16) => match json {
184 serde_json::Value::Number(n) if n.is_i64() => {
185 let i = n.as_i64().unwrap();
186 if i >= i16::MIN as i64 && i <= i16::MAX as i64 {
187 Ok(Value::I16(i as i16))
188 } else {
189 Err(CastError::NumericOverflow { value: i.to_string(), target_type: ValueType::I16 })
190 }
191 }
192 _ => Err(CastError::IncompatibleTypes { from: source_type, to: target_type }),
193 },
194 (Value::Json(json), ValueType::F64) => match json {
195 serde_json::Value::Number(n) => Ok(Value::F64(n.as_f64().unwrap_or(0.0))),
196 _ => Err(CastError::IncompatibleTypes { from: source_type, to: target_type }),
197 },
198 (Value::Json(json), ValueType::Bool) => match json {
199 serde_json::Value::Bool(b) => Ok(Value::Bool(*b)),
200 _ => Err(CastError::IncompatibleTypes { from: source_type, to: target_type }),
201 },
202
203 _ => Err(CastError::IncompatibleTypes { from: source_type, to: target_type }),
205 }
206 }
207
208 pub fn try_cast_to(&self, target_type: ValueType) -> Option<Value> { self.cast_to(target_type).ok() }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_string_to_entity_id() {
218 let entity_id = EntityId::new();
219 let base64_str = entity_id.to_base64();
220 let value = Value::String(base64_str.clone());
221
222 let result = value.cast_to(ValueType::EntityId).unwrap();
223 match result {
224 Value::EntityId(parsed_id) => assert_eq!(parsed_id, entity_id),
225 _ => panic!("Expected EntityId variant"),
226 }
227 }
228
229 #[test]
230 fn test_entity_id_to_string() {
231 let entity_id = EntityId::new();
232 let value = Value::EntityId(entity_id.clone());
233
234 let result = value.cast_to(ValueType::String).unwrap();
235 match result {
236 Value::String(s) => assert_eq!(s, entity_id.to_base64()),
237 _ => panic!("Expected String variant"),
238 }
239 }
240
241 #[test]
242 fn test_invalid_entity_id_string() {
243 let value = Value::String("invalid-entity-id".to_string());
244 let result = value.cast_to(ValueType::EntityId);
245
246 assert!(matches!(result, Err(CastError::InvalidFormat { .. })));
247 }
248
249 #[test]
250 fn test_numeric_upcasting() {
251 let value = Value::I16(42);
252
253 assert_eq!(value.cast_to(ValueType::I32).unwrap(), Value::I32(42));
254 assert_eq!(value.cast_to(ValueType::I64).unwrap(), Value::I64(42));
255 assert_eq!(value.cast_to(ValueType::F64).unwrap(), Value::F64(42.0));
256 }
257
258 #[test]
259 fn test_numeric_downcasting() {
260 let value = Value::I32(42);
261 assert_eq!(value.cast_to(ValueType::I16).unwrap(), Value::I16(42));
262
263 let large_value = Value::I32(100000);
264 assert!(matches!(large_value.cast_to(ValueType::I16), Err(CastError::NumericOverflow { .. })));
265 }
266
267 #[test]
268 fn test_string_to_numeric() {
269 let value = Value::String("42".to_string());
270
271 assert_eq!(value.cast_to(ValueType::I16).unwrap(), Value::I16(42));
272 assert_eq!(value.cast_to(ValueType::I32).unwrap(), Value::I32(42));
273 assert_eq!(value.cast_to(ValueType::I64).unwrap(), Value::I64(42));
274 assert_eq!(value.cast_to(ValueType::F64).unwrap(), Value::F64(42.0));
275 }
276
277 #[test]
278 fn test_string_to_bool() {
279 assert_eq!(Value::String("true".to_string()).cast_to(ValueType::Bool).unwrap(), Value::Bool(true));
280 assert_eq!(Value::String("false".to_string()).cast_to(ValueType::Bool).unwrap(), Value::Bool(false));
281 assert_eq!(Value::String("1".to_string()).cast_to(ValueType::Bool).unwrap(), Value::Bool(true));
282 assert_eq!(Value::String("0".to_string()).cast_to(ValueType::Bool).unwrap(), Value::Bool(false));
283
284 assert!(matches!(Value::String("maybe".to_string()).cast_to(ValueType::Bool), Err(CastError::InvalidFormat { .. })));
285 }
286
287 #[test]
288 fn test_incompatible_types() {
289 let value = Value::Binary(vec![1, 2, 3]);
291 let result = value.cast_to(ValueType::I32);
292
293 assert!(matches!(result, Err(CastError::IncompatibleTypes { .. })));
294 }
295
296 #[test]
297 fn test_same_type_cast() {
298 let value = Value::I32(42);
299 let result = value.cast_to(ValueType::I32).unwrap();
300
301 assert_eq!(result, value);
302 }
303}