Skip to main content

apollo_smith/
field.rs

1use crate::argument::Argument;
2use crate::argument::ArgumentsDef;
3use crate::description::Description;
4use crate::directive::Directive;
5use crate::directive::DirectiveLocation;
6use crate::name::Name;
7use crate::selection_set::SelectionSet;
8use crate::ty::Ty;
9use crate::DocumentBuilder;
10use apollo_compiler::ast;
11use apollo_compiler::coordinate::TypeAttributeCoordinate;
12use apollo_compiler::Node;
13use arbitrary::Result as ArbitraryResult;
14use indexmap::IndexMap;
15use indexmap::IndexSet;
16
17/// The __FieldDef type represents each field definition in an Object definition or Interface type definition.
18///
19/// *FieldDefinition*:
20///     Description? Name ArgumentsDefinition? **:** Type Directives?
21///
22/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#FieldDefinition).
23#[derive(Debug, Clone)]
24pub struct FieldDef {
25    pub(crate) description: Option<Description>,
26    pub(crate) name: Name,
27    pub(crate) arguments_definition: Option<ArgumentsDef>,
28    pub(crate) ty: Ty,
29    pub(crate) directives: IndexMap<Name, Directive>,
30}
31
32impl From<FieldDef> for ast::FieldDefinition {
33    fn from(x: FieldDef) -> Self {
34        Self {
35            description: x.description.map(Into::into),
36            name: x.name.into(),
37            directives: Directive::to_ast(x.directives),
38            arguments: x.arguments_definition.map(Into::into).unwrap_or_default(),
39            ty: x.ty.into(),
40        }
41    }
42}
43
44impl TryFrom<apollo_parser::cst::FieldDefinition> for FieldDef {
45    type Error = crate::FromError;
46
47    fn try_from(field_def: apollo_parser::cst::FieldDefinition) -> Result<Self, Self::Error> {
48        Ok(Self {
49            description: field_def.description().map(Description::from),
50            name: field_def
51                .name()
52                .expect("field definition must have a name")
53                .into(),
54            arguments_definition: field_def
55                .arguments_definition()
56                .map(ArgumentsDef::try_from)
57                .transpose()?,
58            ty: field_def.ty().unwrap().into(),
59            directives: field_def
60                .directives()
61                .map(Directive::convert_directives)
62                .transpose()?
63                .unwrap_or_default(),
64        })
65    }
66}
67
68/// The __Field type represents each field in an Object or Interface type.
69///
70/// *Field*:
71///     Alias? Name Arguments? Directives? SelectionSet?
72///
73/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Language.Fields).
74#[derive(Debug, Clone)]
75pub struct Field {
76    pub(crate) alias: Option<Name>,
77    pub(crate) name: Name,
78    pub(crate) args: Vec<Argument>,
79    pub(crate) directives: IndexMap<Name, Directive>,
80    pub(crate) selection_set: Option<SelectionSet>,
81}
82
83impl From<Field> for ast::Field {
84    fn from(x: Field) -> Self {
85        Self {
86            alias: x.alias.map(Into::into),
87            name: x.name.into(),
88            directives: Directive::to_ast(x.directives),
89            arguments: x.args.into_iter().map(|x| Node::new(x.into())).collect(),
90            selection_set: x.selection_set.map(Into::into).unwrap_or_default(),
91        }
92    }
93}
94
95impl TryFrom<apollo_parser::cst::Field> for Field {
96    type Error = crate::FromError;
97
98    fn try_from(field: apollo_parser::cst::Field) -> Result<Self, Self::Error> {
99        Ok(Self {
100            alias: field.alias().map(|alias| alias.name().unwrap().into()),
101            name: field.name().unwrap().into(),
102            args: field
103                .arguments()
104                .map(|arguments| {
105                    arguments
106                        .arguments()
107                        .map(Argument::try_from)
108                        .collect::<Result<_, _>>()
109                })
110                .transpose()?
111                .unwrap_or_default(),
112            directives: field
113                .directives()
114                .map(Directive::convert_directives)
115                .transpose()?
116                .unwrap_or_default(),
117            selection_set: field
118                .selection_set()
119                .map(SelectionSet::try_from)
120                .transpose()?,
121        })
122    }
123}
124
125impl DocumentBuilder<'_> {
126    /// Create an arbitrary list of `FieldDef`
127    pub fn fields_definition(
128        &mut self,
129        exclude: &IndexSet<Name>,
130    ) -> ArbitraryResult<Vec<FieldDef>> {
131        let num_fields = self.u.int_in_range(2..=50usize)?;
132        let mut fields_names = IndexSet::with_capacity(num_fields);
133
134        for i in 0..num_fields {
135            let name = self.name_with_index(i)?;
136            if !exclude.contains(&name) {
137                fields_names.insert(name);
138            }
139        }
140
141        // TODO add mechanism to add own type for recursive type
142        let available_types: Vec<Ty> = self.list_existing_types();
143
144        fields_names
145            .into_iter()
146            .map(|field_name| {
147                Ok(FieldDef {
148                    description: self
149                        .u
150                        .arbitrary()
151                        .unwrap_or(false)
152                        .then(|| self.description())
153                        .transpose()?,
154                    name: field_name,
155                    arguments_definition: self
156                        .u
157                        .arbitrary()
158                        .unwrap_or(false)
159                        .then(|| self.arguments_definition())
160                        .transpose()?,
161                    ty: self.choose_ty(&available_types)?,
162                    directives: self.directives(DirectiveLocation::FieldDefinition)?,
163                })
164            })
165            .collect()
166    }
167
168    /// Create an arbitrary `Field` given an object type
169    pub fn field(&mut self, _index: usize) -> ArbitraryResult<Field> {
170        let fields_defs = self
171            .stack
172            .last()
173            .expect("an object type must be added on the stack")
174            .fields_def();
175
176        let chosen_field_def = self.u.choose(fields_defs)?.clone();
177
178        let name = chosen_field_def.name.clone();
179        let coord = TypeAttributeCoordinate {
180            ty: self.stack.last().unwrap().name().clone().into(),
181            attribute: name.clone().into(),
182        };
183        // To not have same selection with different arguments
184        let args = match self.chosen_arguments.get(&coord) {
185            Some(args) => args.clone(),
186            None => {
187                let args = chosen_field_def
188                    .arguments_definition
189                    .clone()
190                    .map(|args_def| self.arguments_with_def(&args_def))
191                    .unwrap_or_else(|| Ok(vec![]))?;
192                self.chosen_arguments.insert(coord, args.clone());
193
194                args
195            }
196        };
197        let directives = self.directives(DirectiveLocation::Field)?;
198
199        let selection_set = if !chosen_field_def.ty.is_builtin() {
200            // Put current ty on the stack
201            if self.stack_ty(&chosen_field_def.ty) {
202                let res = Some(self.selection_set()?);
203                self.stack.pop();
204                res
205            } else {
206                None
207            }
208        } else {
209            None
210        };
211
212        // TODO: Reintroduce alias generation logic which respects aliases on other fields
213        // or fragments. For now, we will not generate aliases to avoid conflicts.
214        // See <https://spec.graphql.org/October2021/#sec-Field-Selection-Merging> for merge
215        // requirements.
216
217        Ok(Field {
218            alias: None,
219            name,
220            args,
221            directives,
222            selection_set,
223        })
224    }
225}