config_lib/
value.rs

1//! Value types and operations for the config library.
2//!
3//! This module provides a flexible value system that can represent various data types
4//! commonly found in configuration files.
5
6use crate::error::{Error, Result};
7use std::collections::BTreeMap;
8use std::fmt;
9
10/// Represents a configuration value.
11#[derive(Debug, Clone, PartialEq)]
12pub enum Value {
13    /// Null/empty value
14    Null,
15    /// Boolean value
16    Bool(bool),
17    /// Integer value
18    Integer(i64),
19    /// Floating point value
20    Float(f64),
21    /// String value
22    String(String),
23    /// Array of values
24    Array(Vec<Value>),
25    /// Table (key-value pairs)
26    Table(BTreeMap<String, Value>),
27    /// DateTime value (when chrono feature is enabled)
28    #[cfg(feature = "chrono")]
29    DateTime(chrono::DateTime<chrono::Utc>),
30}
31
32impl Value {
33    /// Create a new null value
34    pub fn null() -> Self {
35        Value::Null
36    }
37
38    /// Create a new boolean value
39    pub fn bool(value: bool) -> Self {
40        Value::Bool(value)
41    }
42
43    /// Create a new integer value
44    pub fn integer(value: i64) -> Self {
45        Value::Integer(value)
46    }
47
48    /// Create a new float value
49    pub fn float(value: f64) -> Self {
50        Value::Float(value)
51    }
52
53    /// Create a new string value
54    pub fn string<S: Into<String>>(value: S) -> Self {
55        Value::String(value.into())
56    }
57
58    /// Create a new array value
59    pub fn array(values: Vec<Value>) -> Self {
60        Value::Array(values)
61    }
62
63    /// Create a new table value
64    pub fn table(table: BTreeMap<String, Value>) -> Self {
65        Value::Table(table)
66    }
67
68    /// Create a new datetime value (when chrono feature is enabled)
69    #[cfg(feature = "chrono")]
70    pub fn datetime(dt: chrono::DateTime<chrono::Utc>) -> Self {
71        Value::DateTime(dt)
72    }
73
74    /// Get the type name of this value
75    pub fn type_name(&self) -> &'static str {
76        match self {
77            Value::Null => "null",
78            Value::Bool(_) => "bool",
79            Value::Integer(_) => "integer",
80            Value::Float(_) => "float",
81            Value::String(_) => "string",
82            Value::Array(_) => "array",
83            Value::Table(_) => "table",
84            #[cfg(feature = "chrono")]
85            Value::DateTime(_) => "datetime",
86        }
87    }
88
89    /// Check if this value is null
90    pub fn is_null(&self) -> bool {
91        matches!(self, Value::Null)
92    }
93
94    /// Check if this value is a boolean
95    pub fn is_bool(&self) -> bool {
96        matches!(self, Value::Bool(_))
97    }
98
99    /// Check if this value is an integer
100    pub fn is_integer(&self) -> bool {
101        matches!(self, Value::Integer(_))
102    }
103
104    /// Check if this value is a float
105    pub fn is_float(&self) -> bool {
106        matches!(self, Value::Float(_))
107    }
108
109    /// Check if this value is a string
110    pub fn is_string(&self) -> bool {
111        matches!(self, Value::String(_))
112    }
113
114    /// Check if this value is an array
115    pub fn is_array(&self) -> bool {
116        matches!(self, Value::Array(_))
117    }
118
119    /// Check if this value is a table
120    pub fn is_table(&self) -> bool {
121        matches!(self, Value::Table(_))
122    }
123
124    /// Try to convert this value to a boolean
125    pub fn as_bool(&self) -> Result<bool> {
126        match self {
127            Value::Bool(b) => Ok(*b),
128            Value::String(s) => match s.to_lowercase().as_str() {
129                "true" | "yes" | "1" | "on" => Ok(true),
130                "false" | "no" | "0" | "off" => Ok(false),
131                _ => Err(Error::type_error(
132                    "Cannot convert to boolean",
133                    "bool",
134                    self.type_name(),
135                )),
136            },
137            _ => Err(Error::type_error(
138                "Cannot convert to boolean",
139                "bool",
140                self.type_name(),
141            )),
142        }
143    }
144
145    /// Try to convert this value to an integer
146    pub fn as_integer(&self) -> Result<i64> {
147        match self {
148            Value::Integer(i) => Ok(*i),
149            Value::Float(f) => Ok(*f as i64),
150            Value::String(s) => s.parse::<i64>().map_err(|_| {
151                Error::type_error("Cannot convert to integer", "integer", self.type_name())
152            }),
153            _ => Err(Error::type_error(
154                "Cannot convert to integer",
155                "integer",
156                self.type_name(),
157            )),
158        }
159    }
160
161    /// Try to convert this value to a float
162    pub fn as_float(&self) -> Result<f64> {
163        match self {
164            Value::Float(f) => Ok(*f),
165            Value::Integer(i) => Ok(*i as f64),
166            Value::String(s) => s.parse::<f64>().map_err(|_| {
167                Error::type_error("Cannot convert to float", "float", self.type_name())
168            }),
169            _ => Err(Error::type_error(
170                "Cannot convert to float",
171                "float",
172                self.type_name(),
173            )),
174        }
175    }
176
177    /// Try to convert this value to a string - ZERO-COPY optimized
178    pub fn as_string(&self) -> Result<&str> {
179        match self {
180            Value::String(s) => Ok(s.as_str()),
181            _ => Err(Error::type_error(
182                "Cannot convert to string",
183                "string",
184                self.type_name(),
185            )),
186        }
187    }
188
189    /// Convert this value to a string representation (allocating)
190    pub fn to_string_representation(&self) -> Result<String> {
191        match self {
192            Value::String(s) => Ok(s.clone()),
193            Value::Integer(i) => Ok(i.to_string()),
194            Value::Float(f) => Ok(f.to_string()),
195            Value::Bool(b) => Ok(b.to_string()),
196            _ => Err(Error::type_error(
197                "Cannot convert to string representation",
198                "string",
199                self.type_name(),
200            )),
201        }
202    }
203
204    /// Try to get this value as an array
205    pub fn as_array(&self) -> Result<&Vec<Value>> {
206        match self {
207            Value::Array(arr) => Ok(arr),
208            _ => Err(Error::type_error(
209                "Cannot convert to array",
210                "array",
211                self.type_name(),
212            )),
213        }
214    }
215
216    /// Try to get this value as a mutable array
217    pub fn as_array_mut(&mut self) -> Result<&mut Vec<Value>> {
218        match self {
219            Value::Array(arr) => Ok(arr),
220            _ => Err(Error::type_error(
221                "Cannot convert to array",
222                "array",
223                self.type_name(),
224            )),
225        }
226    }
227
228    /// Try to get this value as a table
229    pub fn as_table(&self) -> Result<&BTreeMap<String, Value>> {
230        match self {
231            Value::Table(table) => Ok(table),
232            _ => Err(Error::type_error(
233                "Cannot convert to table",
234                "table",
235                self.type_name(),
236            )),
237        }
238    }
239
240    /// Try to get this value as a mutable table
241    pub fn as_table_mut(&mut self) -> Result<&mut BTreeMap<String, Value>> {
242        match self {
243            Value::Table(table) => Ok(table),
244            _ => Err(Error::type_error(
245                "Cannot convert to table",
246                "table",
247                self.type_name(),
248            )),
249        }
250    }
251
252    /// Get a value by path (dot-separated)
253    pub fn get(&self, path: &str) -> Option<&Value> {
254        if path.is_empty() {
255            return Some(self);
256        }
257
258        // First try nested table access
259        let parts: Vec<&str> = path.split('.').collect();
260        let mut current = self;
261        let mut found_nested = true;
262
263        for part in parts {
264            match current {
265                Value::Table(table) => {
266                    if let Some(next) = table.get(part) {
267                        current = next;
268                    } else {
269                        found_nested = false;
270                        break;
271                    }
272                }
273                _ => {
274                    found_nested = false;
275                    break;
276                }
277            }
278        }
279
280        // If nested access succeeds, return it
281        if found_nested {
282            return Some(current);
283        }
284
285        // Fallback: try flat key access for formats like INI that use dotted keys
286        if let Value::Table(table) = self {
287            table.get(path)
288        } else {
289            None
290        }
291    }
292
293    /// Get a mutable reference to a value by path (ENTERPRISE ERROR HANDLING)
294    pub fn get_mut_nested(&mut self, path: &str) -> Result<&mut Value> {
295        if path.is_empty() {
296            return Ok(self);
297        }
298
299        let parts: Vec<&str> = path.split('.').collect();
300        if parts.is_empty() {
301            return Err(Error::key_not_found(path));
302        }
303
304        let (last_key, parent_path) = parts
305            .split_last()
306            .ok_or_else(|| Error::key_not_found(path))?;
307
308        // Navigate to parent
309        let mut current = self;
310        for part in parent_path {
311            match current {
312                Value::Table(table) => {
313                    current = table
314                        .get_mut(*part)
315                        .ok_or_else(|| Error::key_not_found(*part))?;
316                }
317                _ => {
318                    return Err(Error::type_error(
319                        format!(
320                            "Cannot navigate into {} when looking for key '{}'",
321                            current.type_name(),
322                            part
323                        ),
324                        "table",
325                        current.type_name(),
326                    ))
327                }
328            }
329        }
330
331        // Get the final value
332        match current {
333            Value::Table(table) => table
334                .get_mut(*last_key)
335                .ok_or_else(|| Error::key_not_found(*last_key)),
336            _ => Err(Error::type_error(
337                format!("Cannot get key '{}' from {}", last_key, current.type_name()),
338                "table",
339                current.type_name(),
340            )),
341        }
342    }
343
344    /// Set a value by path, creating intermediate tables as needed (ZERO-COPY optimized)
345    pub fn set_nested(&mut self, path: &str, value: Value) -> Result<()> {
346        if path.is_empty() {
347            return Err(Error::key_not_found(""));
348        }
349
350        let parts: Vec<&str> = path.split('.').collect();
351        if parts.is_empty() {
352            return Err(Error::key_not_found(path));
353        }
354
355        let (last_key, parent_path) = parts
356            .split_last()
357            .ok_or_else(|| Error::key_not_found(path))?;
358
359        // Navigate to parent, creating tables as needed
360        let mut current = self;
361        for part in parent_path {
362            if let Value::Table(table) = current {
363                // ZERO-COPY: Use entry API to avoid string allocation when possible
364                let entry = table
365                    .entry((*part).to_string())
366                    .or_insert_with(|| Value::table(BTreeMap::new()));
367                current = entry;
368            } else {
369                return Err(Error::type_error(
370                    format!("Cannot navigate into {}", current.type_name()),
371                    "table",
372                    current.type_name(),
373                ));
374            }
375        }
376
377        // Set the final value
378        if let Value::Table(table) = current {
379            table.insert((*last_key).to_string(), value);
380            Ok(())
381        } else {
382            Err(Error::type_error(
383                format!("Cannot set key in {}", current.type_name()),
384                "table",
385                current.type_name(),
386            ))
387        }
388    }
389
390    /// Remove a value by path (ENTERPRISE ERROR HANDLING)
391    pub fn remove(&mut self, path: &str) -> Result<Option<Value>> {
392        if path.is_empty() {
393            let old = std::mem::replace(self, Value::Null);
394            return Ok(Some(old));
395        }
396
397        let parts: Vec<&str> = path.split('.').collect();
398        if parts.is_empty() {
399            return Err(Error::key_not_found(path));
400        }
401
402        let (last_key, parent_path) = parts
403            .split_last()
404            .ok_or_else(|| Error::key_not_found(path))?;
405
406        // Navigate to parent
407        let mut current = self;
408        for part in parent_path {
409            match current {
410                Value::Table(table) => {
411                    current = table
412                        .get_mut(*part)
413                        .ok_or_else(|| Error::key_not_found(*part))?;
414                }
415                _ => {
416                    return Err(Error::type_error(
417                        format!(
418                            "Cannot navigate into {} when removing key '{}'",
419                            current.type_name(),
420                            part
421                        ),
422                        "table",
423                        current.type_name(),
424                    ))
425                }
426            }
427        }
428
429        // Remove from parent
430        if let Value::Table(table) = current {
431            Ok(table.remove(*last_key))
432        } else {
433            Err(Error::type_error(
434                format!(
435                    "Cannot remove key '{}' from {}",
436                    last_key,
437                    current.type_name()
438                ),
439                "table",
440                current.type_name(),
441            ))
442        }
443    }
444
445    /// Get all keys at the current level (for tables only) - ZERO-COPY optimized
446    pub fn keys(&self) -> Result<Vec<&str>> {
447        match self {
448            Value::Table(table) => Ok(table.keys().map(|k| k.as_str()).collect()),
449            _ => Err(Error::type_error(
450                "Cannot get keys from non-table value",
451                "table",
452                self.type_name(),
453            )),
454        }
455    }
456
457    /// Check if a path exists
458    pub fn contains_key(&self, path: &str) -> bool {
459        self.get(path).is_some()
460    }
461
462    /// Set a value by path (backward compatibility alias)
463    pub fn set(&mut self, path: &str, value: Value) -> Result<()> {
464        self.set_nested(path, value)
465    }
466
467    /// Get the length of arrays or tables
468    pub fn len(&self) -> usize {
469        match self {
470            Value::Array(arr) => arr.len(),
471            Value::Table(table) => table.len(),
472            Value::String(s) => s.len(),
473            _ => 0,
474        }
475    }
476
477    /// Check if arrays or tables are empty
478    pub fn is_empty(&self) -> bool {
479        self.len() == 0
480    }
481}
482
483impl fmt::Display for Value {
484    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
485        match self {
486            Value::Null => write!(f, "null"),
487            Value::Bool(b) => write!(f, "{b}"),
488            Value::Integer(i) => write!(f, "{i}"),
489            Value::Float(fl) => write!(f, "{fl}"),
490            Value::String(s) => write!(f, "{s}"),
491            Value::Array(arr) => {
492                write!(f, "[")?;
493                for (i, item) in arr.iter().enumerate() {
494                    if i > 0 {
495                        write!(f, ", ")?;
496                    }
497                    write!(f, "{item}")?;
498                }
499                write!(f, "]")
500            }
501            Value::Table(table) => {
502                write!(f, "{{")?;
503                for (i, (key, value)) in table.iter().enumerate() {
504                    if i > 0 {
505                        write!(f, ", ")?;
506                    }
507                    write!(f, "{key}: {value}")?;
508                }
509                write!(f, "}}")
510            }
511            #[cfg(feature = "chrono")]
512            Value::DateTime(dt) => write!(f, "{}", dt.to_rfc3339()),
513        }
514    }
515}
516
517// ZERO-COPY conversion implementations
518impl From<bool> for Value {
519    fn from(value: bool) -> Self {
520        Value::Bool(value)
521    }
522}
523
524impl From<i32> for Value {
525    fn from(value: i32) -> Self {
526        Value::Integer(i64::from(value))
527    }
528}
529
530impl From<i64> for Value {
531    fn from(value: i64) -> Self {
532        Value::Integer(value)
533    }
534}
535
536impl From<f32> for Value {
537    fn from(value: f32) -> Self {
538        Value::Float(f64::from(value))
539    }
540}
541
542impl From<f64> for Value {
543    fn from(value: f64) -> Self {
544        Value::Float(value)
545    }
546}
547
548impl From<String> for Value {
549    fn from(value: String) -> Self {
550        Value::String(value)
551    }
552}
553
554impl From<&str> for Value {
555    fn from(value: &str) -> Self {
556        Value::String(value.to_string())
557    }
558}
559
560impl From<Vec<Value>> for Value {
561    fn from(value: Vec<Value>) -> Self {
562        Value::Array(value)
563    }
564}
565
566impl From<BTreeMap<String, Value>> for Value {
567    fn from(value: BTreeMap<String, Value>) -> Self {
568        Value::Table(value)
569    }
570}
571
572// ENTERPRISE: Helper functions for zero-copy operations
573impl Value {
574    /// Create a string value from a slice without unnecessary allocation
575    pub fn string_from_slice(value: &str) -> Self {
576        Value::String(value.to_string())
577    }
578
579    /// Get string slice without allocation - enterprise optimization
580    pub fn as_str(&self) -> Result<&str> {
581        match self {
582            Value::String(s) => Ok(s.as_str()),
583            _ => Err(Error::type_error(
584                "Value is not a string",
585                "string",
586                self.type_name(),
587            )),
588        }
589    }
590}
591
592#[cfg(test)]
593mod tests {
594    use super::*;
595
596    #[test]
597    fn test_value_creation() {
598        assert_eq!(Value::null(), Value::Null);
599        assert_eq!(Value::bool(true), Value::Bool(true));
600        assert_eq!(Value::integer(42), Value::Integer(42));
601        assert_eq!(Value::float(1.234), Value::Float(1.234));
602        assert_eq!(Value::string("test"), Value::String("test".to_string()));
603    }
604
605    #[test]
606    fn test_type_checking() {
607        let null = Value::null();
608        let bool_val = Value::bool(true);
609        let int_val = Value::integer(42);
610        let float_val = Value::float(5.678);
611        let string_val = Value::string("test");
612        let array_val = Value::array(vec![Value::integer(1), Value::integer(2)]);
613        let table_val = Value::table(BTreeMap::new());
614
615        assert!(null.is_null());
616        assert!(bool_val.is_bool());
617        assert!(int_val.is_integer());
618        assert!(float_val.is_float());
619        assert!(string_val.is_string());
620        assert!(array_val.is_array());
621        assert!(table_val.is_table());
622    }
623
624    #[test]
625    fn test_value_conversion() {
626        let bool_val = Value::bool(true);
627        let int_val = Value::integer(42);
628        let float_val = Value::float(1.234);
629        let string_val = Value::string("test");
630
631        assert!(bool_val.as_bool().unwrap());
632        assert_eq!(int_val.as_integer().unwrap(), 42);
633        assert_eq!(float_val.as_float().unwrap(), 1.234);
634        assert_eq!(string_val.as_string().unwrap(), "test");
635    }
636
637    #[test]
638    fn test_nested_access() {
639        let mut table = BTreeMap::new();
640        let mut inner_table = BTreeMap::new();
641        inner_table.insert("inner_key".to_string(), Value::string("inner_value"));
642        table.insert("outer_key".to_string(), Value::table(inner_table));
643
644        let value = Value::table(table);
645
646        assert_eq!(
647            value
648                .get("outer_key.inner_key")
649                .unwrap()
650                .as_string()
651                .unwrap(),
652            "inner_value"
653        );
654    }
655
656    #[test]
657    fn test_enterprise_error_handling() {
658        let mut value = Value::table(BTreeMap::new());
659
660        // Test proper error handling instead of panics
661        assert!(value.get_mut_nested("nonexistent.key").is_err());
662        assert!(value.remove("nonexistent.key").is_err());
663
664        // Test successful operations
665        assert!(value.set_nested("test.key", Value::string("value")).is_ok());
666        assert!(value.get("test.key").is_some());
667    }
668}