apollo_encoder/
input_object_def.rs

1use std::fmt;
2
3use crate::{Directive, InputField, StringValue};
4
5/// Input objects are composite types used as inputs into queries defined as a list of named input values..
6///
7/// InputObjectTypeDefinition
8///     Description? **input** Name Directives? FieldsDefinition?
9///
10/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Input-Objects).
11///
12/// ### Example
13/// ```rust
14/// use apollo_encoder::{Type_, InputField, InputObjectDefinition};
15/// use indoc::indoc;
16///
17/// let ty_1 = Type_::NamedType {
18///     name: "DanglerPoleToys".to_string(),
19/// };
20///
21/// let ty_2 = Type_::List { ty: Box::new(ty_1) };
22/// let mut field = InputField::new("toys".to_string(), ty_2);
23/// field.default_value("\"Cat Dangler Pole Bird\"".to_string());
24/// let ty_3 = Type_::NamedType {
25///     name: "FavouriteSpots".to_string(),
26/// };
27/// let mut field_2 = InputField::new("playSpot".to_string(), ty_3);
28/// field_2.description("Best playime spots, e.g. tree, bed.".to_string());
29///
30/// let mut input_def = InputObjectDefinition::new("PlayTime".to_string());
31/// input_def.field(field);
32/// input_def.field(field_2);
33/// input_def.description("Cat playtime input".to_string());
34///
35/// assert_eq!(
36///     input_def.to_string(),
37///     indoc! { r#"
38///         "Cat playtime input"
39///         input PlayTime {
40///           toys: [DanglerPoleToys] = "Cat Dangler Pole Bird"
41///           "Best playime spots, e.g. tree, bed."
42///           playSpot: FavouriteSpots
43///         }
44///     "#}
45/// );
46/// ```
47#[derive(Debug, Clone)]
48pub struct InputObjectDefinition {
49    // Name must return a String.
50    name: String,
51    // Description may return a String or null.
52    description: Option<StringValue>,
53    // A vector of fields
54    fields: Vec<InputField>,
55    /// Contains all directives.
56    directives: Vec<Directive>,
57    extend: bool,
58}
59
60impl InputObjectDefinition {
61    /// Create a new instance of ObjectDef with a name.
62    pub fn new(name: String) -> Self {
63        Self {
64            name,
65            description: None,
66            fields: Vec::new(),
67            directives: Vec::new(),
68            extend: false,
69        }
70    }
71
72    /// Set the input object type as an extension
73    pub fn extend(&mut self) {
74        self.extend = true;
75    }
76
77    /// Set the InputObjectDef's description field.
78    pub fn description(&mut self, description: String) {
79        self.description = Some(StringValue::Top {
80            source: description,
81        });
82    }
83
84    /// Push a Field to InputObjectDef's fields vector.
85    pub fn field(&mut self, field: InputField) {
86        self.fields.push(field)
87    }
88
89    /// Add a directive.
90    pub fn directive(&mut self, directive: Directive) {
91        self.directives.push(directive)
92    }
93}
94
95impl fmt::Display for InputObjectDefinition {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        if self.extend {
98            write!(f, "extend ")?;
99        // No description when it's a extension
100        } else if let Some(description) = &self.description {
101            writeln!(f, "{description}")?;
102        }
103
104        write!(f, "input {}", &self.name)?;
105
106        for directive in &self.directives {
107            write!(f, " {directive}")?;
108        }
109        write!(f, " {{")?;
110
111        for field in &self.fields {
112            write!(f, "\n{field}")?;
113        }
114        writeln!(f, "\n}}")
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::{Argument, InputField, Type_, Value};
122    use indoc::indoc;
123    use pretty_assertions::assert_eq;
124
125    #[test]
126    fn it_encodes_input_object() {
127        let ty_1 = Type_::NamedType {
128            name: "DanglerPoleToys".to_string(),
129        };
130
131        let ty_2 = Type_::List { ty: Box::new(ty_1) };
132        let mut field = InputField::new("toys".to_string(), ty_2);
133        field.default_value("\"Cat Dangler Pole Bird\"".to_string());
134        let ty_3 = Type_::NamedType {
135            name: "FavouriteSpots".to_string(),
136        };
137        let mut field_2 = InputField::new("playSpot".to_string(), ty_3);
138        field_2.description("Best playime spots, e.g. tree, bed.".to_string());
139        let mut directive = Directive::new(String::from("testDirective"));
140        directive.arg(Argument::new(
141            String::from("first"),
142            Value::String("one".to_string()),
143        ));
144
145        let mut input_def = InputObjectDefinition::new("PlayTime".to_string());
146        input_def.field(field);
147        input_def.field(field_2);
148        input_def.directive(directive);
149
150        assert_eq!(
151            input_def.to_string(),
152            indoc! { r#"
153                input PlayTime @testDirective(first: "one") {
154                  toys: [DanglerPoleToys] = "Cat Dangler Pole Bird"
155                  "Best playime spots, e.g. tree, bed."
156                  playSpot: FavouriteSpots
157                }
158            "#}
159        );
160    }
161
162    #[test]
163    fn it_encodes_input_object_with_description() {
164        let ty_1 = Type_::NamedType {
165            name: "DanglerPoleToys".to_string(),
166        };
167
168        let ty_2 = Type_::List { ty: Box::new(ty_1) };
169        let mut field = InputField::new("toys".to_string(), ty_2);
170        field.default_value("\"Cat Dangler Pole Bird\"".to_string());
171        let ty_3 = Type_::NamedType {
172            name: "FavouriteSpots".to_string(),
173        };
174        let mut field_2 = InputField::new("playSpot".to_string(), ty_3);
175        field_2.description("Best playime spots, e.g. tree, bed.".to_string());
176
177        let mut input_def = InputObjectDefinition::new("PlayTime".to_string());
178        input_def.field(field);
179        input_def.field(field_2);
180        input_def.description("Cat playtime input".to_string());
181
182        assert_eq!(
183            input_def.to_string(),
184            indoc! { r#"
185                "Cat playtime input"
186                input PlayTime {
187                  toys: [DanglerPoleToys] = "Cat Dangler Pole Bird"
188                  "Best playime spots, e.g. tree, bed."
189                  playSpot: FavouriteSpots
190                }
191            "#}
192        );
193    }
194
195    #[test]
196    fn it_encodes_input_object_extension() {
197        let ty_1 = Type_::NamedType {
198            name: "DanglerPoleToys".to_string(),
199        };
200
201        let ty_2 = Type_::List { ty: Box::new(ty_1) };
202        let mut field = InputField::new("toys".to_string(), ty_2);
203        field.default_value("\"Cat Dangler Pole Bird\"".to_string());
204        let ty_3 = Type_::NamedType {
205            name: "FavouriteSpots".to_string(),
206        };
207        let mut field_2 = InputField::new("playSpot".to_string(), ty_3);
208        field_2.description("Best playime spots, e.g. tree, bed.".to_string());
209
210        let mut input_def = InputObjectDefinition::new("PlayTime".to_string());
211        input_def.field(field);
212        input_def.field(field_2);
213        input_def.description("Cat playtime input".to_string());
214        input_def.extend();
215
216        assert_eq!(
217            input_def.to_string(),
218            indoc! { r#"
219                extend input PlayTime {
220                  toys: [DanglerPoleToys] = "Cat Dangler Pole Bird"
221                  "Best playime spots, e.g. tree, bed."
222                  playSpot: FavouriteSpots
223                }
224            "#}
225        );
226    }
227}