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<&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_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.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,
                        );
                    }
                }
            }
        }
    });
}