use std::collections::HashMap;
use serde_json::Value as JsonValue;
use super::types::DirectiveError;
use crate::graphql::{
types::{Directive, FieldSelection},
value_json,
};
pub struct DirectiveEvaluator;
impl DirectiveEvaluator {
pub fn evaluate_directives(
selection: &FieldSelection,
variables: &HashMap<String, JsonValue>,
) -> Result<bool, DirectiveError> {
if selection.directives.is_empty() {
return Ok(true);
}
for directive in &selection.directives {
match directive.name.as_str() {
"skip" => {
if Self::evaluate_skip(directive, variables)? {
return Ok(false); }
},
"include" => {
if !Self::evaluate_include(directive, variables)? {
return Ok(false); }
},
"stream" | "defer" => {},
_ => {
tracing::warn!("Unknown directive @{}", directive.name);
},
}
}
Ok(true)
}
pub fn evaluate_skip(
directive: &Directive,
variables: &HashMap<String, JsonValue>,
) -> Result<bool, DirectiveError> {
let if_arg = directive
.arguments
.iter()
.find(|a| a.name == "if")
.ok_or(DirectiveError::MissingDirectiveArgument("if".to_string()))?;
Self::resolve_boolean_condition(&if_arg.value_json, variables)
}
pub fn evaluate_include(
directive: &Directive,
variables: &HashMap<String, JsonValue>,
) -> Result<bool, DirectiveError> {
let if_arg = directive
.arguments
.iter()
.find(|a| a.name == "if")
.ok_or(DirectiveError::MissingDirectiveArgument("if".to_string()))?;
Self::resolve_boolean_condition(&if_arg.value_json, variables)
}
fn resolve_boolean_condition(
value_json: &str,
variables: &HashMap<String, JsonValue>,
) -> Result<bool, DirectiveError> {
let parsed =
value_json::decode(value_json).map_err(|_| DirectiveError::InvalidDirectiveArgument)?;
if let Some(var_name) = value_json::variable_name(&parsed) {
let val = variables
.get(var_name)
.ok_or_else(|| DirectiveError::UndefinedVariable(var_name.to_string()))?;
return match val {
JsonValue::Bool(b) => Ok(*b),
_ => Err(DirectiveError::VariableTypeMismatch(var_name.to_string())),
};
}
match parsed {
JsonValue::Bool(b) => Ok(b),
_ => Err(DirectiveError::InvalidDirectiveArgument),
}
}
pub fn filter_selections(
selections: &[FieldSelection],
variables: &HashMap<String, JsonValue>,
) -> Result<Vec<FieldSelection>, DirectiveError> {
let mut result = Vec::new();
for selection in selections {
if Self::evaluate_directives(selection, variables)? {
let mut field = selection.clone();
if !field.nested_fields.is_empty() {
field.nested_fields = Self::filter_selections(&field.nested_fields, variables)?;
}
result.push(field);
}
}
Ok(result)
}
pub fn parse_directive_args(
directive: &Directive,
variables: &HashMap<String, JsonValue>,
) -> Result<HashMap<String, JsonValue>, DirectiveError> {
let mut args = HashMap::new();
for arg in &directive.arguments {
let value = Self::resolve_argument_value(&arg.value_json, variables)?;
args.insert(arg.name.clone(), value);
}
Ok(args)
}
fn resolve_argument_value(
value_json: &str,
variables: &HashMap<String, JsonValue>,
) -> Result<JsonValue, DirectiveError> {
let parsed =
value_json::decode(value_json).map_err(|_| DirectiveError::InvalidDirectiveArgument)?;
if let Some(var_name) = value_json::variable_name(&parsed) {
return variables
.get(var_name)
.cloned()
.ok_or_else(|| DirectiveError::UndefinedVariable(var_name.to_string()));
}
Ok(value_json::resolve_variables(parsed, variables))
}
}