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