toon-format-rs 0.1.0

Token-Oriented Object Notation (TOON) parser and serializer for Rust
Documentation
use indexmap::IndexMap;
use std::fmt;

/// Represents any valid TOON value
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    /// Null value
    Null,
    /// Boolean value
    Bool(bool),
    /// Number value (stored as string for precision)
    Number(String),
    /// String value
    String(String),
    /// Array of values
    Array(Vec<Value>),
    /// Object with preserved key order
    Object(IndexMap<String, Value>),
}

impl Value {
    /// Creates a null value
    pub fn null() -> Self {
        Value::Null
    }

    /// Creates a boolean value
    pub fn bool(v: bool) -> Self {
        Value::Bool(v)
    }

    /// Creates a number value from an integer
    pub fn integer(v: i64) -> Self {
        Value::Number(v.to_string())
    }

    /// Creates a number value from a float
    pub fn float(v: f64) -> Self {
        // Use ryu for fast, accurate float formatting if available,
        // otherwise fallback to standard formatting
        let s = if v.fract() == 0.0 {
            format!("{:.0}", v)
        } else {
            format!("{}", v)
        };
        Value::Number(s)
    }

    /// Creates a string value
    pub fn string(v: impl Into<String>) -> Self {
        Value::String(v.into())
    }

    /// Creates an array value
    pub fn array(v: Vec<Value>) -> Self {
        Value::Array(v)
    }

    /// Creates an object value
    pub fn object(v: IndexMap<String, Value>) -> Self {
        Value::Object(v)
    }

    /// Returns true if the value is null
    pub fn is_null(&self) -> bool {
        matches!(self, Value::Null)
    }

    /// Returns true if the value is a boolean
    pub fn is_bool(&self) -> bool {
        matches!(self, Value::Bool(_))
    }

    /// Returns true if the value is a number
    pub fn is_number(&self) -> bool {
        matches!(self, Value::Number(_))
    }

    /// Returns true if the value is a string
    pub fn is_string(&self) -> bool {
        matches!(self, Value::String(_))
    }

    /// Returns true if the value is an array
    pub fn is_array(&self) -> bool {
        matches!(self, Value::Array(_))
    }

    /// Returns true if the value is an object
    pub fn is_object(&self) -> bool {
        matches!(self, Value::Object(_))
    }

    /// Returns the boolean value if it is a boolean
    pub fn as_bool(&self) -> Option<bool> {
        match self {
            Value::Bool(v) => Some(*v),
            _ => None,
        }
    }

    /// Returns the number as i64 if possible
    pub fn as_i64(&self) -> Option<i64> {
        match self {
            Value::Number(s) => s.parse().ok(),
            _ => None,
        }
    }

    /// Returns the number as f64 if possible
    pub fn as_f64(&self) -> Option<f64> {
        match self {
            Value::Number(s) => s.parse().ok(),
            _ => None,
        }
    }

    /// Returns the string value if it is a string
    pub fn as_str(&self) -> Option<&str> {
        match self {
            Value::String(s) => Some(s),
            _ => None,
        }
    }

    /// Returns the array if it is an array
    pub fn as_array(&self) -> Option<&Vec<Value>> {
        match self {
            Value::Array(a) => Some(a),
            _ => None,
        }
    }

    /// Returns the object if it is an object
    pub fn as_object(&self) -> Option<&IndexMap<String, Value>> {
        match self {
            Value::Object(o) => Some(o),
            _ => None,
        }
    }

    /// Returns a reference to the value at the given key if this is an object
    pub fn get(&self, key: &str) -> Option<&Value> {
        match self {
            Value::Object(o) => o.get(key),
            _ => None,
        }
    }

    /// Returns a reference to the value at the given index if this is an array
    pub fn get_index(&self, index: usize) -> Option<&Value> {
        match self {
            Value::Array(a) => a.get(index),
            _ => None,
        }
    }

    /// Returns the number of elements if array or object
    pub fn len(&self) -> usize {
        match self {
            Value::Array(a) => a.len(),
            Value::Object(o) => o.len(),
            _ => 0,
        }
    }

    /// Returns true if the value is empty (for arrays and objects)
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

impl Default for Value {
    fn default() -> Self {
        Value::Null
    }
}

impl From<()> for Value {
    fn from(_: ()) -> Self {
        Value::Null
    }
}

impl From<bool> for Value {
    fn from(v: bool) -> Self {
        Value::Bool(v)
    }
}

impl From<i8> for Value {
    fn from(v: i8) -> Self {
        Value::integer(v as i64)
    }
}

impl From<i16> for Value {
    fn from(v: i16) -> Self {
        Value::integer(v as i64)
    }
}

impl From<i32> for Value {
    fn from(v: i32) -> Self {
        Value::integer(v as i64)
    }
}

impl From<i64> for Value {
    fn from(v: i64) -> Self {
        Value::integer(v)
    }
}

impl From<u8> for Value {
    fn from(v: u8) -> Self {
        Value::integer(v as i64)
    }
}

impl From<u16> for Value {
    fn from(v: u16) -> Self {
        Value::integer(v as i64)
    }
}

impl From<u32> for Value {
    fn from(v: u32) -> Self {
        Value::integer(v as i64)
    }
}

impl From<u64> for Value {
    fn from(v: u64) -> Self {
        Value::Number(v.to_string())
    }
}

impl From<f32> for Value {
    fn from(v: f32) -> Self {
        Value::float(v as f64)
    }
}

impl From<f64> for Value {
    fn from(v: f64) -> Self {
        Value::float(v)
    }
}

impl From<String> for Value {
    fn from(v: String) -> Self {
        Value::String(v)
    }
}

impl From<&str> for Value {
    fn from(v: &str) -> Self {
        Value::String(v.to_string())
    }
}

impl<T: Into<Value>> From<Vec<T>> for Value {
    fn from(v: Vec<T>) -> Self {
        Value::Array(v.into_iter().map(Into::into).collect())
    }
}

impl<K: Into<String>, V: Into<Value>> From<IndexMap<K, V>> for Value {
    fn from(v: IndexMap<K, V>) -> Self {
        Value::Object(
            v.into_iter()
                .map(|(k, v)| (k.into(), v.into()))
                .collect(),
        )
    }
}

impl fmt::Display for Value {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Value::Null => write!(f, "null"),
            Value::Bool(true) => write!(f, "true"),
            Value::Bool(false) => write!(f, "false"),
            Value::Number(n) => write!(f, "{}", n),
            Value::String(s) => write!(f, "\"{}\"", s),
            Value::Array(arr) => {
                write!(f, "[")?;
                for (i, v) in arr.iter().enumerate() {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{}", v)?;
                }
                write!(f, "]")
            }
            Value::Object(obj) => {
                write!(f, "{{")?;
                for (i, (k, v)) in obj.iter().enumerate() {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "\"{}\": {}", k, v)?;
                }
                write!(f, "}}")
            }
        }
    }
}