use std::collections::HashMap;
use serde_json::Value as JsonValue;
use super::types::DirectiveError;
use crate::graphql::types::{Directive, FieldSelection};
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); }
},
_ => {
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> {
match serde_json::from_str::<JsonValue>(value_json) {
Ok(JsonValue::Bool(b)) => Ok(b),
Ok(JsonValue::String(s)) if s.starts_with('$') => {
let var_name = &s[1..]; let val = variables
.get(var_name)
.ok_or_else(|| DirectiveError::UndefinedVariable(var_name.to_string()))?;
match val {
JsonValue::Bool(b) => Ok(*b),
_ => Err(DirectiveError::VariableTypeMismatch(var_name.to_string())),
}
},
Ok(_) => Err(DirectiveError::InvalidDirectiveArgument),
Err(_) => {
if let Some(var_name) = value_json.strip_prefix('$') {
let val = variables
.get(var_name)
.ok_or_else(|| DirectiveError::UndefinedVariable(var_name.to_string()))?;
match val {
JsonValue::Bool(b) => Ok(*b),
_ => Err(DirectiveError::VariableTypeMismatch(var_name.to_string())),
}
} else {
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> {
match serde_json::from_str::<JsonValue>(value_json) {
Ok(JsonValue::String(s)) if s.starts_with('$') => {
let var_name = &s[1..];
variables
.get(var_name)
.cloned()
.ok_or_else(|| DirectiveError::UndefinedVariable(var_name.to_string()))
},
Ok(value) => Ok(value),
Err(_) => {
if let Some(var_name) = value_json.strip_prefix('$') {
variables
.get(var_name)
.cloned()
.ok_or_else(|| DirectiveError::UndefinedVariable(var_name.to_string()))
} else {
Ok(JsonValue::String(value_json.to_string()))
}
},
}
}
}