use std::fmt;
use std::ops::Range;
use serde_json::{Map, Value};
use crate::{
error::HelperError, helper::HelperResult, json, parser::ast::Call,
};
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Type {
Null,
Bool,
Number,
String,
Object,
Array,
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", {
match *self {
Self::Null => "null",
Self::Bool => "boolean",
Self::Number => "number",
Self::String => "string",
Self::Object => "object",
Self::Array => "array",
}
})
}
}
impl From<&Value> for Type {
fn from(value: &Value) -> Self {
match value {
Value::Null => Self::Null,
Value::Bool(_) => Self::Bool,
Value::Number(_) => Self::Number,
Value::String(_) => Self::String,
Value::Object(_) => Self::Object,
Value::Array(_) => Self::Array,
}
}
}
#[derive(Debug)]
pub struct Property {
pub name: String,
pub value: Value,
}
pub struct Context<'call> {
call: &'call Call<'call>,
name: String,
arguments: Vec<Value>,
parameters: Map<String, Value>,
text: Option<&'call str>,
property: Option<Property>,
}
impl<'call> Context<'call> {
pub(crate) fn new(
call: &'call Call<'call>,
name: String,
arguments: Vec<Value>,
parameters: Map<String, Value>,
text: Option<&'call str>,
property: Option<Property>,
) -> Self {
Self {
call,
name,
arguments,
parameters,
text,
property,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn arguments(&self) -> &Vec<Value> {
&self.arguments
}
pub fn parameters(&self) -> &Map<String, Value> {
&self.parameters
}
pub fn get(&self, index: usize) -> Option<&Value> {
self.arguments.get(index)
}
pub fn hash(&self, name: &str) -> Option<&Value> {
self.parameters.get(name)
}
pub fn try_get(
&self,
index: usize,
kinds: &[Type],
) -> HelperResult<&Value> {
let value = self.arguments.get(index).or(Some(&Value::Null)).unwrap();
self.assert(value, kinds)?;
Ok(value)
}
pub fn try_hash(&self, name: &str, kinds: &[Type]) -> HelperResult<&Value> {
let value = self.parameters.get(name).or(Some(&Value::Null)).unwrap();
self.assert(value, kinds)?;
Ok(value)
}
pub fn text(&self) -> &Option<&'call str> {
&self.text
}
pub fn property(&self) -> &Option<Property> {
&self.property
}
pub fn arity(&self, range: Range<usize>) -> HelperResult<()> {
if range.start == range.end {
if self.arguments.len() != range.start {
return Err(HelperError::ArityExact(
self.name.clone(),
range.start,
));
}
} else {
if self.arguments.len() < range.start
|| self.arguments.len() >= range.end
{
return Err(HelperError::ArityRange(
self.name.clone(),
range.start,
range.end,
));
}
}
Ok(())
}
pub fn assert(&self, value: &Value, kinds: &[Type]) -> HelperResult<()> {
for kind in kinds {
if !self.assert_type(value, kind) {
return Err(HelperError::TypeAssert(
self.name().to_string(),
kind.to_string(),
Type::from(value).to_string(),
));
}
}
Ok(())
}
fn assert_type(&self, value: &Value, kind: &Type) -> bool {
match value {
Value::Null => kind == &Type::Null,
Value::Bool(_) => kind == &Type::Bool,
Value::String(_) => kind == &Type::String,
Value::Number(_) => kind == &Type::Number,
Value::Object(_) => kind == &Type::Object,
Value::Array(_) => kind == &Type::Array,
}
}
pub fn lookup<'a, S: AsRef<str>>(
&self,
target: &'a Value,
field: S,
) -> Option<&'a Value> {
json::find_field(target, field)
}
pub fn is_truthy(&self, value: &Value) -> bool {
json::is_truthy(value)
}
}