gunny 0.3.0

A library for rendering static text content from templates.
Documentation
use std::fmt::Display;

use ordermap::OrderMap;
use serde::{
    Serialize,
    ser::{SerializeMap, SerializeSeq},
};

use crate::{errors::JsonError, number::Number};

/// A single, typed variable value.
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Value {
    Null,
    Bool(bool),
    Number(Number),
    String(String),
    Array(Vec<Value>),
    Map(OrderMap<String, Value>),
}

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

impl From<u8> for Value {
    fn from(value: u8) -> Self {
        Self::Number(value.into())
    }
}

impl From<u16> for Value {
    fn from(value: u16) -> Self {
        Self::Number(value.into())
    }
}

impl From<u32> for Value {
    fn from(value: u32) -> Self {
        Self::Number(value.into())
    }
}

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

impl From<i8> for Value {
    fn from(value: i8) -> Self {
        Self::Number(value.into())
    }
}

impl From<i16> for Value {
    fn from(value: i16) -> Self {
        Self::Number(value.into())
    }
}

impl From<i32> for Value {
    fn from(value: i32) -> Self {
        Self::Number(value.into())
    }
}

impl From<i64> for Value {
    fn from(value: i64) -> Self {
        Self::Number(value.into())
    }
}

impl From<f32> for Value {
    fn from(value: f32) -> Self {
        Self::Number(value.into())
    }
}

impl From<f64> for Value {
    fn from(value: f64) -> Self {
        Self::Number(value.into())
    }
}

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

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

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

impl TryFrom<serde_json::Value> for Value {
    type Error = JsonError;

    fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
        Ok(match value {
            serde_json::Value::Null => Self::Null,
            serde_json::Value::Bool(value) => Self::Bool(value),
            serde_json::Value::Number(value) => Self::Number(value.try_into()?),
            serde_json::Value::String(value) => Self::String(value),
            serde_json::Value::Array(values) => Self::Array(
                values
                    .into_iter()
                    .map(Value::try_from)
                    .collect::<Result<Vec<Value>, Self::Error>>()?,
            ),
            serde_json::Value::Object(value) => Self::Map(value.into_iter().try_fold(
                OrderMap::new(),
                |mut acc, (k, v)| {
                    acc.insert(k, v.try_into()?);
                    Ok(acc)
                },
            )?),
        })
    }
}

impl Serialize for Value {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        match self {
            Self::Null => serializer.serialize_none(),
            Self::Bool(b) => serializer.serialize_bool(*b),
            Self::Number(n) => n.serialize(serializer),
            Self::String(s) => serializer.serialize_str(s),
            Self::Array(values) => {
                let mut seq = serializer.serialize_seq(Some(values.len()))?;
                for v in values {
                    seq.serialize_element(v)?;
                }
                seq.end()
            }
            Self::Map(value) => {
                let mut map = serializer.serialize_map(Some(value.len()))?;
                for (k, v) in value {
                    map.serialize_entry(k, v)?;
                }
                map.end()
            }
        }
    }
}

impl Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Value::Null => write!(f, "null"),
            Value::Bool(b) => write!(f, "{}", b),
            Value::Number(n) => write!(f, "{}", n),
            Value::String(s) => write!(f, "\"{}\"", s),
            Value::Array(values) => {
                write!(
                    f,
                    "[{}]",
                    values
                        .iter()
                        .map(|v| format!("{}", v))
                        .collect::<Vec<String>>()
                        .join(", ")
                )
            }
            Value::Map(map) => write!(
                f,
                "{{ {} }}",
                map.iter()
                    .map(|(k, v)| format!("{}: {}", k, v))
                    .collect::<Vec<String>>()
                    .join(",")
            ),
        }
    }
}

/// A mapping of variable names to values.
#[derive(Debug, Clone, PartialEq)]
pub struct Variables(OrderMap<String, Value>);

impl TryFrom<serde_json::Value> for Variables {
    type Error = JsonError;

    fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
        match value {
            serde_json::Value::Object(map) => Ok(Self(map.into_iter().try_fold(
                OrderMap::new(),
                |mut acc, (k, v)| {
                    acc.insert(k, v.try_into()?);
                    Ok(acc)
                },
            )?)),
            _ => Err(JsonError::ExpectingAnObject),
        }
    }
}

impl From<OrderMap<String, Value>> for Variables {
    fn from(value: OrderMap<String, Value>) -> Self {
        Self(value)
    }
}

impl Serialize for Variables {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut map = serializer.serialize_map(Some(self.0.len()))?;
        for (k, v) in &self.0 {
            map.serialize_entry(k, v)?;
        }
        map.end()
    }
}

impl Display for Variables {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{{ {} }}",
            self.0
                .iter()
                .map(|(k, v)| { format!("{}: {}", k, v) })
                .collect::<Vec<String>>()
                .join(", ")
        )
    }
}

impl Variables {
    /// Constructs an empty set of variables.
    pub fn new() -> Self {
        Self(OrderMap::new())
    }

    /// Set the variable with the given name to the specified value.
    pub fn set<S: AsRef<str>, V: Into<Value>>(&mut self, name: S, value: V) {
        self.0.insert(name.as_ref().to_string(), value.into());
    }

    /// Get the value of the variable with the given name, if it exists. Returns None if no such
    /// variable exists.
    pub fn get<S: AsRef<str>>(&self, name: S) -> Option<&Value> {
        self.0.get(name.as_ref())
    }

    /// Merge this set of variables with the given set of other variables. Other variables take
    /// precedence when similarly named variables exist.
    pub fn merge(&mut self, other: &Self) -> &Self {
        for (k, v) in &other.0 {
            self.0.insert(k.to_string(), v.clone());
        }
        self
    }
}

#[macro_export]
macro_rules! vars {
    {$($k:expr => $v:expr),* $(,)?} => {
        {
            let mut temp_vars = Variables::new();
            $(
                temp_vars.set($k, $v);
            )*
            temp_vars
        }
    };
}

#[macro_export]
macro_rules! map {
    {$($k:expr => $v:expr),* $(,)?} => {
        {
            let mut temp_map = ordermap::OrderMap::new();
            $(
                temp_map.insert($k.to_string(), $v.into());
            )*
            Value::Map(temp_map)
        }
    };
}

#[cfg(test)]
mod test {
    use crate::variables::{Value, Variables};

    #[test]
    fn can_load_variables_from_json() {
        let test_cases = vec![
            (r#"{"string": "value"}"#, vars! {"string" => "value"}),
            (r#"{"number": 123}"#, vars! {"number" => 123u64}),
            (
                r#"{"null": null, "bool": true, "number": -1, "string": "something"}"#,
                vars! {
                    "null" => Value::Null,
                    "bool" => true,
                    "number" => -1i64,
                    "string" => "something",
                },
            ),
            (
                r#"{"strings": ["hello", "world"]}"#,
                vars! {
                    "strings" => vec!["hello", "world"],
                },
            ),
            (
                r#"{"object": {"hello": 123, "object": true, "number": false}}"#,
                vars! {
                    "object" => map! {
                        "hello" => 123,
                        "object" => true,
                        "number" => false,
                    },
                },
            ),
        ];

        for test_case in test_cases {
            let (json, expected_variables) = test_case;
            let json_value = serde_json::from_str::<serde_json::Value>(json)
                .expect("to be able to deserialize the JSON string");
            let actual_variables = Variables::try_from(json_value)
                .expect("to be able to convert a JSON value to variables");
            assert_eq!(actual_variables, expected_variables,);
        }
    }
}