apollo_encoder/
document.rs

1use std::fmt;
2
3use crate::{
4    DirectiveDefinition, EnumDefinition, FragmentDefinition, InputObjectDefinition,
5    InterfaceDefinition, ObjectDefinition, OperationDefinition, ScalarDefinition, SchemaDefinition,
6    UnionDefinition,
7};
8
9/// The `Document` type represents a GraphQL document. A GraphQL Document
10/// describes a complete file or request string operated on by a GraphQL service
11/// or client.  A document contains multiple definitions, either executable or
12/// representative of a GraphQL type system.
13///
14/// *Document*:
15///     OperationDefinition*
16///     FragmentDefinition*
17///     SchemaDefinition*
18///     ScalarTypeDefinition*
19///     ObjectTypeDefinition*
20///     InterfaceTypeDefinition*
21///     UnionTypeDefinition*
22///     EnumTypeDefinition*
23///     InputObjectDefinition*
24///     DirectiveDefinition*
25///
26/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Document).
27///
28/// ### Example
29/// ```rust
30/// use apollo_encoder::{
31///     Argument, Directive, Document, Field, OperationDefinition, OperationType, Selection, SelectionSet, Type_, Value,
32///     VariableDefinition,
33/// };
34/// use indoc::indoc;
35///
36/// let mut document = Document::new();
37/// let selection_set = {
38///     let sels = vec![
39///         Selection::Field(Field::new(String::from("first"))),
40///         Selection::Field(Field::new(String::from("second"))),
41///     ];
42///     let mut sel_set = SelectionSet::new();
43///     sels.into_iter().for_each(|sel| sel_set.selection(sel));
44///
45///     sel_set
46/// };
47/// let var_def = VariableDefinition::new(
48///     String::from("variable_def"),
49///     Type_::List {
50///         ty: Box::new(Type_::NamedType {
51///             name: String::from("Int"),
52///         }),
53///     },
54/// );
55/// let mut new_op = OperationDefinition::new(OperationType::Query, selection_set);
56/// let mut directive = Directive::new(String::from("testDirective"));
57/// directive.arg(Argument::new(
58///     String::from("first"),
59///     Value::String("one".to_string()),
60/// ));
61/// new_op.variable_definition(var_def);
62/// new_op.directive(directive);
63///
64/// document.operation(new_op);
65///
66/// assert_eq!(
67///     document.to_string(),
68///     indoc! { r#"
69///         query($variable_def: [Int]) @testDirective(first: "one") {
70///           first
71///           second
72///         }
73///     "#}
74/// );
75/// ```
76#[derive(Debug, Clone, Default)]
77pub struct Document {
78    operation_definitions: Vec<OperationDefinition>,
79    fragment_definitions: Vec<FragmentDefinition>,
80    schema_definitions: Vec<SchemaDefinition>,
81    // Type definitions
82    scalar_type_definitions: Vec<ScalarDefinition>,
83    object_type_definitions: Vec<ObjectDefinition>,
84    interface_type_definitions: Vec<InterfaceDefinition>,
85    union_type_definitions: Vec<UnionDefinition>,
86    enum_type_definitions: Vec<EnumDefinition>,
87    input_object_type_definitions: Vec<InputObjectDefinition>,
88    // DirectiveDefs
89    directive_definitions: Vec<DirectiveDefinition>,
90}
91
92impl Document {
93    /// Create a new instance of Document
94    pub fn new() -> Self {
95        Self::default()
96    }
97
98    /// Add operation
99    pub fn operation(&mut self, operation_definition: OperationDefinition) {
100        self.operation_definitions.push(operation_definition);
101    }
102
103    /// Add fragment
104    pub fn fragment(&mut self, fragment_definition: FragmentDefinition) {
105        self.fragment_definitions.push(fragment_definition);
106    }
107
108    /// Add schema
109    pub fn schema(&mut self, schema_definition: SchemaDefinition) {
110        self.schema_definitions.push(schema_definition);
111    }
112    /// Add scalar
113    pub fn scalar(&mut self, scalar_type_definition: ScalarDefinition) {
114        self.scalar_type_definitions.push(scalar_type_definition);
115    }
116    /// Add object
117    pub fn object(&mut self, object_type_definition: ObjectDefinition) {
118        self.object_type_definitions.push(object_type_definition);
119    }
120    /// Add interface
121    pub fn interface(&mut self, interface_type_definition: InterfaceDefinition) {
122        self.interface_type_definitions
123            .push(interface_type_definition);
124    }
125    /// Add union
126    pub fn union(&mut self, union_type_definition: UnionDefinition) {
127        self.union_type_definitions.push(union_type_definition);
128    }
129    /// Add enum
130    pub fn enum_(&mut self, enum_type_definition: EnumDefinition) {
131        self.enum_type_definitions.push(enum_type_definition);
132    }
133
134    /// Add input_object
135    pub fn input_object(&mut self, input_object_type_definition: InputObjectDefinition) {
136        self.input_object_type_definitions
137            .push(input_object_type_definition);
138    }
139    /// Add directive
140    pub fn directive(&mut self, directive_definition: DirectiveDefinition) {
141        self.directive_definitions.push(directive_definition);
142    }
143}
144
145impl fmt::Display for Document {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        for operation_def in &self.operation_definitions {
148            write!(f, "{operation_def}")?;
149        }
150        for fragment_def in &self.fragment_definitions {
151            write!(f, "{fragment_def}")?;
152        }
153        for schema_def in &self.schema_definitions {
154            write!(f, "{schema_def}")?;
155        }
156        for scalar_type_def in &self.scalar_type_definitions {
157            write!(f, "{scalar_type_def}")?;
158        }
159        for object_type_def in &self.object_type_definitions {
160            write!(f, "{object_type_def}")?;
161        }
162        for interface_type_def in &self.interface_type_definitions {
163            write!(f, "{interface_type_def}")?;
164        }
165        for union_type_def in &self.union_type_definitions {
166            write!(f, "{union_type_def}")?;
167        }
168        for enum_type_def in &self.enum_type_definitions {
169            write!(f, "{enum_type_def}")?;
170        }
171        for input_object_type_def in &self.input_object_type_definitions {
172            write!(f, "{input_object_type_def}")?;
173        }
174        for directive_def in &self.directive_definitions {
175            write!(f, "{directive_def}")?;
176        }
177
178        Ok(())
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use crate::{
185        Argument, Directive, EnumValue, Field, FragmentSpread, InlineFragment, OperationType,
186        Selection, SelectionSet, TypeCondition, Type_, Value, VariableDefinition,
187    };
188
189    use super::*;
190    use indoc::indoc;
191
192    #[test]
193    fn it_encodes_a_document_with_operation() {
194        let mut document = Document::new();
195        let selection_set = {
196            let sels = vec![
197                Selection::Field(Field::new(String::from("first"))),
198                Selection::Field(Field::new(String::from("second"))),
199            ];
200            let mut sel_set = SelectionSet::new();
201            sels.into_iter().for_each(|sel| sel_set.selection(sel));
202
203            sel_set
204        };
205        let var_def = VariableDefinition::new(
206            String::from("variable_def"),
207            Type_::List {
208                ty: Box::new(Type_::NamedType {
209                    name: String::from("Int"),
210                }),
211            },
212        );
213        let mut new_op = OperationDefinition::new(OperationType::Query, selection_set);
214        let mut directive = Directive::new(String::from("testDirective"));
215        directive.arg(Argument::new(
216            String::from("first"),
217            Value::String("one".to_string()),
218        ));
219        new_op.variable_definition(var_def);
220        new_op.directive(directive);
221
222        document.operation(new_op);
223
224        assert_eq!(
225            document.to_string(),
226            indoc! { r#"
227                query($variable_def: [Int]) @testDirective(first: "one") {
228                  first
229                  second
230                }
231            "#}
232        );
233    }
234
235    #[test]
236    fn it_encodes_document_with_operation_and_fragments() {
237        let mut document = Document::new();
238
239        let mut droid_selection_set = SelectionSet::new();
240        let primary_function_field = Selection::Field(Field::new(String::from("primaryFunction")));
241        droid_selection_set.selection(primary_function_field);
242
243        let mut droid_inline_fragment = InlineFragment::new(droid_selection_set);
244        droid_inline_fragment.type_condition(Some(TypeCondition::new(String::from("Droid"))));
245
246        let comparison_fields_fragment_spread =
247            FragmentSpread::new(String::from("comparisonFields"));
248
249        let name_field = Field::new(String::from("name"));
250
251        let hero_selection_set = vec![
252            Selection::Field(name_field),
253            Selection::FragmentSpread(comparison_fields_fragment_spread),
254            Selection::InlineFragment(droid_inline_fragment),
255        ];
256
257        let mut hero_field = Field::new(String::from("hero"));
258        hero_field.selection_set(Some(SelectionSet::with_selections(hero_selection_set)));
259
260        let hero_for_episode_selection_set = vec![Selection::Field(hero_field)];
261        let mut hero_for_episode_operation = OperationDefinition::new(
262            OperationType::Query,
263            SelectionSet::with_selections(hero_for_episode_selection_set),
264        );
265        hero_for_episode_operation.name(Some(String::from("HeroForEpisode")));
266
267        document.operation(hero_for_episode_operation);
268
269        assert_eq!(
270            document.to_string(),
271            indoc! { r#"
272                query HeroForEpisode {
273                  hero {
274                    name
275                    ...comparisonFields
276                    ... on Droid {
277                      primaryFunction
278                    }
279                  }
280                }
281            "#}
282        );
283    }
284
285    #[test]
286    fn it_encodes_a_document_with_type_system_definition() {
287        let mut document = Document::new();
288
289        // Create a Directive Definition.
290        let mut directive_def = DirectiveDefinition::new("provideTreat".to_string());
291        directive_def.description("Ensures cats get treats.".to_string());
292        directive_def.location("OBJECT".to_string());
293        directive_def.location("FIELD_DEFINITION".to_string());
294        directive_def.location("INPUT_FIELD_DEFINITION".to_string());
295        document.directive(directive_def);
296
297        // Create an Enum Definition
298        let mut enum_ty_1 = EnumValue::new("CatTree".to_string());
299        enum_ty_1.description("Top bunk of a cat tree.".to_string());
300        let enum_ty_2 = EnumValue::new("Bed".to_string());
301        let mut enum_ty_3 = EnumValue::new("CardboardBox".to_string());
302        let mut directive = Directive::new(String::from("deprecated"));
303        directive.arg(Argument::new(
304            String::from("reason"),
305            Value::String("Box was recycled.".to_string()),
306        ));
307        enum_ty_3.directive(directive);
308
309        let mut enum_def = EnumDefinition::new("NapSpots".to_string());
310        enum_def.description("Favourite cat\nnap spots.".to_string());
311        enum_def.value(enum_ty_1);
312        enum_def.value(enum_ty_2);
313        enum_def.value(enum_ty_3);
314        document.enum_(enum_def);
315        // Union Definition
316        let mut union_def = UnionDefinition::new("Pet".to_string());
317        union_def.description("A union of all pets represented within a household.".to_string());
318        union_def.member("Cat".to_string());
319        union_def.member("Dog".to_string());
320        document.union(union_def);
321
322        assert_eq!(
323            document.to_string(),
324            indoc! { r#"
325        "A union of all pets represented within a household."
326        union Pet = Cat | Dog
327        """
328        Favourite cat
329        nap spots.
330        """
331        enum NapSpots {
332          "Top bunk of a cat tree."
333          CatTree
334          Bed
335          CardboardBox @deprecated(reason: "Box was recycled.")
336        }
337        "Ensures cats get treats."
338        directive @provideTreat on OBJECT | FIELD_DEFINITION | INPUT_FIELD_DEFINITION
339    "# }
340        );
341    }
342}