use std::ops::Range;
use serde_json::{Map, Value};
use crate::{
error::HelperError,
helper::HelperResult,
json,
parser::ast::{Call, Node, Slice},
render::assert::{assert, Type},
};
#[derive(Debug, Eq, PartialEq)]
pub enum MissingValue {
Argument(usize, Value),
Parameter(String, Value),
}
#[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>,
missing: Vec<MissingValue>,
}
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>,
missing: Vec<MissingValue>,
) -> Self {
Self {
call,
name,
arguments,
parameters,
text,
property,
missing,
}
}
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 param(&self, name: &str) -> Option<&Value> {
self.parameters.get(name)
}
pub fn get_fallback(&self, index: usize) -> Option<&Value> {
let value = self.arguments.get(index);
if let Some(&Value::Null) = value {
if let Some(value) = self.missing(index) {
return Some(value);
}
}
value
}
pub fn param_fallback(&self, name: &str) -> Option<&Value> {
let value = self.parameters.get(name);
if let Some(&Value::Null) = value {
if let Some(value) = self.missing_param(name) {
return Some(value);
}
}
value
}
pub fn missing(&self, index: usize) -> Option<&Value> {
for m in self.missing.iter() {
if let MissingValue::Argument(ref i, ref value) = m {
if i == &index {
return Some(value);
}
}
}
None
}
pub fn missing_param(&self, name: &str) -> Option<&Value> {
for m in self.missing.iter() {
if let MissingValue::Parameter(ref key, ref value) = m {
if key == name {
return Some(value);
}
}
}
None
}
pub fn call(&self) -> &'call Call<'call> {
self.call
}
pub fn raw(&self, index: usize) -> Option<&str> {
self.call.arguments().get(index).map(|v| v.as_str())
}
pub fn raw_param(&self, name: &str) -> Option<&str> {
self.call.parameters().get(name).map(|v| v.as_str())
}
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_param(
&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 try_value<'a>(
&self,
value: &'a Value,
kinds: &[Type],
) -> HelperResult<&'a Value> {
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<()> {
let (result, kind) = assert(value, kinds);
if !result {
return Err(HelperError::TypeAssert(
self.name().to_string(),
kind.unwrap(),
Type::from(value).to_string(),
));
}
Ok(())
}
pub fn assert_block<'a>(
&self,
template: Option<&'a Node<'a>>,
) -> HelperResult<&'a Node<'a>> {
if let Some(node) = template {
return Ok(node);
}
Err(HelperError::BlockTemplate(self.name().to_string()))
}
pub fn assert_statement<'a>(
&self,
template: Option<&'a Node<'a>>) -> HelperResult<()> {
if template.is_some() {
return Err(HelperError::BlockTemplateNotAllowed(self.name().to_string()))
}
Ok(())
}
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)
}
}