bluejay_validator/executable/document/rules/
fragment_spread_is_possible.rs1use crate::executable::{
2 document::{Error, Path, Rule, Visitor},
3 Cache,
4};
5use bluejay_core::definition::{
6 ObjectTypeDefinition, SchemaDefinition, TypeDefinitionReference, UnionMemberType,
7 UnionTypeDefinition,
8};
9use bluejay_core::executable::{
10 ExecutableDocument, FragmentDefinition, FragmentSpread, InlineFragment,
11};
12use bluejay_core::AsIter;
13use std::collections::HashSet;
14
15pub struct FragmentSpreadIsPossible<'a, E: ExecutableDocument, S: SchemaDefinition> {
16 errors: Vec<Error<'a, E, S>>,
17 cache: &'a Cache<'a, E, S>,
18 schema_definition: &'a S,
19}
20
21impl<'a, E: ExecutableDocument, S: SchemaDefinition> Visitor<'a, E, S>
22 for FragmentSpreadIsPossible<'a, E, S>
23{
24 fn new(_: &'a E, schema_definition: &'a S, cache: &'a Cache<'a, E, S>) -> Self {
25 Self {
26 errors: Vec::new(),
27 cache,
28 schema_definition,
29 }
30 }
31
32 fn visit_fragment_spread(
33 &mut self,
34 fragment_spread: &'a <E as ExecutableDocument>::FragmentSpread,
35 parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
36 _path: &Path<'a, E>,
37 ) {
38 if let Some(fragment_definition) = self.cache.fragment_definition(fragment_spread.name()) {
39 if let Some(fragment_type) = self
40 .schema_definition
41 .get_type_definition(fragment_definition.type_condition())
42 {
43 if self.spread_is_not_possible(parent_type, fragment_type) {
44 self.errors.push(Error::FragmentSpreadIsNotPossible {
45 fragment_spread,
46 parent_type,
47 });
48 }
49 }
50 }
51 }
52
53 fn visit_inline_fragment(
54 &mut self,
55 inline_fragment: &'a <E as ExecutableDocument>::InlineFragment,
56 parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
57 ) {
58 if let Some(type_condition) = inline_fragment.type_condition() {
59 if let Some(fragment_type) = self.schema_definition.get_type_definition(type_condition)
60 {
61 if self.spread_is_not_possible(parent_type, fragment_type) {
62 self.errors.push(Error::InlineFragmentSpreadIsNotPossible {
63 inline_fragment,
64 parent_type,
65 });
66 }
67 }
68 }
69 }
70}
71
72impl<'a, E: ExecutableDocument, S: SchemaDefinition> FragmentSpreadIsPossible<'a, E, S> {
73 fn get_possible_types(
74 &self,
75 t: TypeDefinitionReference<'a, S::TypeDefinition>,
76 ) -> Option<HashSet<&'a str>> {
77 match t {
78 TypeDefinitionReference::Object(_) => Some(HashSet::from([t.name()])),
79 TypeDefinitionReference::Interface(itd) => Some(HashSet::from_iter(
80 self.schema_definition
81 .get_interface_implementors(itd)
82 .map(ObjectTypeDefinition::name),
83 )),
84 TypeDefinitionReference::Union(utd) => Some(HashSet::from_iter(
85 utd.union_member_types()
86 .iter()
87 .map(|union_member| union_member.name()),
88 )),
89 TypeDefinitionReference::BuiltinScalar(_)
90 | TypeDefinitionReference::CustomScalar(_)
91 | TypeDefinitionReference::Enum(_)
92 | TypeDefinitionReference::InputObject(_) => None,
93 }
94 }
95
96 fn spread_is_not_possible(
97 &self,
98 parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
99 fragment_type: TypeDefinitionReference<'a, S::TypeDefinition>,
100 ) -> bool {
101 let parent_type_possible_types = self.get_possible_types(parent_type);
102 let fragment_possible_types = self.get_possible_types(fragment_type);
103
104 matches!(
105 (parent_type_possible_types, fragment_possible_types),
106 (Some(parent_type_possible_types), Some(fragment_possible_types)) if parent_type_possible_types
107 .intersection(&fragment_possible_types)
108 .next()
109 .is_none(),
110 )
111 }
112}
113
114impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Rule<'a, E, S>
115 for FragmentSpreadIsPossible<'a, E, S>
116{
117 type Error = Error<'a, E, S>;
118 type Errors = std::vec::IntoIter<Error<'a, E, S>>;
119
120 fn into_errors(self) -> Self::Errors {
121 self.errors.into_iter()
122 }
123}