apollo_encoder/
field.rs

1use std::fmt::{self, Write as _};
2
3use crate::{
4    Argument, ArgumentsDefinition, Directive, InputValueDefinition, SelectionSet, StringValue,
5    Type_,
6};
7/// The FieldDefinition type represents each field definition in an Object
8/// definition or Interface type definition.
9///
10/// *FieldDefinition*:
11///     Description? Name ArgumentsDefinition? **:** Type Directives?
12///
13/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#FieldDefinition).
14///
15/// ### Example
16/// ```rust
17/// use apollo_encoder::{Type_, FieldDefinition, InputValueDefinition};
18///
19/// let ty_1 = Type_::NamedType {
20///     name: "CatBreed".to_string(),
21/// };
22///
23/// let mut field = FieldDefinition::new("cat".to_string(), ty_1);
24///
25/// let value_1 = Type_::NamedType {
26///     name: "CatBreed".to_string(),
27/// };
28///
29/// let arg = InputValueDefinition::new("breed".to_string(), value_1);
30///
31/// field.arg(arg);
32///
33/// assert_eq!(
34///     field.to_string(),
35///     r#"  cat(breed: CatBreed): CatBreed"#
36/// );
37/// ```
38#[derive(Debug, PartialEq, Clone)]
39pub struct FieldDefinition {
40    // Name must return a String.
41    name: String,
42    // Description may return a String.
43    description: Option<StringValue>,
44    // Args returns a List of __InputValue representing the arguments this field accepts.
45    args: ArgumentsDefinition,
46    // Type must return a __Type that represents the type of value returned by this field.
47    type_: Type_,
48    /// Contains all directives.
49    directives: Vec<Directive>,
50}
51
52impl FieldDefinition {
53    /// Create a new instance of Field.
54    pub fn new(name: String, type_: Type_) -> Self {
55        Self {
56            description: None,
57            name,
58            type_,
59            args: ArgumentsDefinition::new(),
60            directives: Vec::new(),
61        }
62    }
63
64    /// Set the Field's description.
65    pub fn description(&mut self, description: String) {
66        self.description = Some(StringValue::Field {
67            source: description,
68        });
69    }
70
71    /// Set the Field's arguments.
72    pub fn arg(&mut self, arg: InputValueDefinition) {
73        self.args.input_value(arg);
74    }
75
76    /// Add a directive.
77    pub fn directive(&mut self, directive: Directive) {
78        self.directives.push(directive)
79    }
80}
81
82impl fmt::Display for FieldDefinition {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        if let Some(description) = &self.description {
85            writeln!(f, "{description}")?;
86        }
87        write!(f, "  {}", self.name)?;
88
89        if !self.args.input_values.is_empty() {
90            write!(f, "{}", self.args)?;
91        }
92
93        write!(f, ": {}", self.type_)?;
94
95        for directive in &self.directives {
96            write!(f, " {directive}")?;
97        }
98
99        Ok(())
100    }
101}
102
103/// The __Field type represents each field in an Object or Interface type.
104///
105/// *Field*:
106///     Alias? Name Arguments? Directives? SelectionSet?
107///
108/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Language.Fields).
109///
110/// ### Example
111/// ```rust
112/// use apollo_encoder::Field;
113///
114/// let mut field = Field::new("myField".to_string());
115/// field.alias(Some("myAlias".to_string()));
116///
117/// assert_eq!(field.to_string(), r#"myAlias: myField"#);
118/// ```
119#[derive(Debug, PartialEq, Clone)]
120pub struct Field {
121    // Alias must be a String.
122    alias: Option<String>,
123    // Name must return a String.
124    name: String,
125    // Args returns a List of Argument representing the arguments this field accepts.
126    args: Vec<Argument>,
127    /// Contains all directives.
128    directives: Vec<Directive>,
129    selection_set: Option<SelectionSet>,
130}
131
132impl Field {
133    /// Create an instance of Field
134    pub fn new(name: String) -> Self {
135        Self {
136            name,
137            selection_set: None,
138            alias: None,
139            args: Vec::new(),
140            directives: Vec::new(),
141        }
142    }
143
144    /// Set an alias to a field name
145    pub fn alias(&mut self, alias: Option<String>) {
146        self.alias = alias;
147    }
148
149    /// Add a directive to a field
150    pub fn directive(&mut self, directive: Directive) {
151        self.directives.push(directive);
152    }
153
154    /// Add an argument to a field
155    pub fn argument(&mut self, argument: Argument) {
156        self.args.push(argument);
157    }
158
159    /// Set a selection set to a field
160    pub fn selection_set(&mut self, selection_set: Option<SelectionSet>) {
161        self.selection_set = selection_set;
162    }
163
164    /// Should be used everywhere in this crate instead of the Display implementation
165    /// Display implementation is only useful as a public api
166    pub(crate) fn format_with_indent(&self, indent_level: usize) -> String {
167        let mut text = match &self.alias {
168            Some(alias) => format!("{alias}: {}", self.name),
169            None => String::from(&self.name),
170        };
171
172        if !self.args.is_empty() {
173            for (i, arg) in self.args.iter().enumerate() {
174                match i {
175                    0 => {
176                        let _ = write!(text, "({arg}");
177                    }
178                    _ => {
179                        let _ = write!(text, ", {arg}");
180                    }
181                }
182            }
183            text.push(')');
184        }
185
186        for directive in &self.directives {
187            let _ = write!(text, " {directive}");
188        }
189        if let Some(sel_set) = &self.selection_set {
190            let _ = write!(text, " {}", sel_set.format_with_indent(indent_level));
191        }
192
193        text
194    }
195}
196
197impl fmt::Display for Field {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        let indent_level = 0;
200        write!(f, "{}", self.format_with_indent(indent_level))
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use crate::{Argument, Value};
207
208    use super::*;
209    use pretty_assertions::assert_eq;
210
211    #[test]
212    fn it_encodes_simple_fields() {
213        let mut field = Field::new("myField".to_string());
214        field.alias(Some("myAlias".to_string()));
215
216        assert_eq!(field.to_string(), r#"myAlias: myField"#);
217    }
218
219    #[test]
220    fn it_encodes_simple_fields_def() {
221        let ty_1 = Type_::NamedType {
222            name: "SpaceProgram".to_string(),
223        };
224
225        let ty_2 = Type_::List { ty: Box::new(ty_1) };
226        let ty_3 = Type_::NonNull { ty: Box::new(ty_2) };
227        let field = FieldDefinition::new("spaceCat".to_string(), ty_3);
228
229        assert_eq!(field.to_string(), r#"  spaceCat: [SpaceProgram]!"#);
230    }
231
232    #[test]
233    fn it_encodes_fields_with_directive() {
234        let ty_1 = Type_::NamedType {
235            name: "SpaceProgram".to_string(),
236        };
237
238        let ty_2 = Type_::List { ty: Box::new(ty_1) };
239        let mut field = FieldDefinition::new("cat".to_string(), ty_2);
240        let mut directive = Directive::new(String::from("testDirective"));
241        directive.arg(Argument::new(String::from("first"), Value::Int(1)));
242        field.description("Very good cats".to_string());
243        field.directive(directive);
244
245        assert_eq!(
246            field.to_string(),
247            r#"  "Very good cats"
248  cat: [SpaceProgram] @testDirective(first: 1)"#
249        );
250    }
251
252    #[test]
253    fn it_encodes_fields_with_description() {
254        let ty_1 = Type_::NamedType {
255            name: "SpaceProgram".to_string(),
256        };
257
258        let ty_2 = Type_::NonNull { ty: Box::new(ty_1) };
259        let ty_3 = Type_::List { ty: Box::new(ty_2) };
260        let ty_4 = Type_::NonNull { ty: Box::new(ty_3) };
261        let mut field = FieldDefinition::new("spaceCat".to_string(), ty_4);
262        field.description("Very good space cats".to_string());
263
264        assert_eq!(
265            field.to_string(),
266            r#"  "Very good space cats"
267  spaceCat: [SpaceProgram!]!"#
268        );
269    }
270
271    #[test]
272    fn it_encodes_fields_with_value_arguments() {
273        let ty_1 = Type_::NamedType {
274            name: "SpaceProgram".to_string(),
275        };
276
277        let ty_2 = Type_::NonNull { ty: Box::new(ty_1) };
278        let ty_3 = Type_::List { ty: Box::new(ty_2) };
279        let ty_4 = Type_::NonNull { ty: Box::new(ty_3) };
280        let mut field_definition = FieldDefinition::new("spaceCat".to_string(), ty_4);
281        field_definition.description("Very good space cats".to_string());
282
283        let value_1 = Type_::NamedType {
284            name: "SpaceProgram".to_string(),
285        };
286
287        let value_2 = Type_::List {
288            ty: Box::new(value_1),
289        };
290        let mut arg = InputValueDefinition::new("cat".to_string(), value_2);
291        let mut deprecated_directive = Directive::new(String::from("deprecated"));
292        deprecated_directive.arg(Argument::new(
293            String::from("reason"),
294            Value::String(String::from("Cats are no longer sent to space.")),
295        ));
296        arg.directive(deprecated_directive);
297        field_definition.arg(arg);
298
299        assert_eq!(
300            field_definition.to_string(),
301            r#"  "Very good space cats"
302  spaceCat(cat: [SpaceProgram] @deprecated(reason: "Cats are no longer sent to space.")): [SpaceProgram!]!"#
303        );
304    }
305
306    #[test]
307    fn it_encodes_fields_with_argument_descriptions() {
308        let ty = Type_::NamedType {
309            name: "Cat".to_string(),
310        };
311
312        let mut field_definition = FieldDefinition::new("spaceCat".to_string(), ty);
313
314        let value = Type_::NamedType {
315            name: "Treat".to_string(),
316        };
317
318        let mut arg = InputValueDefinition::new("treat".to_string(), value);
319        arg.description("The type of treats given in space".to_string());
320        field_definition.arg(arg);
321
322        let value = Type_::NamedType {
323            name: "Int".to_string(),
324        };
325
326        let mut arg = InputValueDefinition::new("age".to_string(), value);
327        arg.description("Optimal age of a \"space\" cat".to_string());
328        field_definition.arg(arg);
329
330        assert_eq!(
331            field_definition.to_string(),
332            r#"  spaceCat(
333    "The type of treats given in space"
334    treat: Treat,
335    """
336    Optimal age of a "space" cat
337    """
338    age: Int
339  ): Cat"#
340        );
341    }
342}