1use crate::directive::Directive;
2use crate::directive::DirectiveLocation;
3use crate::name::Name;
4use crate::operation::OperationDef;
5use crate::selection_set::SelectionSet;
6use crate::ty::Ty;
7use crate::DocumentBuilder;
8use apollo_compiler::ast;
9use arbitrary::Result as ArbitraryResult;
10use indexmap::IndexMap;
11use indexmap::IndexSet;
12
13#[derive(Debug, Clone)]
20pub struct FragmentDef {
21 pub(crate) name: Name,
22 pub(crate) type_condition: TypeCondition,
23 pub(crate) directives: IndexMap<Name, Directive>,
24 pub(crate) selection_set: SelectionSet,
25}
26
27impl From<FragmentDef> for ast::Definition {
28 fn from(x: FragmentDef) -> Self {
29 ast::FragmentDefinition {
30 name: x.name.into(),
31 type_condition: x.type_condition.name.into(),
32 directives: Directive::to_ast(x.directives),
33 selection_set: x.selection_set.into(),
34 }
35 .into()
36 }
37}
38
39impl TryFrom<apollo_parser::cst::FragmentDefinition> for FragmentDef {
40 type Error = crate::FromError;
41
42 fn try_from(fragment_def: apollo_parser::cst::FragmentDefinition) -> Result<Self, Self::Error> {
43 Ok(Self {
44 name: fragment_def.fragment_name().unwrap().name().unwrap().into(),
45 directives: fragment_def
46 .directives()
47 .map(Directive::convert_directives)
48 .transpose()?
49 .unwrap_or_default(),
50 type_condition: fragment_def.type_condition().unwrap().into(),
51 selection_set: fragment_def.selection_set().unwrap().try_into()?,
52 })
53 }
54}
55
56#[derive(Debug, Clone)]
63pub struct FragmentSpread {
64 pub(crate) name: Name,
65 pub(crate) directives: IndexMap<Name, Directive>,
66}
67
68impl From<FragmentSpread> for ast::FragmentSpread {
69 fn from(x: FragmentSpread) -> Self {
70 Self {
71 fragment_name: x.name.into(),
72 directives: Directive::to_ast(x.directives),
73 }
74 }
75}
76
77impl TryFrom<apollo_parser::cst::FragmentSpread> for FragmentSpread {
78 type Error = crate::FromError;
79
80 fn try_from(fragment_spread: apollo_parser::cst::FragmentSpread) -> Result<Self, Self::Error> {
81 Ok(Self {
82 name: fragment_spread
83 .fragment_name()
84 .unwrap()
85 .name()
86 .unwrap()
87 .into(),
88 directives: fragment_spread
89 .directives()
90 .map(Directive::convert_directives)
91 .transpose()?
92 .unwrap_or_default(),
93 })
94 }
95}
96
97#[derive(Debug, Clone)]
104pub struct InlineFragment {
105 pub(crate) type_condition: Option<TypeCondition>,
106 pub(crate) directives: IndexMap<Name, Directive>,
107 pub(crate) selection_set: SelectionSet,
108}
109
110impl From<InlineFragment> for ast::InlineFragment {
111 fn from(x: InlineFragment) -> Self {
112 Self {
113 type_condition: x.type_condition.map(|t| t.name.into()),
114 directives: Directive::to_ast(x.directives),
115 selection_set: x.selection_set.into(),
116 }
117 }
118}
119
120impl TryFrom<apollo_parser::cst::InlineFragment> for InlineFragment {
121 type Error = crate::FromError;
122
123 fn try_from(inline_fragment: apollo_parser::cst::InlineFragment) -> Result<Self, Self::Error> {
124 Ok(Self {
125 directives: inline_fragment
126 .directives()
127 .map(Directive::convert_directives)
128 .transpose()?
129 .unwrap_or_default(),
130 selection_set: inline_fragment.selection_set().unwrap().try_into()?,
131 type_condition: inline_fragment.type_condition().map(TypeCondition::from),
132 })
133 }
134}
135
136#[derive(Debug, Clone)]
143pub struct TypeCondition {
144 name: Name,
145}
146
147impl From<apollo_parser::cst::TypeCondition> for TypeCondition {
148 fn from(type_condition: apollo_parser::cst::TypeCondition) -> Self {
149 Self {
150 name: type_condition.named_type().unwrap().name().unwrap().into(),
151 }
152 }
153}
154
155impl DocumentBuilder<'_> {
156 pub fn fragment_definition(&mut self) -> ArbitraryResult<FragmentDef> {
158 let selected_object_type_name = self.u.choose(&self.object_type_defs)?.name.clone();
160 let _ = self.stack_ty(&Ty::Named(selected_object_type_name));
161 let name = self.type_name()?;
162 let directives = self.directives(DirectiveLocation::FragmentDefinition)?;
163 let selection_set = self.selection_set()?;
164 let type_condition = self.type_condition()?;
165 self.stack.pop();
166
167 Ok(FragmentDef {
168 name,
169 type_condition,
170 directives,
171 selection_set,
172 })
173 }
174
175 pub fn fragment_spread(
177 &mut self,
178 excludes: &mut Vec<Name>,
179 ) -> ArbitraryResult<Option<FragmentSpread>> {
180 let current_type = self.stack.last().map(|e| e.name().clone());
181 let available_fragment: Vec<&FragmentDef> = self
182 .fragment_defs
183 .iter()
184 .filter(|f| {
185 !excludes.contains(&f.name)
186 && self.fragment_spread_possible(&f.type_condition.name, current_type.as_ref())
187 })
188 .collect();
189
190 let name = if available_fragment.is_empty() {
191 return Ok(None);
192 } else {
193 self.u.choose(&available_fragment)?.name.clone()
194 };
195 let directives = self.directives(DirectiveLocation::FragmentSpread)?;
196 excludes.push(name.clone());
197
198 Ok(Some(FragmentSpread { name, directives }))
199 }
200
201 pub fn inline_fragment(&mut self) -> ArbitraryResult<InlineFragment> {
203 let type_condition = self
204 .u
205 .arbitrary()
206 .unwrap_or(false)
207 .then(|| self.type_condition())
208 .transpose()?;
209 let selection_set = self.selection_set()?;
210 let directives = self.directives(DirectiveLocation::InlineFragment)?;
211
212 Ok(InlineFragment {
213 type_condition,
214 directives,
215 selection_set,
216 })
217 }
218
219 fn fragment_spread_possible(&self, fragment_type: &Name, current_type: Option<&Name>) -> bool {
225 let Some(current) = current_type else {
226 return true;
227 };
228 let current_objects = self.possible_object_types(current);
229 let fragment_objects = self.possible_object_types(fragment_type);
230 current_objects.iter().any(|o| fragment_objects.contains(o))
231 }
232
233 fn possible_object_types(&self, type_name: &Name) -> IndexSet<Name> {
235 if self.object_type_defs.iter().any(|o| &o.name == type_name) {
236 return IndexSet::from([type_name.clone()]);
237 }
238 if let Some(u) = self.union_type_defs.iter().find(|u| &u.name == type_name) {
239 return u.members.clone();
240 }
241 self.object_type_defs
243 .iter()
244 .filter(|o| self.implements_graph.closure(&o.name).contains(type_name))
245 .map(|o| o.name.clone())
246 .collect()
247 }
248
249 pub fn type_condition(&mut self) -> ArbitraryResult<TypeCondition> {
251 let last_element = self.stack.last();
252 match last_element {
253 Some(last_element) => Ok(TypeCondition {
254 name: last_element.name().clone(),
255 }),
256 None => {
257 let named_types: Vec<Ty> = self
258 .list_existing_object_types()
259 .into_iter()
260 .filter(Ty::is_named)
261 .collect();
262
263 Ok(TypeCondition {
264 name: self.choose_named_ty(&named_types)?.name().clone(),
265 })
266 }
267 }
268 }
269}
270
271pub(crate) fn reachable_fragment_names(
279 operations: &[OperationDef],
280 fragments: &[FragmentDef],
281) -> IndexSet<Name> {
282 let mut reachable: IndexSet<Name> = IndexSet::new();
283 for op in operations {
284 op.selection_set.collect_fragment_spreads(&mut reachable);
285 }
286 let mut frontier: Vec<Name> = reachable.iter().cloned().collect();
287 while let Some(name) = frontier.pop() {
288 if let Some(frag) = fragments.iter().find(|f| f.name == name) {
289 let mut nested: IndexSet<Name> = IndexSet::new();
290 frag.selection_set.collect_fragment_spreads(&mut nested);
291 for n in nested {
292 if reachable.insert(n.clone()) {
293 frontier.push(n);
294 }
295 }
296 }
297 }
298 reachable
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 fn parse(src: &str) -> (Vec<OperationDef>, Vec<FragmentDef>) {
306 let cst = apollo_parser::Parser::new(src).parse();
307 assert!(cst.errors().next().is_none(), "parse errors: {src}");
308 let mut ops = vec![];
309 let mut frags = vec![];
310 for def in cst.document().definitions() {
311 match def {
312 apollo_parser::cst::Definition::OperationDefinition(o) => {
313 ops.push(o.try_into().unwrap())
314 }
315 apollo_parser::cst::Definition::FragmentDefinition(f) => {
316 frags.push(f.try_into().unwrap())
317 }
318 _ => panic!("unexpected definition in test input"),
319 }
320 }
321 (ops, frags)
322 }
323
324 fn names(items: &[&str]) -> IndexSet<Name> {
325 items.iter().map(|s| Name::new(s.to_string())).collect()
326 }
327
328 #[test]
329 fn no_operations_means_nothing_reachable() {
330 let (ops, frags) = parse("fragment A on T { __typename }");
331 let result = reachable_fragment_names(&ops, &frags);
332 assert!(result.is_empty());
333 }
334
335 #[test]
336 fn direct_spread_is_reachable() {
337 let (ops, frags) = parse(
338 "
339 query { ...A }
340 fragment A on T { __typename }
341 ",
342 );
343 let result = reachable_fragment_names(&ops, &frags);
344 assert_eq!(result, names(&["A"]));
345 }
346
347 #[test]
348 fn transitive_chain_is_reachable() {
349 let (ops, frags) = parse(
350 "
351 query { ...A }
352 fragment A on T { ...B }
353 fragment B on T { ...C }
354 fragment C on T { __typename }
355 ",
356 );
357 let result = reachable_fragment_names(&ops, &frags);
358 assert_eq!(result, names(&["A", "B", "C"]));
359 }
360
361 #[test]
362 fn orphan_chain_is_not_reachable() {
363 let (ops, frags) = parse(
365 "
366 fragment A on T { ...B }
367 fragment B on T { __typename }
368 ",
369 );
370 let result = reachable_fragment_names(&ops, &frags);
371 assert!(result.is_empty());
372 }
373
374 #[test]
375 fn unreferenced_fragment_among_used_ones_is_pruned() {
376 let (ops, frags) = parse(
377 "
378 query { ...A }
379 fragment A on T { __typename }
380 fragment B on T { __typename }
381 ",
382 );
383 let result = reachable_fragment_names(&ops, &frags);
384 assert_eq!(result, names(&["A"]));
385 }
386
387 #[test]
388 fn cycle_terminates() {
389 let (ops, frags) = parse(
391 "
392 query { ...A }
393 fragment A on T { ...B }
394 fragment B on T { ...A }
395 ",
396 );
397 let result = reachable_fragment_names(&ops, &frags);
398 assert_eq!(result, names(&["A", "B"]));
399 }
400}