use crate::argument::Argument;
use crate::argument::ArgumentsDef;
use crate::description::Description;
use crate::directive::Directive;
use crate::directive::DirectiveLocation;
use crate::name::Name;
use crate::selection_set::SelectionSet;
use crate::ty::Ty;
use crate::DocumentBuilder;
use apollo_compiler::ast;
use apollo_compiler::Node;
use arbitrary::Result as ArbitraryResult;
use indexmap::IndexMap;
use indexmap::IndexSet;
#[derive(Debug, Clone)]
pub struct FieldDef {
pub(crate) description: Option<Description>,
pub(crate) name: Name,
pub(crate) arguments_definition: Option<ArgumentsDef>,
pub(crate) ty: Ty,
pub(crate) directives: IndexMap<Name, Directive>,
}
impl From<FieldDef> for ast::FieldDefinition {
fn from(x: FieldDef) -> Self {
Self {
description: x.description.map(Into::into),
name: x.name.into(),
directives: Directive::to_ast(x.directives),
arguments: x.arguments_definition.map(Into::into).unwrap_or_default(),
ty: x.ty.into(),
}
}
}
impl TryFrom<apollo_parser::cst::FieldDefinition> for FieldDef {
type Error = crate::FromError;
fn try_from(field_def: apollo_parser::cst::FieldDefinition) -> Result<Self, Self::Error> {
Ok(Self {
description: field_def.description().map(Description::from),
name: field_def
.name()
.expect("field definition must have a name")
.into(),
arguments_definition: field_def
.arguments_definition()
.map(ArgumentsDef::try_from)
.transpose()?,
ty: field_def.ty().unwrap().into(),
directives: field_def
.directives()
.map(Directive::convert_directives)
.transpose()?
.unwrap_or_default(),
})
}
}
#[derive(Debug, Clone)]
pub struct Field {
pub(crate) alias: Option<Name>,
pub(crate) name: Name,
pub(crate) args: Vec<Argument>,
pub(crate) directives: IndexMap<Name, Directive>,
pub(crate) selection_set: Option<SelectionSet>,
}
impl From<Field> for ast::Field {
fn from(x: Field) -> Self {
Self {
alias: x.alias.map(Into::into),
name: x.name.into(),
directives: Directive::to_ast(x.directives),
arguments: x.args.into_iter().map(|x| Node::new(x.into())).collect(),
selection_set: x.selection_set.map(Into::into).unwrap_or_default(),
}
}
}
impl TryFrom<apollo_parser::cst::Field> for Field {
type Error = crate::FromError;
fn try_from(field: apollo_parser::cst::Field) -> Result<Self, Self::Error> {
Ok(Self {
alias: field.alias().map(|alias| alias.name().unwrap().into()),
name: field.name().unwrap().into(),
args: field
.arguments()
.map(|arguments| {
arguments
.arguments()
.map(Argument::try_from)
.collect::<Result<_, _>>()
})
.transpose()?
.unwrap_or_default(),
directives: field
.directives()
.map(Directive::convert_directives)
.transpose()?
.unwrap_or_default(),
selection_set: field
.selection_set()
.map(SelectionSet::try_from)
.transpose()?,
})
}
}
impl<'a> DocumentBuilder<'a> {
pub fn fields_definition(&mut self, exclude: &[&Name]) -> ArbitraryResult<Vec<FieldDef>> {
let num_fields = self.u.int_in_range(2..=50usize)?;
let mut fields_names = IndexSet::with_capacity(num_fields);
for i in 0..num_fields {
let name = self.name_with_index(i)?;
if !exclude.contains(&&name) {
fields_names.insert(name);
}
}
let available_types: Vec<Ty> = self.list_existing_types();
fields_names
.into_iter()
.map(|field_name| {
Ok(FieldDef {
description: self
.u
.arbitrary()
.unwrap_or(false)
.then(|| self.description())
.transpose()?,
name: field_name,
arguments_definition: self
.u
.arbitrary()
.unwrap_or(false)
.then(|| self.arguments_definition())
.transpose()?,
ty: self.choose_ty(&available_types)?,
directives: self.directives(DirectiveLocation::FieldDefinition)?,
})
})
.collect()
}
pub fn field(&mut self, index: usize) -> ArbitraryResult<Field> {
let fields_defs = self
.stack
.last()
.expect("an object type must be added on the stack")
.fields_def();
let chosen_field_def = self.u.choose(fields_defs)?.clone();
let mut alias = self
.u
.arbitrary()
.unwrap_or(false)
.then(|| self.name_with_index(index))
.transpose()?;
let name = chosen_field_def.name.clone();
let args = match self.chosen_arguments.get(&name) {
Some(args) => args.clone(),
None => {
let args = chosen_field_def
.arguments_definition
.clone()
.map(|args_def| self.arguments_with_def(&args_def))
.unwrap_or_else(|| Ok(vec![]))?;
self.chosen_arguments.insert(name.clone(), args.clone());
args
}
};
let directives = self.directives(DirectiveLocation::Field)?;
let selection_set = if !chosen_field_def.ty.is_builtin() {
if self.stack_ty(&chosen_field_def.ty) {
let res = Some(self.selection_set()?);
self.stack.pop();
res
} else {
None
}
} else {
None
};
if let Some(alias_name) = alias.take() {
match self.chosen_aliases.get(&alias_name) {
None => {
self.chosen_aliases.insert(alias_name.clone(), name.clone());
alias = Some(alias_name);
}
Some(original_field_name) => {
if original_field_name == &name {
alias = Some(alias_name);
}
}
}
}
Ok(Field {
alias,
name,
args,
directives,
selection_set,
})
}
}