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
116
117
118
119
120
121
122
123
124
125
126
use crate::executable::{Cache, Error, Path, Rule, Visitor};
use bluejay_core::definition::{
    ObjectTypeDefinition, SchemaDefinition, TypeDefinitionReference, UnionMemberType,
    UnionTypeDefinition,
};
use bluejay_core::executable::{
    ExecutableDocument, FragmentDefinition, FragmentSpread, InlineFragment,
};
use bluejay_core::AsIter;
use std::collections::HashSet;

pub struct FragmentSpreadIsPossible<'a, E: ExecutableDocument, S: SchemaDefinition> {
    errors: Vec<Error<'a, E, S>>,
    cache: &'a Cache<'a, E, S>,
    schema_definition: &'a S,
}

impl<'a, E: ExecutableDocument, S: SchemaDefinition> Visitor<'a, E, S>
    for FragmentSpreadIsPossible<'a, E, S>
{
    fn visit_fragment_spread(
        &mut self,
        fragment_spread: &'a <E as ExecutableDocument>::FragmentSpread,
        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
        _path: &Path<'a, E>,
    ) {
        if let Some(fragment_definition) = self.cache.fragment_definition(fragment_spread.name()) {
            if let Some(fragment_type) = self
                .schema_definition
                .get_type_definition(fragment_definition.type_condition())
            {
                if self.spread_is_not_possible(parent_type, fragment_type) {
                    self.errors.push(Error::FragmentSpreadIsNotPossible {
                        fragment_spread,
                        parent_type,
                    });
                }
            }
        }
    }

    fn visit_inline_fragment(
        &mut self,
        inline_fragment: &'a <E as ExecutableDocument>::InlineFragment,
        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
    ) {
        if let Some(type_condition) = inline_fragment.type_condition() {
            if let Some(fragment_type) = self.schema_definition.get_type_definition(type_condition)
            {
                if self.spread_is_not_possible(parent_type, fragment_type) {
                    self.errors.push(Error::InlineFragmentSpreadIsNotPossible {
                        inline_fragment,
                        parent_type,
                    });
                }
            }
        }
    }
}

impl<'a, E: ExecutableDocument, S: SchemaDefinition> FragmentSpreadIsPossible<'a, E, S> {
    fn get_possible_types(
        &self,
        t: TypeDefinitionReference<'a, S::TypeDefinition>,
    ) -> Option<HashSet<&'a str>> {
        match t {
            TypeDefinitionReference::Object(_) => Some(HashSet::from([t.name()])),
            TypeDefinitionReference::Interface(itd) => Some(HashSet::from_iter(
                self.schema_definition
                    .get_interface_implementors(itd)
                    .map(ObjectTypeDefinition::name),
            )),
            TypeDefinitionReference::Union(utd) => Some(HashSet::from_iter(
                utd.union_member_types()
                    .iter()
                    .map(|union_member| union_member.member_type().name()),
            )),
            TypeDefinitionReference::BuiltinScalar(_)
            | TypeDefinitionReference::CustomScalar(_)
            | TypeDefinitionReference::Enum(_)
            | TypeDefinitionReference::InputObject(_) => None,
        }
    }

    fn spread_is_not_possible(
        &self,
        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
        fragment_type: TypeDefinitionReference<'a, S::TypeDefinition>,
    ) -> bool {
        let parent_type_possible_types = self.get_possible_types(parent_type);
        let fragment_possible_types = self.get_possible_types(fragment_type);

        matches!(
            (parent_type_possible_types, fragment_possible_types),
            (Some(parent_type_possible_types), Some(fragment_possible_types)) if parent_type_possible_types
                .intersection(&fragment_possible_types)
                .next()
                .is_none(),
        )
    }
}

impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> IntoIterator
    for FragmentSpreadIsPossible<'a, E, S>
{
    type Item = Error<'a, E, S>;
    type IntoIter = std::vec::IntoIter<Error<'a, E, S>>;

    fn into_iter(self) -> Self::IntoIter {
        self.errors.into_iter()
    }
}

impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Rule<'a, E, S>
    for FragmentSpreadIsPossible<'a, E, S>
{
    type Error = Error<'a, E, S>;

    fn new(_: &'a E, schema_definition: &'a S, cache: &'a Cache<'a, E, S>) -> Self {
        Self {
            errors: Vec::new(),
            cache,
            schema_definition,
        }
    }
}