1use std::fmt::{self, Write as _};
2
3use crate::{
4 Argument, ArgumentsDefinition, Directive, InputValueDefinition, SelectionSet, StringValue,
5 Type_,
6};
7#[derive(Debug, PartialEq, Clone)]
39pub struct FieldDefinition {
40 name: String,
42 description: Option<StringValue>,
44 args: ArgumentsDefinition,
46 type_: Type_,
48 directives: Vec<Directive>,
50}
51
52impl FieldDefinition {
53 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 pub fn description(&mut self, description: String) {
66 self.description = Some(StringValue::Field {
67 source: description,
68 });
69 }
70
71 pub fn arg(&mut self, arg: InputValueDefinition) {
73 self.args.input_value(arg);
74 }
75
76 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#[derive(Debug, PartialEq, Clone)]
120pub struct Field {
121 alias: Option<String>,
123 name: String,
125 args: Vec<Argument>,
127 directives: Vec<Directive>,
129 selection_set: Option<SelectionSet>,
130}
131
132impl Field {
133 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 pub fn alias(&mut self, alias: Option<String>) {
146 self.alias = alias;
147 }
148
149 pub fn directive(&mut self, directive: Directive) {
151 self.directives.push(directive);
152 }
153
154 pub fn argument(&mut self, argument: Argument) {
156 self.args.push(argument);
157 }
158
159 pub fn selection_set(&mut self, selection_set: Option<SelectionSet>) {
161 self.selection_set = selection_set;
162 }
163
164 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}