apollo_encoder/
fragment.rs

1use std::fmt::{self, Write as _};
2
3use crate::{Directive, SelectionSet};
4
5/// The FragmentDefinition type represents a fragment definition
6///
7/// *FragmentDefinition*:
8///     fragment FragmentName TypeCondition Directives? SelectionSet
9///
10/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#FragmentDefinition).
11///
12/// ### Example
13/// ```rust
14/// use apollo_encoder::{Field, FragmentDefinition, Selection, SelectionSet, TypeCondition};
15/// use indoc::indoc;
16///
17/// let selections = vec![Selection::Field(Field::new(String::from("myField")))];
18/// let mut selection_set = SelectionSet::new();
19/// selections
20///     .into_iter()
21///     .for_each(|s| selection_set.selection(s));
22/// let mut fragment_def = FragmentDefinition::new(
23///     String::from("myFragment"),
24///     TypeCondition::new(String::from("User")),
25///     selection_set,
26/// );
27///
28/// assert_eq!(
29///     fragment_def.to_string(),
30///     indoc! {r#"
31///         fragment myFragment on User {
32///           myField
33///         }
34///     "#}
35/// );
36/// ```
37#[derive(Debug, Clone)]
38pub struct FragmentDefinition {
39    name: String,
40    type_condition: TypeCondition,
41    directives: Vec<Directive>,
42    selection_set: SelectionSet,
43}
44
45impl FragmentDefinition {
46    /// Create an instance of FragmentDefinition.
47    pub fn new(name: String, type_condition: TypeCondition, selection_set: SelectionSet) -> Self {
48        Self {
49            name,
50            type_condition,
51            selection_set,
52            directives: Vec::new(),
53        }
54    }
55
56    /// Add a directive.
57    pub fn directive(&mut self, directive: Directive) {
58        self.directives.push(directive)
59    }
60}
61
62impl fmt::Display for FragmentDefinition {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        let indent_level = 0;
65        write!(f, "fragment {} {}", self.name, self.type_condition)?;
66        for directive in &self.directives {
67            write!(f, " {directive}")?;
68        }
69        write!(
70            f,
71            " {}",
72            self.selection_set.format_with_indent(indent_level)
73        )?;
74
75        Ok(())
76    }
77}
78
79/// The FragmentSpread type represents a named fragment used in a selection set.
80///
81/// *FragmentSpread*:
82///     ... FragmentName Directives?
83///
84/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#FragmentSpread).
85///
86/// ### Example
87/// ```rust
88/// use apollo_encoder::FragmentSpread;
89///
90/// let fragment = FragmentSpread::new(String::from("myFragment"));
91/// assert_eq!(fragment.to_string(), r#"...myFragment"#);
92/// ```
93#[derive(Debug, Clone, PartialEq)]
94pub struct FragmentSpread {
95    name: String,
96    directives: Vec<Directive>,
97}
98
99impl FragmentSpread {
100    /// Create a new instance of FragmentSpread
101    pub fn new(name: String) -> Self {
102        Self {
103            name,
104            directives: Vec::new(),
105        }
106    }
107
108    /// Add a directive.
109    pub fn directive(&mut self, directive: Directive) {
110        self.directives.push(directive)
111    }
112}
113
114impl fmt::Display for FragmentSpread {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        write!(f, "...{}", self.name)?;
117        for directive in &self.directives {
118            write!(f, " {directive}")?;
119        }
120
121        Ok(())
122    }
123}
124
125/// The InlineFragment type represents an inline fragment in a selection set that could be used as a field
126///
127/// *InlineFragment*:
128///     ... TypeCondition? Directives? SelectionSet
129///
130/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Inline-Fragments).
131///
132/// ### Example
133/// ```rust
134/// use apollo_encoder::{Field, InlineFragment, Selection, SelectionSet, TypeCondition};
135/// use indoc::indoc;
136///
137/// let selections = vec![Selection::Field(Field::new(String::from("myField")))];
138/// let mut selection_set = SelectionSet::new();
139/// selections
140///     .into_iter()
141///     .for_each(|s| selection_set.selection(s));
142///
143/// let mut inline_fragment = InlineFragment::new(selection_set);
144/// inline_fragment.type_condition(Some(TypeCondition::new(String::from("User"))));
145///
146/// assert_eq!(
147///     inline_fragment.to_string(),
148///     indoc! {r#"
149///         ... on User {
150///           myField
151///         }
152///     "#}
153/// );
154/// ```
155#[derive(Debug, Clone, PartialEq)]
156pub struct InlineFragment {
157    type_condition: Option<TypeCondition>,
158    directives: Vec<Directive>,
159    selection_set: SelectionSet,
160    pub(crate) indent_level: usize,
161}
162
163impl InlineFragment {
164    /// Create an instance of InlineFragment
165    pub fn new(selection_set: SelectionSet) -> Self {
166        Self {
167            selection_set,
168            type_condition: Option::default(),
169            directives: Vec::new(),
170            indent_level: 0,
171        }
172    }
173
174    /// Add a directive.
175    pub fn directive(&mut self, directive: Directive) {
176        self.directives.push(directive)
177    }
178
179    /// Set the inline fragment's type condition.
180    pub fn type_condition(&mut self, type_condition: Option<TypeCondition>) {
181        self.type_condition = type_condition;
182    }
183
184    /// Should be used everywhere in this crate isntead of the Display implementation
185    /// Display implementation is only useful as a public api
186    pub(crate) fn format_with_indent(&self, indent_level: usize) -> String {
187        let mut text = String::from("...");
188
189        if let Some(type_condition) = &self.type_condition {
190            let _ = write!(text, " {type_condition}");
191        }
192        for directive in &self.directives {
193            let _ = write!(text, " {directive}");
194        }
195
196        let _ = write!(
197            text,
198            " {}",
199            self.selection_set.format_with_indent(indent_level),
200        );
201
202        text
203    }
204}
205
206// This impl is only useful when we generate only an InlineFragment
207// If it's used from a parent element, we call `format_with_indent`
208impl fmt::Display for InlineFragment {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        let indent_level = 0;
211        write!(f, "{}", self.format_with_indent(indent_level))
212    }
213}
214
215/// The TypeCondition type represents where a fragment could be applied
216///
217/// *TypeCondition*:
218///     on NamedType
219///
220/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#TypeCondition).
221#[derive(Debug, Clone, PartialEq, Eq)]
222pub struct TypeCondition {
223    name: String,
224}
225
226impl TypeCondition {
227    /// Create an instance of TypeCondition
228    pub fn new(name: String) -> Self {
229        Self { name }
230    }
231}
232
233impl fmt::Display for TypeCondition {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        write!(f, "on {}", self.name)
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use indoc::indoc;
242
243    use crate::{field::Field, Argument, Selection, Value};
244
245    use super::*;
246
247    #[test]
248    fn it_encodes_simple_inline_fragment() {
249        let selections = vec![Selection::Field(Field::new(String::from("myField")))];
250        let mut selection_set = SelectionSet::new();
251        selections
252            .into_iter()
253            .for_each(|s| selection_set.selection(s));
254
255        let mut inline_fragment = InlineFragment::new(selection_set);
256        inline_fragment.type_condition(Some(TypeCondition::new(String::from("User"))));
257
258        assert_eq!(
259            inline_fragment.to_string(),
260            indoc! {r#"
261                ... on User {
262                  myField
263                }
264            "#}
265        );
266    }
267
268    #[test]
269    fn it_encodes_simple_fragment_spread() {
270        let fragment = FragmentSpread::new(String::from("myFragment"));
271
272        assert_eq!(fragment.to_string(), indoc! {r#"...myFragment"#});
273    }
274
275    #[test]
276    fn it_encodes_deeper_inline_fragment() {
277        let another_nested_field_bis = Field::new(String::from("anotherNestedBisField"));
278
279        let mut another_nested_field = Field::new(String::from("anotherNestedField"));
280        let mut selection_set = SelectionSet::new();
281        selection_set.selection(Selection::Field(another_nested_field_bis));
282        another_nested_field.selection_set(selection_set.into());
283
284        let mut selection_set = SelectionSet::new();
285        selection_set.selection(Selection::Field(another_nested_field.clone()));
286        another_nested_field.selection_set(selection_set.into());
287
288        let nested_selections = vec![
289            Selection::Field(Field::new(String::from("nestedField"))),
290            Selection::Field(another_nested_field),
291        ];
292        let mut nested_selection_set = SelectionSet::new();
293        nested_selections
294            .into_iter()
295            .for_each(|s| nested_selection_set.selection(s));
296
297        let other_inline_fragment = InlineFragment::new(nested_selection_set);
298        let selections = vec![
299            Selection::Field(Field::new(String::from("myField"))),
300            Selection::FragmentSpread(FragmentSpread::new(String::from("myFragment"))),
301            Selection::InlineFragment(other_inline_fragment),
302        ];
303        let mut selection_set = SelectionSet::new();
304        selections
305            .into_iter()
306            .for_each(|s| selection_set.selection(s));
307
308        let mut inline_fragment = InlineFragment::new(selection_set);
309        inline_fragment.type_condition(Some(TypeCondition::new(String::from("User"))));
310
311        assert_eq!(
312            inline_fragment.to_string(),
313            indoc! {r#"
314                ... on User {
315                  myField
316                  ...myFragment
317                  ... {
318                    nestedField
319                    anotherNestedField {
320                      anotherNestedField {
321                        anotherNestedBisField
322                      }
323                    }
324                  }
325                }
326            "#}
327        );
328    }
329
330    #[test]
331    fn it_encodes_fragment_definition() {
332        let selections = vec![Selection::Field(Field::new(String::from("myField")))];
333        let mut selection_set = SelectionSet::new();
334        selections
335            .into_iter()
336            .for_each(|s| selection_set.selection(s));
337        let mut fragment_def = FragmentDefinition::new(
338            String::from("myFragment"),
339            TypeCondition::new(String::from("User")),
340            selection_set,
341        );
342        let mut directive = Directive::new(String::from("myDirective"));
343        directive.arg(Argument::new(String::from("first"), Value::Int(5)));
344        fragment_def.directive(directive);
345
346        assert_eq!(
347            fragment_def.to_string(),
348            indoc! {r#"
349                fragment myFragment on User @myDirective(first: 5) {
350                  myField
351                }
352            "#}
353        );
354    }
355}