1use std::fmt;
2
3use crate::{Directive, FieldDefinition, StringValue};
4#[derive(Debug, Clone)]
55pub struct ObjectDefinition {
56 name: String,
58 description: Option<StringValue>,
60 interfaces: Vec<String>,
62 fields: Vec<FieldDefinition>,
64 directives: Vec<Directive>,
66 extend: bool,
67}
68
69impl ObjectDefinition {
70 pub fn new(name: String) -> Self {
72 Self {
73 name,
74 description: None,
75 interfaces: Vec::new(),
76 fields: Vec::new(),
77 directives: Vec::new(),
78 extend: false,
79 }
80 }
81
82 pub fn description(&mut self, description: String) {
84 self.description = Some(StringValue::Top {
85 source: description,
86 });
87 }
88
89 pub fn directive(&mut self, directive: Directive) {
91 self.directives.push(directive)
92 }
93
94 pub fn extend(&mut self) {
96 self.extend = true;
97 }
98
99 pub fn field(&mut self, field: FieldDefinition) {
101 self.fields.push(field)
102 }
103
104 pub fn interface(&mut self, interface: String) {
106 self.interfaces.push(interface)
107 }
108}
109
110impl fmt::Display for ObjectDefinition {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 if self.extend {
113 write!(f, "extend ")?;
114 } else if let Some(description) = &self.description {
116 writeln!(f, "{description}")?;
117 }
118
119 write!(f, "type {}", &self.name)?;
120 for (i, interface) in self.interfaces.iter().enumerate() {
121 match i {
122 0 => write!(f, " implements {interface}")?,
123 _ => write!(f, " & {interface}")?,
124 }
125 }
126 for directive in &self.directives {
127 write!(f, " {directive}")?;
128 }
129
130 write!(f, " {{")?;
131
132 for field in &self.fields {
133 write!(f, "\n{field}")?;
134 }
135 writeln!(f, "\n}}")
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::{Argument, FieldDefinition, InputValueDefinition, Type_, Value};
143 use indoc::indoc;
144 use pretty_assertions::assert_eq;
145
146 #[test]
147 fn it_encodes_object_with_description() {
148 let ty_1 = Type_::NamedType {
149 name: "DanglerPoleToys".to_string(),
150 };
151
152 let ty_2 = Type_::List { ty: Box::new(ty_1) };
153 let field = FieldDefinition::new("toys".to_string(), ty_2);
154
155 let mut object_def = ObjectDefinition::new("PetStoreTrip".to_string());
156 object_def.field(field);
157 object_def.description("What to get at Fressnapf?".to_string());
158
159 assert_eq!(
160 object_def.to_string(),
161 indoc! { r#"
162 "What to get at Fressnapf?"
163 type PetStoreTrip {
164 toys: [DanglerPoleToys]
165 }
166 "#}
167 );
168 }
169
170 #[test]
171 fn it_encodes_object_with_field_directives() {
172 let ty_1 = Type_::NamedType {
173 name: "DanglerPoleToys".to_string(),
174 };
175
176 let mut field = FieldDefinition::new("toys".to_string(), ty_1);
177 let mut deprecated_directive = Directive::new(String::from("deprecated"));
178 deprecated_directive.arg(Argument::new(
179 String::from("reason"),
180 Value::String(String::from(
181 "\"DanglerPoleToys\" are no longer interesting",
182 )),
183 ));
184 field.directive(deprecated_directive);
185
186 let mut object_def = ObjectDefinition::new("PetStoreTrip".to_string());
187 object_def.field(field);
188 object_def.description("What to get at Fressnapf?".to_string());
189
190 assert_eq!(
191 object_def.to_string(),
192 indoc! { r#"
193 "What to get at Fressnapf?"
194 type PetStoreTrip {
195 toys: DanglerPoleToys @deprecated(reason: """"DanglerPoleToys" are no longer interesting""")
196 }
197 "#}
198 );
199 }
200
201 #[test]
202 fn it_encodes_object_with_extend() {
203 let ty_1 = Type_::NamedType {
204 name: "DanglerPoleToys".to_string(),
205 };
206
207 let mut field = FieldDefinition::new("toys".to_string(), ty_1);
208 let mut deprecated_directive = Directive::new(String::from("deprecated"));
209 deprecated_directive.arg(Argument::new(
210 String::from("reason"),
211 Value::String(String::from("DanglerPoleToys are no longer interesting")),
212 ));
213 field.directive(deprecated_directive);
214
215 let mut object_def = ObjectDefinition::new("PetStoreTrip".to_string());
216 object_def.field(field);
217 object_def.description("What to get at Fressnapf?".to_string());
218 object_def.extend();
219
220 assert_eq!(
221 object_def.to_string(),
222 indoc! { r#"
223 extend type PetStoreTrip {
224 toys: DanglerPoleToys @deprecated(reason: "DanglerPoleToys are no longer interesting")
225 }
226 "#}
227 );
228 }
229
230 #[test]
231 fn it_encodes_object_with_interface() {
232 let ty_1 = Type_::NamedType {
233 name: "DanglerPoleToys".to_string(),
234 };
235
236 let ty_2 = Type_::List { ty: Box::new(ty_1) };
237 let mut field = FieldDefinition::new("toys".to_string(), ty_2);
238 let mut deprecated_directive = Directive::new(String::from("deprecated"));
239 deprecated_directive.arg(Argument::new(
240 String::from("reason"),
241 Value::String(String::from("Cats are too spoiled")),
242 ));
243 field.directive(deprecated_directive);
244 let ty_3 = Type_::NamedType {
245 name: "FoodType".to_string(),
246 };
247 let mut field_2 = FieldDefinition::new("food".to_string(), ty_3);
248 field_2.description("Dry or wet food?".to_string());
249
250 let ty_4 = Type_::NamedType {
251 name: "Boolean".to_string(),
252 };
253 let field_3 = FieldDefinition::new("catGrass".to_string(), ty_4);
254
255 let mut object_def = ObjectDefinition::new("PetStoreTrip".to_string());
256 object_def.field(field);
257 object_def.field(field_2);
258 object_def.field(field_3);
259 object_def.description("Shopping list for cats at the pet store.".to_string());
260 object_def.interface("ShoppingTrip".to_string());
261
262 assert_eq!(
263 object_def.to_string(),
264 indoc! { r#"
265 "Shopping list for cats at the pet store."
266 type PetStoreTrip implements ShoppingTrip {
267 toys: [DanglerPoleToys] @deprecated(reason: "Cats are too spoiled")
268 "Dry or wet food?"
269 food: FoodType
270 catGrass: Boolean
271 }
272 "#}
273 );
274 }
275
276 #[test]
277 fn it_encodes_object_with_interface_and_directives() {
278 let ty_1 = Type_::NamedType {
279 name: "DanglerPoleToys".to_string(),
280 };
281
282 let mut directive = Directive::new(String::from("testDirective"));
283 directive.arg(Argument::new(
284 String::from("first"),
285 Value::String("one".to_string()),
286 ));
287 let ty_2 = Type_::List { ty: Box::new(ty_1) };
288 let mut field = FieldDefinition::new("toys".to_string(), ty_2);
289 let mut deprecated_directive = Directive::new(String::from("deprecated"));
290 deprecated_directive.arg(Argument::new(
291 String::from("reason"),
292 Value::String(String::from("Cats are too spoiled")),
293 ));
294 field.directive(deprecated_directive);
295 let ty_3 = Type_::NamedType {
296 name: "FoodType".to_string(),
297 };
298 let mut field_2 = FieldDefinition::new("food".to_string(), ty_3);
299 field_2.description("Dry or wet food?".to_string());
300
301 let ty_4 = Type_::NamedType {
302 name: "Boolean".to_string(),
303 };
304 let mut field_3 = FieldDefinition::new("catGrass".to_string(), ty_4);
305
306 let value = Type_::NamedType {
307 name: "Boolean".to_string(),
308 };
309
310 let mut arg = InputValueDefinition::new("fresh".to_string(), value);
311 arg.description("How fresh is this grass".to_string());
312 field_3.arg(arg);
313
314 let mut object_def = ObjectDefinition::new("PetStoreTrip".to_string());
315 object_def.field(field);
316 object_def.field(field_2);
317 object_def.field(field_3);
318 object_def.description("Shopping list for cats at the pet store.".to_string());
319 object_def.interface("ShoppingTrip".to_string());
320 object_def.directive(directive);
321
322 assert_eq!(
323 object_def.to_string(),
324 indoc! { r#"
325 "Shopping list for cats at the pet store."
326 type PetStoreTrip implements ShoppingTrip @testDirective(first: "one") {
327 toys: [DanglerPoleToys] @deprecated(reason: "Cats are too spoiled")
328 "Dry or wet food?"
329 food: FoodType
330 catGrass(
331 "How fresh is this grass"
332 fresh: Boolean
333 ): Boolean
334 }
335 "#}
336 );
337 }
338
339 #[test]
340 fn it_encodes_object_with_input_value_definition_fields() {
341 let ty_1 = Type_::NamedType {
342 name: "DanglerPoleToys".to_string(),
343 };
344
345 let ty_2 = Type_::List { ty: Box::new(ty_1) };
346 let field = FieldDefinition::new("toys".to_string(), ty_2);
347
348 let ty_4 = Type_::NamedType {
349 name: "Boolean".to_string(),
350 };
351 let mut field_3 = FieldDefinition::new("catGrass".to_string(), ty_4);
352
353 let value = Type_::NamedType {
354 name: "Boolean".to_string(),
355 };
356
357 let mut arg = InputValueDefinition::new("fresh".to_string(), value);
358 arg.description("How fresh is this grass".to_string());
359 field_3.arg(arg);
360
361 let value = Type_::NamedType {
362 name: "Int".to_string(),
363 };
364
365 let mut arg = InputValueDefinition::new("quantity".to_string(), value);
366 arg.description("Number of pots\ngrowing at a given time".to_string());
367 field_3.arg(arg);
368
369 let mut object_def = ObjectDefinition::new("PetStoreTrip".to_string());
370 object_def.field(field);
371 object_def.field(field_3);
372 object_def.description("Shopping list for cats at the pet store.".to_string());
373
374 assert_eq!(
375 object_def.to_string(),
376 indoc! { r#"
377 "Shopping list for cats at the pet store."
378 type PetStoreTrip {
379 toys: [DanglerPoleToys]
380 catGrass(
381 "How fresh is this grass"
382 fresh: Boolean,
383 """
384 Number of pots
385 growing at a given time
386 """
387 quantity: Int
388 ): Boolean
389 }
390 "#}
391 );
392 }
393
394 #[test]
395 fn it_encodes_object_with_block_string_description() {
396 let ty_1 = Type_::NamedType {
397 name: "String".to_string(),
398 };
399
400 let mut field = FieldDefinition::new("name".to_string(), ty_1);
401 field.description("multiline\ndescription".to_string());
402
403 let mut object_def = ObjectDefinition::new("Book".to_string());
404 object_def.field(field);
405 object_def.description("Book Object\nType".to_string());
406
407 assert_eq!(
408 object_def.to_string(),
409 indoc! { r#"
410 """
411 Book Object
412 Type
413 """
414 type Book {
415 """
416 multiline
417 description
418 """
419 name: String
420 }
421 "#}
422 );
423 }
424}