online-dsl-forge 0.2.0

A bounded parser and runtime for in-memory DSL expressions.
Documentation
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::fmt;

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
pub enum Value {
  Null,
  Bool(bool),
  Int(i64),
  Float(f64),
  String(String),
  Array(Vec<Value>),
  Object(BTreeMap<String, Value>),
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ValueConversionError {
  message: String,
}

impl ValueConversionError {
  fn new(message: impl Into<String>) -> Self {
    Self {
      message: message.into(),
    }
  }
}

impl fmt::Display for ValueConversionError {
  fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    formatter.write_str(&self.message)
  }
}

impl std::error::Error for ValueConversionError {}

impl Value {
  pub fn type_name(&self) -> &'static str {
    match self {
      Self::Null => "null",
      Self::Bool(_) => "bool",
      Self::Int(_) => "int",
      Self::Float(_) => "float",
      Self::String(_) => "string",
      Self::Array(_) => "array",
      Self::Object(_) => "object",
    }
  }

  pub fn as_bool(&self) -> Option<bool> {
    match self {
      Self::Bool(value) => Some(*value),
      _ => None,
    }
  }

  pub fn is_number(&self) -> bool {
    matches!(self, Self::Int(_) | Self::Float(_))
  }
}

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

  fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
    match value {
      serde_json::Value::Null => Ok(Self::Null),
      serde_json::Value::Bool(value) => Ok(Self::Bool(value)),
      serde_json::Value::Number(number) => {
        if let Some(value) = number.as_i64() {
          Ok(Self::Int(value))
        } else if let Some(value) = number.as_f64() {
          Ok(Self::Float(value))
        } else {
          Err(ValueConversionError::new("unsupported JSON number"))
        }
      }
      serde_json::Value::String(value) => Ok(Self::String(value)),
      serde_json::Value::Array(values) => values
        .into_iter()
        .map(Value::try_from)
        .collect::<Result<Vec<_>, _>>()
        .map(Self::Array),
      serde_json::Value::Object(values) => values
        .into_iter()
        .map(|(key, value)| Value::try_from(value).map(|value| (key, value)))
        .collect::<Result<BTreeMap<_, _>, _>>()
        .map(Self::Object),
    }
  }
}

impl From<Value> for serde_json::Value {
  fn from(value: Value) -> Self {
    match value {
      Value::Null => Self::Null,
      Value::Bool(value) => Self::Bool(value),
      Value::Int(value) => Self::Number(value.into()),
      Value::Float(value) => serde_json::Number::from_f64(value)
        .map(Self::Number)
        .unwrap_or(Self::Null),
      Value::String(value) => Self::String(value),
      Value::Array(values) => Self::Array(values.into_iter().map(Self::from).collect()),
      Value::Object(values) => Self::Object(
        values
          .into_iter()
          .map(|(key, value)| (key, Self::from(value)))
          .collect(),
      ),
    }
  }
}