c5store/
value.rs

1use std::collections::HashMap;
2use std::convert::TryInto;
3
4use base64::Engine;
5use serde::{Deserialize, Serialize};
6
7// Assuming ConfigError is accessible, e.g., via `crate::ConfigError` or `use crate::ConfigError;`
8use crate::ConfigError;
9
10// Macro for basic TryInto implementation (non-numeric primitives, collections)
11macro_rules! try_into_impl_basic {
12  // $target_type: The Rust type to convert into (e.g., bool, String)
13  // $c5_variant: The corresponding C5DataValue variant (e.g., Boolean, String)
14  // $expected_type_str: A static string describing the expected C5 type (e.g., "Boolean", "String")
15  ($target_type:ty, $c5_variant:ident, $expected_type_str:literal) => {
16    impl TryInto<$target_type> for C5DataValue {
17      type Error = ConfigError;
18
19      #[inline]
20      fn try_into(self) -> Result<$target_type, Self::Error> {
21        match self {
22          C5DataValue::$c5_variant(inner_value) => Ok(inner_value),
23          other => Err(ConfigError::TypeMismatch {
24            // Key context is not available within TryInto itself.
25            // The caller (get_into) handles KeyNotFound before calling try_into.
26            key: "_conversion_".to_string(),
27            expected_type: $expected_type_str,
28            found_type: other.type_name(),
29          }),
30        }
31      }
32    }
33
34    // Implementation for converting from a reference (&C5DataValue)
35    impl TryInto<$target_type> for &C5DataValue {
36      type Error = ConfigError;
37
38      #[inline]
39      fn try_into(self) -> Result<$target_type, Self::Error> {
40        match self {
41          // For owned types like String, Vec, HashMap, we need to clone.
42          // For Copy types (like bool, numbers), cloning is cheap/implicit.
43          C5DataValue::$c5_variant(inner_value) => Ok(inner_value.clone()),
44          other => Err(ConfigError::TypeMismatch {
45            key: "_conversion_".to_string(),
46            expected_type: $expected_type_str,
47            found_type: other.type_name(),
48          }),
49        }
50      }
51    }
52  };
53  // Specific override for Copy types where clone isn't needed on ref access
54  ($target_type:ty, $c5_variant:ident, $expected_type_str:literal, Copy) => {
55    impl TryInto<$target_type> for C5DataValue {
56      type Error = ConfigError;
57
58      #[inline]
59      fn try_into(self) -> Result<$target_type, Self::Error> {
60        match self {
61          C5DataValue::$c5_variant(inner_value) => Ok(inner_value),
62          other => Err(ConfigError::TypeMismatch {
63            key: "_conversion_".to_string(),
64            expected_type: $expected_type_str,
65            found_type: other.type_name(),
66          }),
67        }
68      }
69    }
70
71    impl TryInto<$target_type> for &C5DataValue {
72      type Error = ConfigError;
73
74      #[inline]
75      fn try_into(self) -> Result<$target_type, Self::Error> {
76        match self {
77          C5DataValue::$c5_variant(inner_value) => Ok(*inner_value), // Direct deref for Copy types
78          other => Err(ConfigError::TypeMismatch {
79            key: "_conversion_".to_string(),
80            expected_type: $expected_type_str,
81            found_type: other.type_name(),
82          }),
83        }
84      }
85    }
86  };
87}
88
89// Macro specifically for numeric TryInto where casting occurs
90// Handles simple casts between C5 Integer/UInteger/Float and Rust numeric types
91macro_rules! try_into_impl_numeric_cast {
92  // $target_type: The Rust numeric type (e.g., i32, u16, f32)
93  // $c5_variant: The primary C5DataValue variant to check (Integer, UInteger, Float)
94  // $expected_type_str: Static string for error message
95  ($target_type:ty, $c5_variant:ident, $expected_type_str:literal) => {
96    impl TryInto<$target_type> for C5DataValue {
97      type Error = ConfigError;
98
99      #[inline]
100      fn try_into(self) -> Result<$target_type, Self::Error> {
101        match self {
102          // Direct cast - Rust handles range checks for float->int, etc.
103          // but we rely on the source type matching mostly.
104          // More robust range checks could be added if needed.
105          C5DataValue::$c5_variant(inner_value) => Ok(inner_value as $target_type),
106          other => Err(ConfigError::TypeMismatch {
107            key: "_conversion_".to_string(),
108            expected_type: $expected_type_str,
109            found_type: other.type_name(),
110          }),
111        }
112      }
113    }
114
115    impl TryInto<$target_type> for &C5DataValue {
116      type Error = ConfigError;
117
118      #[inline]
119      fn try_into(self) -> Result<$target_type, Self::Error> {
120        match self {
121          C5DataValue::$c5_variant(inner_value) => Ok(*inner_value as $target_type),
122          other => Err(ConfigError::TypeMismatch {
123            key: "_conversion_".to_string(),
124            expected_type: $expected_type_str,
125            found_type: other.type_name(),
126          }),
127        }
128      }
129    }
130  };
131}
132
133// Macro to implement From<primitive> for C5DataValue
134macro_rules! from_impl_numeric {
135  ($from_type:ty, $c5_variant:ident, $cast_type:ty) => {
136    impl From<$from_type> for C5DataValue {
137      #[inline]
138      fn from(value: $from_type) -> Self {
139        C5DataValue::$c5_variant(value as $cast_type)
140      }
141    }
142  };
143}
144
145// Macro for Vec<T> TryInto conversion
146macro_rules! try_into_impl_vec {
147  ($target_element_type:ty) => {
148    impl TryInto<Vec<$target_element_type>> for C5DataValue {
149      type Error = ConfigError;
150
151      fn try_into(self) -> Result<Vec<$target_element_type>, Self::Error> {
152        match self {
153          C5DataValue::Array(inner_value) => inner_value
154            .into_iter()
155            .map(|vec_item| vec_item.try_into()) // Each element conversion can fail
156            .collect::<Result<Vec<$target_element_type>, ConfigError>>(), // Collect results
157          other => Err(ConfigError::TypeMismatch {
158            key: "_conversion_".to_string(),
159            expected_type: "Array",
160            found_type: other.type_name(),
161          }),
162        }
163      }
164    }
165
166    impl TryInto<Vec<$target_element_type>> for &C5DataValue {
167      type Error = ConfigError;
168
169      fn try_into(self) -> Result<Vec<$target_element_type>, Self::Error> {
170        match self {
171          // Note: .into_iter() on a slice iterates over references
172          C5DataValue::Array(inner_value) => inner_value
173            .iter() // Iterate over references
174            .map(|vec_item_ref| vec_item_ref.try_into()) // TryInto<&C5DataValue> for T
175            .collect::<Result<Vec<$target_element_type>, ConfigError>>(),
176          other => Err(ConfigError::TypeMismatch {
177            key: "_conversion_".to_string(),
178            expected_type: "Array",
179            found_type: other.type_name(),
180          }),
181        }
182      }
183    }
184  };
185}
186
187#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
188pub enum C5DataValue {
189  Null,
190  Bytes(Vec<u8>),
191  Boolean(bool),
192  // This represents numbers less than zero (or typically signed)
193  Integer(i64),
194  // This represents non-negative numbers (or typically unsigned)
195  UInteger(u64),
196  Float(f64),
197  String(String),
198  Array(Vec<C5DataValue>),
199  Map(HashMap<String, C5DataValue>),
200}
201
202impl C5DataValue {
203  // Helper to get type name as a string for error messages
204  pub(crate) fn type_name(&self) -> &'static str {
205    match self {
206      C5DataValue::Null => "Null",
207      C5DataValue::Bytes(_) => "Bytes",
208      C5DataValue::Boolean(_) => "Boolean",
209      C5DataValue::Integer(_) => "Integer",
210      C5DataValue::UInteger(_) => "UInteger",
211      C5DataValue::Float(_) => "Float",
212      C5DataValue::String(_) => "String",
213      C5DataValue::Array(_) => "Array",
214      C5DataValue::Map(_) => "Map",
215    }
216  }
217
218  // Helper method for converting value to bytes - useful internally?
219  // Keep this internal or remove if not strictly needed by public API consumers
220  pub(crate) fn as_bytes(&self) -> Option<Vec<u8>> {
221    match self {
222      C5DataValue::String(value) => Some(value.as_bytes().to_vec()),
223      C5DataValue::Bytes(value) => Some(value.clone()),
224      C5DataValue::Boolean(value) => Some(if *value { vec![1] } else { vec![0] }),
225      C5DataValue::Float(value) => Some(value.to_ne_bytes().to_vec()),
226      C5DataValue::Integer(value) => Some(value.to_ne_bytes().to_vec()),
227      C5DataValue::UInteger(value) => Some(value.to_ne_bytes().to_vec()),
228      _ => None,
229    }
230  }
231}
232
233// --- From Implementations ---
234
235impl From<()> for C5DataValue {
236  #[inline]
237  fn from(_value: ()) -> Self {
238    C5DataValue::Null
239  }
240}
241impl From<Vec<u8>> for C5DataValue {
242  #[inline]
243  fn from(value: Vec<u8>) -> Self {
244    C5DataValue::Bytes(value)
245  }
246}
247impl From<bool> for C5DataValue {
248  #[inline]
249  fn from(value: bool) -> Self {
250    C5DataValue::Boolean(value)
251  }
252}
253impl From<String> for C5DataValue {
254  #[inline]
255  fn from(value: String) -> Self {
256    C5DataValue::String(value)
257  }
258}
259impl From<&str> for C5DataValue {
260  #[inline]
261  fn from(value: &str) -> Self {
262    C5DataValue::String(value.to_string())
263  }
264}
265impl From<Box<str>> for C5DataValue {
266  #[inline]
267  fn from(value: Box<str>) -> Self {
268    C5DataValue::String(value.into_string())
269  }
270}
271impl From<i64> for C5DataValue {
272  #[inline]
273  fn from(value: i64) -> Self {
274    C5DataValue::Integer(value)
275  }
276}
277impl From<u64> for C5DataValue {
278  #[inline]
279  fn from(value: u64) -> Self {
280    C5DataValue::UInteger(value)
281  }
282}
283impl From<f64> for C5DataValue {
284  #[inline]
285  fn from(value: f64) -> Self {
286    C5DataValue::Float(value)
287  }
288}
289impl From<Vec<C5DataValue>> for C5DataValue {
290  #[inline]
291  fn from(value: Vec<C5DataValue>) -> Self {
292    C5DataValue::Array(value)
293  }
294}
295impl From<HashMap<String, C5DataValue>> for C5DataValue {
296  #[inline]
297  fn from(value: HashMap<String, C5DataValue>) -> Self {
298    C5DataValue::Map(value)
299  }
300}
301
302// From impls for smaller numeric types using macro
303from_impl_numeric!(i8, Integer, i64);
304from_impl_numeric!(i16, Integer, i64);
305from_impl_numeric!(i32, Integer, i64);
306from_impl_numeric!(isize, Integer, i64);
307from_impl_numeric!(u8, UInteger, u64);
308from_impl_numeric!(u16, UInteger, u64);
309from_impl_numeric!(u32, UInteger, u64);
310from_impl_numeric!(usize, UInteger, u64);
311from_impl_numeric!(f32, Float, f64);
312
313// --- TryInto Implementations ---
314
315// TryInto<()>
316impl TryInto<()> for C5DataValue {
317  type Error = ConfigError;
318  #[inline]
319  fn try_into(self) -> Result<(), Self::Error> {
320    match self {
321      C5DataValue::Null => Ok(()),
322      other => Err(ConfigError::TypeMismatch {
323        key: "_conversion_".to_string(),
324        expected_type: "Null",
325        found_type: other.type_name(),
326      }),
327    }
328  }
329}
330impl TryInto<()> for &C5DataValue {
331  type Error = ConfigError;
332  #[inline]
333  fn try_into(self) -> Result<(), Self::Error> {
334    match self {
335      C5DataValue::Null => Ok(()),
336      other => Err(ConfigError::TypeMismatch {
337        key: "_conversion_".to_string(),
338        expected_type: "Null",
339        found_type: other.type_name(),
340      }),
341    }
342  }
343}
344
345// TryInto<Vec<u8>> using macro
346try_into_impl_basic!(Vec<u8>, Bytes, "Bytes");
347
348// TryInto<bool> using macro (with Copy optimization)
349try_into_impl_basic!(bool, Boolean, "Boolean", Copy);
350
351// TryInto<String> using macro
352impl TryInto<String> for C5DataValue {
353  type Error = ConfigError;
354
355  #[inline]
356  fn try_into(self) -> Result<String, Self::Error> {
357    match self {
358      C5DataValue::String(s) => Ok(s),
359      C5DataValue::Bytes(b) => String::from_utf8(b).map_err(|e| ConfigError::ConversionError {
360        key: "_conversion_".to_string(), // Key context isn't available here
361        message: format!("decrypted bytes are not valid UTF-8: {}", e),
362      }),
363      other => Err(ConfigError::TypeMismatch {
364        key: "_conversion_".to_string(),
365        expected_type: "String or Bytes",
366        found_type: other.type_name(),
367      }),
368    }
369  }
370}
371
372impl TryInto<String> for &C5DataValue {
373  type Error = ConfigError;
374
375  #[inline]
376  fn try_into(self) -> Result<String, Self::Error> {
377    match self {
378      C5DataValue::String(s) => Ok(s.clone()), // Must clone from a reference
379      C5DataValue::Bytes(b) => {
380        String::from_utf8(b.clone()).map_err(|e| ConfigError::ConversionError {
381          key: "_conversion_".to_string(),
382          message: format!("decrypted bytes are not valid UTF-8: {}", e),
383        })
384      }
385      other => Err(ConfigError::TypeMismatch {
386        key: "_conversion_".to_string(),
387        expected_type: "String or Bytes",
388        found_type: other.type_name(),
389      }),
390    }
391  }
392}
393
394// TryInto<Box<str>>
395impl TryInto<Box<str>> for C5DataValue {
396  type Error = ConfigError;
397  #[inline]
398  fn try_into(self) -> Result<Box<str>, Self::Error> {
399    match self {
400      C5DataValue::String(inner_value) => Ok(inner_value.into_boxed_str()),
401      other => Err(ConfigError::TypeMismatch {
402        key: "_conversion_".to_string(),
403        expected_type: "String",
404        found_type: other.type_name(),
405      }),
406    }
407  }
408}
409impl TryInto<Box<str>> for &C5DataValue {
410  type Error = ConfigError;
411  #[inline]
412  fn try_into(self) -> Result<Box<str>, Self::Error> {
413    match self {
414      C5DataValue::String(inner_value) => Ok(inner_value.clone().into_boxed_str()),
415      other => Err(ConfigError::TypeMismatch {
416        key: "_conversion_".to_string(),
417        expected_type: "String",
418        found_type: other.type_name(),
419      }),
420    }
421  }
422}
423
424// --- Numeric TryInto Implementations ---
425
426// TryInto<i64> (Special case: Allow conversion from UInteger if in range)
427impl TryInto<i64> for C5DataValue {
428  type Error = ConfigError;
429  #[inline]
430  fn try_into(self) -> Result<i64, Self::Error> {
431    match self {
432      C5DataValue::Integer(i) => Ok(i),
433      C5DataValue::UInteger(u) => {
434        if u <= i64::MAX as u64 {
435          Ok(u as i64)
436        } else {
437          Err(ConfigError::ConversionError {
438            key: "_conversion_".to_string(),
439            message: format!("UInteger value {} out of range for i64", u),
440          })
441        }
442      }
443      other => Err(ConfigError::TypeMismatch {
444        key: "_conversion_".to_string(),
445        expected_type: "Integer or UInteger",
446        found_type: other.type_name(),
447      }),
448    }
449  }
450}
451impl TryInto<i64> for &C5DataValue {
452  type Error = ConfigError;
453  #[inline]
454  fn try_into(self) -> Result<i64, Self::Error> {
455    match self {
456      C5DataValue::Integer(i) => Ok(*i),
457      C5DataValue::UInteger(u) => {
458        if *u <= i64::MAX as u64 {
459          Ok(*u as i64)
460        } else {
461          Err(ConfigError::ConversionError {
462            key: "_conversion_".to_string(),
463            message: format!("UInteger value {} out of range for i64", u),
464          })
465        }
466      }
467      other => Err(ConfigError::TypeMismatch {
468        key: "_conversion_".to_string(),
469        expected_type: "Integer or UInteger",
470        found_type: other.type_name(),
471      }),
472    }
473  }
474}
475
476// TryInto<u64> (Special case: Allow conversion from Integer if non-negative)
477impl TryInto<u64> for C5DataValue {
478  type Error = ConfigError;
479  #[inline]
480  fn try_into(self) -> Result<u64, Self::Error> {
481    match self {
482      C5DataValue::UInteger(u) => Ok(u),
483      C5DataValue::Integer(i) => {
484        if i >= 0 {
485          Ok(i as u64)
486        } else {
487          Err(ConfigError::ConversionError {
488            key: "_conversion_".to_string(),
489            message: format!("Negative Integer value {} cannot be converted to u64", i),
490          })
491        }
492      }
493      other => Err(ConfigError::TypeMismatch {
494        key: "_conversion_".to_string(),
495        expected_type: "Integer or UInteger",
496        found_type: other.type_name(),
497      }),
498    }
499  }
500}
501impl TryInto<u64> for &C5DataValue {
502  type Error = ConfigError;
503  #[inline]
504  fn try_into(self) -> Result<u64, Self::Error> {
505    match self {
506      C5DataValue::UInteger(u) => Ok(*u),
507      C5DataValue::Integer(i) => {
508        if *i >= 0 {
509          Ok(*i as u64)
510        } else {
511          Err(ConfigError::ConversionError {
512            key: "_conversion_".to_string(),
513            message: format!("Negative Integer value {} cannot be converted to u64", i),
514          })
515        }
516      }
517      other => Err(ConfigError::TypeMismatch {
518        key: "_conversion_".to_string(),
519        expected_type: "Integer or UInteger",
520        found_type: other.type_name(),
521      }),
522    }
523  }
524}
525
526// TryInto<f64> using macro (Copy type)
527try_into_impl_basic!(f64, Float, "Float", Copy);
528
529// TryInto for smaller integer types using casting macro
530// Note: These only check the C5 type, not the range. A C5DataValue::Integer(1000)
531// could be cast to i8 resulting in overflow if not careful. More robust checks
532// could be added using try_into() on the number itself if strictness is required.
533try_into_impl_numeric_cast!(i8, Integer, "Integer");
534try_into_impl_numeric_cast!(i16, Integer, "Integer");
535try_into_impl_numeric_cast!(i32, Integer, "Integer");
536try_into_impl_numeric_cast!(isize, Integer, "Integer");
537try_into_impl_numeric_cast!(u8, UInteger, "UInteger");
538try_into_impl_numeric_cast!(u16, UInteger, "UInteger");
539try_into_impl_numeric_cast!(u32, UInteger, "UInteger");
540try_into_impl_numeric_cast!(usize, UInteger, "UInteger");
541
542// TryInto for smaller float types
543try_into_impl_numeric_cast!(f32, Float, "Float");
544
545// --- Collection TryInto Implementations ---
546
547// TryInto<Vec<C5DataValue>> using macro
548try_into_impl_basic!(Vec<C5DataValue>, Array, "Array");
549
550// TryInto<HashMap<String, C5DataValue>> using macro
551try_into_impl_basic!(HashMap<String, C5DataValue>, Map, "Map");
552
553// --- Vec<T> TryInto Implementations using macro ---
554try_into_impl_vec!(Vec<u8>);
555try_into_impl_vec!(bool);
556try_into_impl_vec!(String);
557try_into_impl_vec!(Box<str>);
558try_into_impl_vec!(i64);
559try_into_impl_vec!(u64);
560try_into_impl_vec!(f64);
561// Add others like i32, u32 etc. if needed
562try_into_impl_vec!(i32);
563try_into_impl_vec!(u32);
564try_into_impl_vec!(f32);
565
566pub(crate) fn c5_value_to_serde_json(
567  c5_value: C5DataValue,
568) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
569  match c5_value {
570    C5DataValue::Null => Ok(serde_json::Value::Null),
571    C5DataValue::Bytes(b) => Ok(serde_json::Value::String(
572      base64::engine::general_purpose::STANDARD.encode(&b),
573    )), // Represent bytes as base64 string
574    C5DataValue::Boolean(b) => Ok(serde_json::Value::Bool(b)),
575    C5DataValue::Integer(i) => Ok(serde_json::json!(i)), // Use json! macro for numbers
576    C5DataValue::UInteger(u) => Ok(serde_json::json!(u)),
577    C5DataValue::Float(f) => Ok(serde_json::json!(f)),
578    C5DataValue::String(s) => Ok(serde_json::Value::String(s)),
579    C5DataValue::Array(arr) => {
580      let mut json_arr = Vec::with_capacity(arr.len());
581      for item in arr {
582        json_arr.push(c5_value_to_serde_json(item)?);
583      }
584      Ok(serde_json::Value::Array(json_arr))
585    }
586    C5DataValue::Map(map) => {
587      let mut json_map = serde_json::Map::with_capacity(map.len());
588      for (key, value) in map {
589        json_map.insert(key, c5_value_to_serde_json(value)?);
590      }
591      Ok(serde_json::Value::Object(json_map))
592    }
593  }
594}