apollo_encoder/
selection_set.rs

1use std::fmt::{self, Write as _};
2
3use crate::{Field, FragmentSpread, InlineFragment};
4
5/// The SelectionSet type represents a selection_set type in a fragment spread,
6/// an operation or a field
7///
8/// *SelectionSet*:
9///     Selection*
10///
11/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Selection-Sets).
12///
13/// ### Example
14/// ```rust
15/// use apollo_encoder::{Field, FragmentSpread, Selection, SelectionSet, TypeCondition};
16/// use indoc::indoc;
17///
18/// let selections = vec![
19///     Selection::Field(Field::new(String::from("myField"))),
20///     Selection::FragmentSpread(FragmentSpread::new(String::from("myFragment"))),
21/// ];
22/// let mut selection_set = SelectionSet::new();
23/// selections
24///     .into_iter()
25///     .for_each(|s| selection_set.selection(s));
26///
27/// assert_eq!(
28///     selection_set.to_string(),
29///     indoc! {r#"
30///         {
31///           myField
32///           ...myFragment
33///         }
34///     "#}
35/// )
36/// ```
37#[derive(Debug, PartialEq, Clone, Default)]
38pub struct SelectionSet {
39    selections: Vec<Selection>,
40}
41
42impl SelectionSet {
43    /// Create an instance of SelectionSet
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    /// Create an instance of SelectionSet given its selections
49    pub fn with_selections(selections: Vec<Selection>) -> Self {
50        Self { selections }
51    }
52
53    /// Add a selection in the SelectionSet
54    pub fn selection(&mut self, selection: Selection) {
55        self.selections.push(selection);
56    }
57
58    /// Should be used everywhere in this crate isntead of the Display implementation
59    /// Display implementation is only useful as a public api
60    pub(crate) fn format_with_indent(&self, mut indent_level: usize) -> String {
61        let mut text = String::from("{\n");
62        indent_level += 1;
63        let indent = "  ".repeat(indent_level);
64        for sel in &self.selections {
65            let _ = writeln!(text, "{}{}", indent, sel.format_with_indent(indent_level));
66        }
67        if indent_level <= 1 {
68            text.push_str("}\n");
69        } else {
70            let _ = write!(text, "{}}}", "  ".repeat(indent_level - 1));
71        }
72
73        text
74    }
75}
76
77// This impl is only useful when we generate only a SelectionSet
78// If it's used from a parent element, we call `format_with_indent`
79impl fmt::Display for SelectionSet {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        let indent_level = 1;
82        writeln!(f, "{{")?;
83        let indent = "  ".repeat(indent_level);
84        for sel in &self.selections {
85            writeln!(f, "{}{}", indent, sel.format_with_indent(indent_level))?;
86        }
87        writeln!(f, "}}")?;
88
89        Ok(())
90    }
91}
92
93/// The Selection type represents a selection in a selection set
94/// *Selection*:
95///     Field | FragmentSpread | InlineFragment
96///
97/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#Selection).
98#[derive(Debug, Clone, PartialEq)]
99pub enum Selection {
100    /// Represents a field
101    Field(Field),
102    /// Represents a fragment spread
103    FragmentSpread(FragmentSpread),
104    /// Represents an inline fragment
105    InlineFragment(InlineFragment),
106}
107
108impl Selection {
109    pub(crate) fn format_with_indent(&self, indent_level: usize) -> String {
110        match self {
111            Selection::Field(field) => field.format_with_indent(indent_level),
112            Selection::FragmentSpread(frag_spread) => frag_spread.to_string(),
113            Selection::InlineFragment(inline_frag) => inline_frag.format_with_indent(indent_level),
114        }
115    }
116}
117
118impl fmt::Display for Selection {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        let indent_level = 0;
121        match self {
122            Selection::Field(field) => write!(f, "{}", field.format_with_indent(indent_level)),
123            Selection::FragmentSpread(fragment_spread) => write!(f, "{fragment_spread}"),
124            Selection::InlineFragment(inline_fragment) => {
125                write!(f, "{}", inline_fragment.format_with_indent(indent_level))
126            }
127        }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use indoc::indoc;
134
135    use super::*;
136
137    #[test]
138    fn it_encodes_selection_set() {
139        let mut aliased_field = Field::new(String::from("myField"));
140        aliased_field.alias(Some(String::from("myAlias")));
141        let selections = vec![
142            Selection::Field(aliased_field),
143            Selection::FragmentSpread(FragmentSpread::new(String::from("myFragment"))),
144        ];
145        let mut selection_set = SelectionSet::new();
146        selections
147            .into_iter()
148            .for_each(|s| selection_set.selection(s));
149
150        assert_eq!(
151            selection_set.to_string(),
152            indoc! {r#"
153                {
154                  myAlias: myField
155                  ...myFragment
156                }
157            "#}
158        )
159    }
160
161    #[test]
162    fn it_encodes_deeper_selection_set() {
163        let fourth_field = Field::new("fourth".to_string());
164        let mut third_field = Field::new("third".to_string());
165        third_field.selection_set(Some(SelectionSet::with_selections(vec![Selection::Field(
166            fourth_field,
167        )])));
168        let mut second_field = Field::new("second".to_string());
169        second_field.selection_set(Some(SelectionSet::with_selections(vec![Selection::Field(
170            third_field,
171        )])));
172
173        let mut first_field = Field::new("first".to_string());
174        first_field.selection_set(Some(SelectionSet::with_selections(vec![Selection::Field(
175            second_field,
176        )])));
177
178        let selections = vec![
179            Selection::Field(first_field),
180            Selection::FragmentSpread(FragmentSpread::new(String::from("myFragment"))),
181        ];
182        let mut selection_set = SelectionSet::new();
183
184        selections
185            .into_iter()
186            .for_each(|s| selection_set.selection(s));
187
188        assert_eq!(
189            selection_set.to_string(),
190            indoc! {r#"
191                {
192                  first {
193                    second {
194                      third {
195                        fourth
196                      }
197                    }
198                  }
199                  ...myFragment
200                }
201            "#}
202        )
203    }
204}