apollo_smith/
fragment.rs

1use crate::directive::Directive;
2use crate::directive::DirectiveLocation;
3use crate::name::Name;
4use crate::selection_set::SelectionSet;
5use crate::ty::Ty;
6use crate::DocumentBuilder;
7use apollo_compiler::ast;
8use arbitrary::Result as ArbitraryResult;
9use indexmap::IndexMap;
10
11/// The __fragmentDef type represents a fragment definition
12///
13/// *FragmentDefinition*:
14///     fragment FragmentName TypeCondition Directives? SelectionSet
15///
16/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#FragmentDefinition).
17#[derive(Debug, Clone)]
18pub struct FragmentDef {
19    pub(crate) name: Name,
20    pub(crate) type_condition: TypeCondition,
21    pub(crate) directives: IndexMap<Name, Directive>,
22    pub(crate) selection_set: SelectionSet,
23}
24
25impl From<FragmentDef> for ast::Definition {
26    fn from(x: FragmentDef) -> Self {
27        ast::FragmentDefinition {
28            name: x.name.into(),
29            type_condition: x.type_condition.name.into(),
30            directives: Directive::to_ast(x.directives),
31            selection_set: x.selection_set.into(),
32        }
33        .into()
34    }
35}
36
37impl TryFrom<apollo_parser::cst::FragmentDefinition> for FragmentDef {
38    type Error = crate::FromError;
39
40    fn try_from(fragment_def: apollo_parser::cst::FragmentDefinition) -> Result<Self, Self::Error> {
41        Ok(Self {
42            name: fragment_def.fragment_name().unwrap().name().unwrap().into(),
43            directives: fragment_def
44                .directives()
45                .map(Directive::convert_directives)
46                .transpose()?
47                .unwrap_or_default(),
48            type_condition: fragment_def.type_condition().unwrap().into(),
49            selection_set: fragment_def.selection_set().unwrap().try_into()?,
50        })
51    }
52}
53
54/// The __fragmentSpread type represents a named fragment used in a selection set.
55///
56/// *FragmentSpread*:
57///     ... FragmentName Directives?
58///
59/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#FragmentSpread).
60#[derive(Debug, Clone)]
61pub struct FragmentSpread {
62    pub(crate) name: Name,
63    pub(crate) directives: IndexMap<Name, Directive>,
64}
65
66impl From<FragmentSpread> for ast::FragmentSpread {
67    fn from(x: FragmentSpread) -> Self {
68        Self {
69            fragment_name: x.name.into(),
70            directives: Directive::to_ast(x.directives),
71        }
72    }
73}
74
75impl TryFrom<apollo_parser::cst::FragmentSpread> for FragmentSpread {
76    type Error = crate::FromError;
77
78    fn try_from(fragment_spread: apollo_parser::cst::FragmentSpread) -> Result<Self, Self::Error> {
79        Ok(Self {
80            name: fragment_spread
81                .fragment_name()
82                .unwrap()
83                .name()
84                .unwrap()
85                .into(),
86            directives: fragment_spread
87                .directives()
88                .map(Directive::convert_directives)
89                .transpose()?
90                .unwrap_or_default(),
91        })
92    }
93}
94
95/// The __inlineFragment type represents an inline fragment in a selection set that could be used as a field
96///
97/// *InlineFragment*:
98///     ... TypeCondition? Directives? SelectionSet
99///
100/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Inline-Fragments).
101#[derive(Debug, Clone)]
102pub struct InlineFragment {
103    pub(crate) type_condition: Option<TypeCondition>,
104    pub(crate) directives: IndexMap<Name, Directive>,
105    pub(crate) selection_set: SelectionSet,
106}
107
108impl From<InlineFragment> for ast::InlineFragment {
109    fn from(x: InlineFragment) -> Self {
110        Self {
111            type_condition: x.type_condition.map(|t| t.name.into()),
112            directives: Directive::to_ast(x.directives),
113            selection_set: x.selection_set.into(),
114        }
115    }
116}
117
118impl TryFrom<apollo_parser::cst::InlineFragment> for InlineFragment {
119    type Error = crate::FromError;
120
121    fn try_from(inline_fragment: apollo_parser::cst::InlineFragment) -> Result<Self, Self::Error> {
122        Ok(Self {
123            directives: inline_fragment
124                .directives()
125                .map(Directive::convert_directives)
126                .transpose()?
127                .unwrap_or_default(),
128            selection_set: inline_fragment.selection_set().unwrap().try_into()?,
129            type_condition: inline_fragment.type_condition().map(TypeCondition::from),
130        })
131    }
132}
133
134/// The __typeCondition type represents where a fragment could be applied
135///
136/// *TypeCondition*:
137///     on NamedType
138///
139/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#TypeCondition).
140#[derive(Debug, Clone)]
141pub struct TypeCondition {
142    name: Name,
143}
144
145impl From<apollo_parser::cst::TypeCondition> for TypeCondition {
146    fn from(type_condition: apollo_parser::cst::TypeCondition) -> Self {
147        Self {
148            name: type_condition.named_type().unwrap().name().unwrap().into(),
149        }
150    }
151}
152
153impl DocumentBuilder<'_> {
154    /// Create an arbitrary `FragmentDef`
155    pub fn fragment_definition(&mut self) -> ArbitraryResult<FragmentDef> {
156        // TODO: also choose between enum/scalars/object
157        let selected_object_type_name = self.u.choose(&self.object_type_defs)?.name.clone();
158        let _ = self.stack_ty(&Ty::Named(selected_object_type_name));
159        let name = self.type_name()?;
160        let directives = self.directives(DirectiveLocation::FragmentDefinition)?;
161        let selection_set = self.selection_set()?;
162        let type_condition = self.type_condition()?;
163        self.stack.pop();
164
165        Ok(FragmentDef {
166            name,
167            type_condition,
168            directives,
169            selection_set,
170        })
171    }
172
173    /// Create an arbitrary `FragmentSpread`, returns `None` if no fragment definition was previously created
174    pub fn fragment_spread(
175        &mut self,
176        excludes: &mut Vec<Name>,
177    ) -> ArbitraryResult<Option<FragmentSpread>> {
178        let available_fragment: Vec<&FragmentDef> = self
179            .fragment_defs
180            .iter()
181            .filter(|f| excludes.contains(&f.name))
182            .collect();
183
184        let name = if available_fragment.is_empty() {
185            return Ok(None);
186        } else {
187            self.u.choose(&available_fragment)?.name.clone()
188        };
189        let directives = self.directives(DirectiveLocation::FragmentSpread)?;
190        excludes.push(name.clone());
191
192        Ok(Some(FragmentSpread { name, directives }))
193    }
194
195    /// Create an arbitrary `InlineFragment`
196    pub fn inline_fragment(&mut self) -> ArbitraryResult<InlineFragment> {
197        let type_condition = self
198            .u
199            .arbitrary()
200            .unwrap_or(false)
201            .then(|| self.type_condition())
202            .transpose()?;
203        let selection_set = self.selection_set()?;
204        let directives = self.directives(DirectiveLocation::InlineFragment)?;
205
206        Ok(InlineFragment {
207            type_condition,
208            directives,
209            selection_set,
210        })
211    }
212
213    /// Create an arbitrary `TypeCondition`
214    pub fn type_condition(&mut self) -> ArbitraryResult<TypeCondition> {
215        let last_element = self.stack.last();
216        match last_element {
217            Some(last_element) => Ok(TypeCondition {
218                name: last_element.name().clone(),
219            }),
220            None => {
221                let named_types: Vec<Ty> = self
222                    .list_existing_object_types()
223                    .into_iter()
224                    .filter(Ty::is_named)
225                    .collect();
226
227                Ok(TypeCondition {
228                    name: self.choose_named_ty(&named_types)?.name().clone(),
229                })
230            }
231        }
232    }
233}