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;
13
14pub struct FragmentSpreadIsPossible<'a, E: ExecutableDocument, S: SchemaDefinition> {
15 errors: Vec<Error<'a, E, S>>,
16 cache: &'a Cache<'a, E, S>,
17 schema_definition: &'a S,
18}
19
20impl<'a, E: ExecutableDocument, S: SchemaDefinition> Visitor<'a, E, S>
21 for FragmentSpreadIsPossible<'a, E, S>
22{
23 fn new(_: &'a E, schema_definition: &'a S, cache: &'a Cache<'a, E, S>) -> Self {
24 Self {
25 errors: Vec::new(),
26 cache,
27 schema_definition,
28 }
29 }
30
31 fn visit_fragment_spread(
32 &mut self,
33 fragment_spread: &'a <E as ExecutableDocument>::FragmentSpread,
34 parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
35 _path: &Path<'a, E>,
36 ) {
37 if let Some(fragment_definition) = self.cache.fragment_definition(fragment_spread.name()) {
38 if let Some(fragment_type) = self
39 .schema_definition
40 .get_type_definition(fragment_definition.type_condition())
41 {
42 if self.spread_is_not_possible(parent_type, fragment_type) {
43 self.errors.push(Error::FragmentSpreadIsNotPossible {
44 fragment_spread,
45 parent_type,
46 });
47 }
48 }
49 }
50 }
51
52 fn visit_inline_fragment(
53 &mut self,
54 inline_fragment: &'a <E as ExecutableDocument>::InlineFragment,
55 parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
56 ) {
57 if let Some(type_condition) = inline_fragment.type_condition() {
58 if let Some(fragment_type) = self.schema_definition.get_type_definition(type_condition)
59 {
60 if self.spread_is_not_possible(parent_type, fragment_type) {
61 self.errors.push(Error::InlineFragmentSpreadIsNotPossible {
62 inline_fragment,
63 parent_type,
64 });
65 }
66 }
67 }
68 }
69}
70
71impl<'a, E: ExecutableDocument, S: SchemaDefinition> FragmentSpreadIsPossible<'a, E, S> {
72 fn spread_is_not_possible(
73 &self,
74 parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
75 fragment_type: TypeDefinitionReference<'a, S::TypeDefinition>,
76 ) -> bool {
77 if !parent_type.is_composite() || !fragment_type.is_composite() {
79 return false;
80 }
81
82 if parent_type.name() == fragment_type.name() {
84 return false;
85 }
86
87 if matches!(parent_type, TypeDefinitionReference::Object(_))
89 && matches!(fragment_type, TypeDefinitionReference::Object(_))
90 {
91 return true;
92 }
93
94 !self.types_have_overlap(parent_type, fragment_type)
96 }
97
98 fn type_contains_name(
99 &self,
100 t: TypeDefinitionReference<'a, S::TypeDefinition>,
101 name: &str,
102 ) -> bool {
103 match t {
104 TypeDefinitionReference::Object(_) => t.name() == name,
105 TypeDefinitionReference::Interface(itd) => self
106 .schema_definition
107 .get_interface_implementors(itd)
108 .any(|otd| ObjectTypeDefinition::name(otd) == name),
109 TypeDefinitionReference::Union(utd) => utd
110 .union_member_types()
111 .iter()
112 .any(|member| member.name() == name),
113 _ => false,
114 }
115 }
116
117 fn types_have_overlap(
118 &self,
119 a: TypeDefinitionReference<'a, S::TypeDefinition>,
120 b: TypeDefinitionReference<'a, S::TypeDefinition>,
121 ) -> bool {
122 match (a, b) {
123 (TypeDefinitionReference::Object(o), other)
124 | (other, TypeDefinitionReference::Object(o)) => {
125 self.type_contains_name(other, ObjectTypeDefinition::name(o))
126 }
127 _ => {
129 let b_names: Vec<&str> = self.possible_type_names(b).collect();
130 self.possible_type_names(a)
131 .any(|name| b_names.contains(&name))
132 }
133 }
134 }
135
136 fn possible_type_names(
137 &self,
138 t: TypeDefinitionReference<'a, S::TypeDefinition>,
139 ) -> impl Iterator<Item = &'a str> + '_ {
140 use itertools::Either;
141 match t {
142 TypeDefinitionReference::Interface(itd) => Either::Left(
143 self.schema_definition
144 .get_interface_implementors(itd)
145 .map(ObjectTypeDefinition::name),
146 ),
147 TypeDefinitionReference::Union(utd) => Either::Right(Either::Left(
148 utd.union_member_types().iter().map(|m| m.name()),
149 )),
150 _ => Either::Right(Either::Right(std::iter::empty())),
151 }
152 }
153}
154
155impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Rule<'a, E, S>
156 for FragmentSpreadIsPossible<'a, E, S>
157{
158 type Error = Error<'a, E, S>;
159 type Errors = std::vec::IntoIter<Error<'a, E, S>>;
160
161 fn into_errors(self) -> Self::Errors {
162 self.errors.into_iter()
163 }
164}