1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use std::collections::HashMap;

use super::{AbstractTypeDefinitionExtension, OperationVisitorContext, SchemaDocumentExtension};
use crate::ast::ext::{SubTypeExtension, TypeDefinitionExtension};
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<String, 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<String, 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_insert(vec![]);
            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()
                .find(|name| f.fragment_name.eq(*name))
                .is_none()
            {
                visited_fragments_names.push(f.fragment_name.clone());

                if let Some(fragment) = known_fragments.get(&f.fragment_name) {
                    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,
                        );
                    }
                }
            }
        }
    });
}