apollo_encoder/
operation.rs

1use std::fmt;
2
3use crate::{Directive, SelectionSet, VariableDefinition};
4
5/// The OperationDefinition type represents an operation definition
6///
7/// *OperationDefinition*:
8///     OperationType Name? VariableDefinitions? Directives? SelectionSet
9///
10/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Language.Operations).
11///
12/// ### Example
13/// ```rust
14/// use apollo_encoder::{Argument, Field, InlineFragment, Directive, OperationDefinition, OperationType, Selection, SelectionSet, TypeCondition, Type_, Value, VariableDefinition};
15/// use indoc::indoc;
16///
17/// let selection_set = {
18///     let sels = vec![
19///         Selection::Field(Field::new(String::from("first"))),
20///         Selection::Field(Field::new(String::from("second"))),
21///     ];
22///     let mut sel_set = SelectionSet::new();
23///     sels.into_iter().for_each(|sel| sel_set.selection(sel));
24///
25///     sel_set
26/// };
27/// let var_def = VariableDefinition::new(
28///     String::from("variable_def"),
29///     Type_::List {
30///         ty: Box::new(Type_::NamedType {
31///             name: String::from("Int"),
32///         }),
33///     },
34/// );
35/// let mut new_op = OperationDefinition::new(OperationType::Query, selection_set);
36/// let mut directive = Directive::new(String::from("testDirective"));
37/// directive.arg(Argument::new(
38///     String::from("first"),
39///     Value::String("one".to_string()),
40/// ));
41/// new_op.variable_definition(var_def);
42/// new_op.directive(directive);
43///
44/// assert_eq!(
45///     new_op.to_string(),
46///     indoc! { r#"
47///         query($variable_def: [Int]) @testDirective(first: "one") {
48///           first
49///           second
50///         }
51///     "#}
52/// );
53/// ```
54#[derive(Debug, Clone)]
55pub struct OperationDefinition {
56    operation_type: OperationType,
57    name: Option<String>,
58    variable_definitions: Vec<VariableDefinition>,
59    directives: Vec<Directive>,
60    selection_set: SelectionSet,
61    /// If a document contains only one operation and that operation is a query which defines no variables and
62    /// contains no directives then that operation may be represented in a short-hand form which omits the query keyword and operation name.
63    shorthand: bool,
64}
65
66impl OperationDefinition {
67    /// Create a new instance of OperationDef
68    pub fn new(operation_type: OperationType, selection_set: SelectionSet) -> Self {
69        Self {
70            operation_type,
71            selection_set,
72            name: None,
73            variable_definitions: Vec::new(),
74            directives: Vec::new(),
75            shorthand: false,
76        }
77    }
78
79    /// Set the operation def's name.
80    pub fn name(&mut self, name: Option<String>) {
81        self.name = name;
82    }
83
84    /// Add a variable definitions.
85    pub fn variable_definition(&mut self, variable_definition: VariableDefinition) {
86        self.variable_definitions.push(variable_definition);
87    }
88
89    /// Add a directive.
90    pub fn directive(&mut self, directive: Directive) {
91        self.directives.push(directive);
92    }
93
94    /// Set this operation as a query shorthand
95    /// If a document contains only one operation and that operation is a query which defines no variables and
96    /// contains no directives then that operation may be represented in a short-hand form which omits the query keyword and operation name.
97    /// Be careful, it will automatically drop variable definitions and directives
98    pub fn shorthand(&mut self) {
99        self.shorthand = true;
100    }
101}
102
103impl fmt::Display for OperationDefinition {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        let indent_level = 0;
106
107        if !self.shorthand {
108            write!(f, "{}", self.operation_type)?;
109            if let Some(name) = &self.name {
110                write!(f, " {name}")?;
111            }
112
113            if !self.variable_definitions.is_empty() {
114                write!(f, "(")?;
115                for (i, var_def) in self.variable_definitions.iter().enumerate() {
116                    if i == self.variable_definitions.len() - 1 {
117                        write!(f, "{var_def}")?;
118                    } else {
119                        write!(f, "{var_def}, ")?;
120                    }
121                }
122                write!(f, ")")?;
123            }
124            for directive in &self.directives {
125                write!(f, " {directive}")?;
126            }
127            write!(f, " ")?;
128        }
129
130        write!(f, "{}", self.selection_set.format_with_indent(indent_level))?;
131
132        Ok(())
133    }
134}
135
136/// The OperationType type represents the kind of operation
137///
138/// *OperationType*:
139///     query | mutation | subscription
140///
141/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#OperationType).
142#[derive(Debug, Clone)]
143pub enum OperationType {
144    /// Represents a query operation
145    Query,
146    /// Represents a mutation operation
147    Mutation,
148    /// Represents a subscription operation
149    Subscription,
150}
151
152impl fmt::Display for OperationType {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        match self {
155            OperationType::Query => write!(f, "query"),
156            OperationType::Mutation => write!(f, "mutation"),
157            OperationType::Subscription => write!(f, "subscription"),
158        }
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use crate::{field::Field, Argument, FragmentSpread, Selection, Type_, Value};
166    use indoc::indoc;
167
168    #[test]
169    fn it_encodes_a_query_operation() {
170        let selection_set = {
171            let sels = vec![
172                Selection::Field(Field::new(String::from("first"))),
173                Selection::Field(Field::new(String::from("second"))),
174            ];
175            let mut sel_set = SelectionSet::new();
176            sels.into_iter().for_each(|sel| sel_set.selection(sel));
177
178            sel_set
179        };
180        let var_def = VariableDefinition::new(
181            String::from("variable_def"),
182            Type_::List {
183                ty: Box::new(Type_::NamedType {
184                    name: String::from("Int"),
185                }),
186            },
187        );
188        let mut new_op = OperationDefinition::new(OperationType::Query, selection_set);
189        let mut directive = Directive::new(String::from("testDirective"));
190        directive.arg(Argument::new(
191            String::from("first"),
192            Value::String("one".to_string()),
193        ));
194        new_op.variable_definition(var_def);
195        new_op.directive(directive);
196
197        assert_eq!(
198            new_op.to_string(),
199            indoc! { r#"
200                query($variable_def: [Int]) @testDirective(first: "one") {
201                  first
202                  second
203                }
204            "#}
205        );
206    }
207
208    #[test]
209    fn it_encodes_a_shorthand_query_operation() {
210        let selection_set = {
211            let sels = vec![
212                Selection::Field(Field::new(String::from("first"))),
213                Selection::Field(Field::new(String::from("second"))),
214            ];
215            let mut sel_set = SelectionSet::new();
216            sels.into_iter().for_each(|sel| sel_set.selection(sel));
217
218            sel_set
219        };
220        let mut new_op = OperationDefinition::new(OperationType::Query, selection_set);
221        new_op.shorthand();
222
223        assert_eq!(
224            new_op.to_string(),
225            indoc! { r#"
226                {
227                  first
228                  second
229                }
230            "#}
231        );
232    }
233
234    #[test]
235    fn it_encodes_a_deeper_query_operation() {
236        // ----- Selection set creation
237        let fourth_field = Field::new("fourth".to_string());
238        let mut third_field = Field::new("third".to_string());
239        third_field.selection_set(Some(SelectionSet::with_selections(vec![Selection::Field(
240            fourth_field,
241        )])));
242        let mut second_field = Field::new("second".to_string());
243        second_field.selection_set(Some(SelectionSet::with_selections(vec![Selection::Field(
244            third_field,
245        )])));
246
247        let mut first_field = Field::new("first".to_string());
248        first_field.selection_set(Some(SelectionSet::with_selections(vec![Selection::Field(
249            second_field,
250        )])));
251
252        let selections = vec![
253            Selection::Field(first_field),
254            Selection::FragmentSpread(FragmentSpread::new(String::from("myFragment"))),
255        ];
256        let mut selection_set = SelectionSet::new();
257        selections
258            .into_iter()
259            .for_each(|s| selection_set.selection(s));
260        // -------------------------
261
262        let var_def = VariableDefinition::new(
263            String::from("variable_def"),
264            Type_::List {
265                ty: Box::new(Type_::NamedType {
266                    name: String::from("Int"),
267                }),
268            },
269        );
270        let mut new_op = OperationDefinition::new(OperationType::Query, selection_set);
271        let mut directive = Directive::new(String::from("testDirective"));
272        directive.arg(Argument::new(
273            String::from("first"),
274            Value::String("one".to_string()),
275        ));
276        new_op.variable_definition(var_def);
277        new_op.directive(directive);
278
279        assert_eq!(
280            new_op.to_string(),
281            indoc! { r#"
282                query($variable_def: [Int]) @testDirective(first: "one") {
283                  first {
284                    second {
285                      third {
286                        fourth
287                      }
288                    }
289                  }
290                  ...myFragment
291                }
292            "#}
293        );
294    }
295}