use std::collections::HashMap;
use super::OperationVisitorContext;
use crate::static_graphql::{
query::{self, Selection, TypeCondition},
schema::{self, TypeDefinition},
};
pub fn collect_fields<'a>(
selection_set: &query::SelectionSet,
parent_type: &schema::TypeDefinition,
known_fragments: &HashMap<&str, &query::FragmentDefinition>,
context: &'a OperationVisitorContext<'a>,
) -> HashMap<String, Vec<query::Field>> {
let mut map = HashMap::new();
let mut visited_fragments_names: Vec<String> = Vec::new();
collect_fields_inner(
selection_set,
parent_type,
known_fragments,
context,
&mut map,
&mut visited_fragments_names,
);
map
}
fn does_fragment_condition_match<'a>(
fragment_condition: &'a Option<TypeCondition>,
current_selection_set_type: &'a TypeDefinition,
context: &'a OperationVisitorContext<'a>,
) -> bool {
if let Some(TypeCondition::On(type_name)) = fragment_condition {
if let Some(conditional_type) = context.schema.type_by_name(type_name) {
if conditional_type
.name()
.eq(current_selection_set_type.name())
{
return true;
}
if conditional_type.is_abstract_type() {
match conditional_type {
TypeDefinition::Interface(interface_type) => {
return interface_type.is_implemented_by(current_selection_set_type)
}
TypeDefinition::Union(union_type) => {
return union_type.has_sub_type(current_selection_set_type.name())
}
_ => return false,
}
}
}
false
} else {
true
}
}
fn collect_fields_inner<'a>(
selection_set: &query::SelectionSet,
parent_type: &schema::TypeDefinition,
known_fragments: &HashMap<&str, &query::FragmentDefinition>,
context: &'a OperationVisitorContext<'a>,
result_arr: &mut HashMap<String, Vec<query::Field>>,
visited_fragments_names: &mut Vec<String>,
) {
selection_set.items.iter().for_each(|item| match item {
Selection::Field(f) => {
let existing = result_arr.entry(f.name.clone()).or_default();
existing.push(f.clone());
}
Selection::InlineFragment(f) => {
if does_fragment_condition_match(&f.type_condition, parent_type, context) {
collect_fields_inner(
&f.selection_set,
parent_type,
known_fragments,
context,
result_arr,
visited_fragments_names,
);
}
}
Selection::FragmentSpread(f) => {
if !visited_fragments_names
.iter()
.any(|name| f.fragment_name.eq(name))
{
visited_fragments_names.push(f.fragment_name.clone());
if let Some(fragment) = known_fragments.get(f.fragment_name.as_str()) {
if does_fragment_condition_match(
&Some(fragment.type_condition.clone()),
parent_type,
context,
) {
collect_fields_inner(
&fragment.selection_set,
parent_type,
known_fragments,
context,
result_arr,
visited_fragments_names,
);
}
}
}
}
});
}