use std::fmt::{self, Write as _};
use crate::{
Argument, ArgumentsDefinition, Directive, InputValueDefinition, SelectionSet, StringValue,
Type_,
};
#[derive(Debug, PartialEq, Clone)]
pub struct FieldDefinition {
name: String,
description: Option<StringValue>,
args: ArgumentsDefinition,
type_: Type_,
directives: Vec<Directive>,
}
impl FieldDefinition {
pub fn new(name: String, type_: Type_) -> Self {
Self {
description: None,
name,
type_,
args: ArgumentsDefinition::new(),
directives: Vec::new(),
}
}
pub fn description(&mut self, description: String) {
self.description = Some(StringValue::Field {
source: description,
});
}
pub fn arg(&mut self, arg: InputValueDefinition) {
self.args.input_value(arg);
}
pub fn directive(&mut self, directive: Directive) {
self.directives.push(directive)
}
}
impl fmt::Display for FieldDefinition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(description) = &self.description {
write!(f, "{}", description)?;
}
write!(f, " {}", self.name)?;
if !self.args.input_values.is_empty() {
write!(f, "{}", self.args)?;
}
write!(f, ": {}", self.type_)?;
for directive in &self.directives {
write!(f, " {}", directive)?;
}
Ok(())
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Field {
alias: Option<String>,
name: String,
args: Vec<Argument>,
directives: Vec<Directive>,
selection_set: Option<SelectionSet>,
}
impl Field {
pub fn new(name: String) -> Self {
Self {
name,
selection_set: None,
alias: None,
args: Vec::new(),
directives: Vec::new(),
}
}
pub fn alias(&mut self, alias: Option<String>) {
self.alias = alias;
}
pub fn directive(&mut self, directive: Directive) {
self.directives.push(directive);
}
pub fn argument(&mut self, argument: Argument) {
self.args.push(argument);
}
pub fn selection_set(&mut self, selection_set: Option<SelectionSet>) {
self.selection_set = selection_set;
}
pub(crate) fn format_with_indent(&self, indent_level: usize) -> String {
let mut text = match &self.alias {
Some(alias) => format!("{alias}: {}", self.name),
None => String::from(&self.name),
};
if !self.args.is_empty() {
for (i, arg) in self.args.iter().enumerate() {
match i {
0 => {
let _ = write!(text, "({arg}");
}
_ => {
let _ = write!(text, ", {arg}");
}
}
}
text.push(')');
}
for directive in &self.directives {
let _ = write!(text, " {directive}");
}
if let Some(sel_set) = &self.selection_set {
let _ = write!(text, " {}", sel_set.format_with_indent(indent_level));
}
text
}
}
impl fmt::Display for Field {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let indent_level = 0;
write!(f, "{}", self.format_with_indent(indent_level))
}
}
#[cfg(test)]
mod tests {
use crate::{Argument, Value};
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn it_encodes_simple_fields() {
let mut field = Field::new("myField".to_string());
field.alias(Some("myAlias".to_string()));
assert_eq!(field.to_string(), r#"myAlias: myField"#);
}
#[test]
fn it_encodes_simple_fields_def() {
let ty_1 = Type_::NamedType {
name: "SpaceProgram".to_string(),
};
let ty_2 = Type_::List { ty: Box::new(ty_1) };
let ty_3 = Type_::NonNull { ty: Box::new(ty_2) };
let field = FieldDefinition::new("spaceCat".to_string(), ty_3);
assert_eq!(field.to_string(), r#" spaceCat: [SpaceProgram]!"#);
}
#[test]
fn it_encodes_fields_with_directive() {
let ty_1 = Type_::NamedType {
name: "SpaceProgram".to_string(),
};
let ty_2 = Type_::List { ty: Box::new(ty_1) };
let mut field = FieldDefinition::new("cat".to_string(), ty_2);
let mut directive = Directive::new(String::from("testDirective"));
directive.arg(Argument::new(String::from("first"), Value::Int(1)));
field.description("Very good cats".to_string());
field.directive(directive);
assert_eq!(
field.to_string(),
r#" "Very good cats"
cat: [SpaceProgram] @testDirective(first: 1)"#
);
}
#[test]
fn it_encodes_fields_with_description() {
let ty_1 = Type_::NamedType {
name: "SpaceProgram".to_string(),
};
let ty_2 = Type_::NonNull { ty: Box::new(ty_1) };
let ty_3 = Type_::List { ty: Box::new(ty_2) };
let ty_4 = Type_::NonNull { ty: Box::new(ty_3) };
let mut field = FieldDefinition::new("spaceCat".to_string(), ty_4);
field.description("Very good space cats".to_string());
assert_eq!(
field.to_string(),
r#" "Very good space cats"
spaceCat: [SpaceProgram!]!"#
);
}
#[test]
fn it_encodes_fields_with_value_arguments() {
let ty_1 = Type_::NamedType {
name: "SpaceProgram".to_string(),
};
let ty_2 = Type_::NonNull { ty: Box::new(ty_1) };
let ty_3 = Type_::List { ty: Box::new(ty_2) };
let ty_4 = Type_::NonNull { ty: Box::new(ty_3) };
let mut field_definition = FieldDefinition::new("spaceCat".to_string(), ty_4);
field_definition.description("Very good space cats".to_string());
let value_1 = Type_::NamedType {
name: "SpaceProgram".to_string(),
};
let value_2 = Type_::List {
ty: Box::new(value_1),
};
let mut arg = InputValueDefinition::new("cat".to_string(), value_2);
let mut deprecated_directive = Directive::new(String::from("deprecated"));
deprecated_directive.arg(Argument::new(
String::from("reason"),
Value::String(String::from("Cats are no longer sent to space.")),
));
arg.directive(deprecated_directive);
field_definition.arg(arg);
assert_eq!(
field_definition.to_string(),
r#" "Very good space cats"
spaceCat(cat: [SpaceProgram] @deprecated(reason: "Cats are no longer sent to space.")): [SpaceProgram!]!"#
);
}
#[test]
fn it_encodes_fields_with_argument_descriptions() {
let ty = Type_::NamedType {
name: "Cat".to_string(),
};
let mut field_definition = FieldDefinition::new("spaceCat".to_string(), ty);
let value = Type_::NamedType {
name: "Treat".to_string(),
};
let mut arg = InputValueDefinition::new("treat".to_string(), value);
arg.description("The type of treats given in space".to_string());
field_definition.arg(arg);
let value = Type_::NamedType {
name: "Int".to_string(),
};
let mut arg = InputValueDefinition::new("age".to_string(), value);
arg.description("Optimal age of a \"space\" cat".to_string());
field_definition.arg(arg);
assert_eq!(
field_definition.to_string(),
r#" spaceCat(
"The type of treats given in space"
treat: Treat,
"""
Optimal age of a "space" cat
"""
age: Int
): Cat"#
);
}
}