use crate::{Context, Value, err::DirectiveError, traits::ToAstring};
use std::borrow::Cow;
#[derive(Debug, Clone)]
pub enum Argument {
Variable(Cow<'static, str>),
Literal(Cow<'static, str>),
Expression(Box<Expression>),
}
#[derive(Debug, Clone)]
pub enum ComparisonOp {
Equals,
NotEquals,
GreaterThan,
LessThan,
GreaterThanEquals,
LessThanEquals,
}
#[derive(Debug, Clone)]
pub enum Expression {
Comparison {
left: Argument,
op: ComparisonOp,
right: Argument,
},
Not(Argument),
}
impl Argument {
pub fn variable(name: Cow<'static, str>) -> Self {
Self::Variable(name)
}
pub fn literal(s: Cow<'static, str>) -> Self {
Self::Literal(s)
}
pub fn expression(expr: Expression) -> Self {
Self::Expression(Box::new(expr))
}
pub fn comparison(left: Self, op: ComparisonOp, right: Self) -> Self {
Self::Expression(Box::new(Expression::Comparison { left, op, right }))
}
pub fn not(arg: Self) -> Self {
Self::Expression(Box::new(Expression::Not(arg)))
}
}
pub trait Resolvable: Sized {
const TYPE_NAME: &'static str;
fn from_value(value: &Value) -> Option<Self>;
fn from_string_slice(s: &str) -> Result<Self, String>;
}
impl Argument {
pub fn resolve_as<T: Resolvable>(&self, ctx: &Context) -> Result<T, DirectiveError> {
match self {
Self::Variable(name) => {
if let Some(value) = ctx.get(name.as_ref()) {
if let Some(parsed) = T::from_value(value) {
return Ok(parsed);
}
return Err(DirectiveError::TypeError {
name: name.to_string(),
expected: T::TYPE_NAME,
found: value.type_name().to_string(),
});
}
Err(DirectiveError::NotFound {
name: name.to_string(),
type_name: T::TYPE_NAME,
})
}
Self::Literal(value) => {
T::from_string_slice(value).map_err(|err| DirectiveError::ParseError {
value: value.to_string(),
type_name: T::TYPE_NAME,
message: err,
})
}
Self::Expression(expr) => {
let result = expr.evaluate(ctx)?;
T::from_value(&result).ok_or_else(|| DirectiveError::TypeError {
name: "expression".to_string(),
expected: T::TYPE_NAME,
found: result.type_name().to_string(),
})
}
}
}
}
impl Expression {
pub fn evaluate(&self, ctx: &Context) -> Result<crate::Value, DirectiveError> {
match self {
Self::Comparison { left, op, right } => {
let left_str = left.resolve_as::<Cow<'static, str>>(ctx)?;
let right_str = right.resolve_as::<Cow<'static, str>>(ctx)?;
let result = match op {
ComparisonOp::Equals => left_str == right_str,
ComparisonOp::NotEquals => left_str != right_str,
ComparisonOp::GreaterThan => {
if let (Ok(l), Ok(r)) = (left_str.parse::<f64>(), right_str.parse::<f64>())
{
l > r
} else {
left_str > right_str
}
}
ComparisonOp::LessThan => {
if let (Ok(l), Ok(r)) = (left_str.parse::<f64>(), right_str.parse::<f64>())
{
l < r
} else {
left_str < right_str
}
}
ComparisonOp::GreaterThanEquals => {
if let (Ok(l), Ok(r)) = (left_str.parse::<f64>(), right_str.parse::<f64>())
{
l >= r
} else {
left_str >= right_str
}
}
ComparisonOp::LessThanEquals => {
if let (Ok(l), Ok(r)) = (left_str.parse::<f64>(), right_str.parse::<f64>())
{
l <= r
} else {
left_str <= right_str
}
}
};
Ok(Value::Bool(result))
}
Self::Not(arg) => {
let value = arg.resolve_as::<bool>(ctx)?;
Ok(Value::Bool(!value))
}
}
}
}
impl Resolvable for Cow<'static, str> {
const TYPE_NAME: &'static str = "string";
fn from_value(value: &Value) -> Option<Self> {
match value {
Value::Str(v) => Some(v.clone()),
Value::Int(v) => Some(Cow::Owned(v.to_astring())),
Value::Float(v) => Some(Cow::Owned(v.to_astring())),
Value::Bool(v) => Some(Cow::Owned(v.to_string())),
}
}
fn from_string_slice(s: &str) -> Result<Self, String> {
Ok(Cow::Owned(s.to_string()))
}
}
impl Resolvable for i64 {
const TYPE_NAME: &'static str = "i64";
fn from_value(value: &Value) -> Option<Self> {
match value {
Value::Str(v) => v.parse().ok(),
Value::Int(v) => Some(*v),
Value::Float(v) => Some(*v as Self),
Value::Bool(v) => Some(*v as Self),
}
}
fn from_string_slice(s: &str) -> Result<Self, String> {
s.parse::<Self>().map_err(|e| e.to_string())
}
}
impl Resolvable for f64 {
const TYPE_NAME: &'static str = "float";
fn from_value(value: &Value) -> Option<Self> {
match value {
Value::Str(v) => v.parse().ok(),
Value::Int(v) => Some(*v as Self),
Value::Float(v) => Some(*v),
Value::Bool(v) => Some(if *v { 1.0 } else { 0.0 }),
}
}
fn from_string_slice(s: &str) -> Result<Self, String> {
s.parse::<Self>().map_err(|e| e.to_string())
}
}
impl Resolvable for bool {
const TYPE_NAME: &'static str = "bool";
fn from_value(value: &Value) -> Option<Self> {
match value {
Value::Str(v) => v.parse().ok(),
Value::Int(v) => Some(*v != 0),
Value::Float(v) => Some(*v != 0.0),
Value::Bool(v) => Some(*v),
}
}
fn from_string_slice(s: &str) -> Result<Self, String> {
s.parse::<Self>().map_err(|e| e.to_string())
}
}