Skip to main content

apollo_smith/
input_object.rs

1use crate::description::Description;
2use crate::directive::Directive;
3use crate::directive::DirectiveLocation;
4use crate::input_value::InputValueDef;
5use crate::name::Name;
6use crate::DocumentBuilder;
7use apollo_compiler::ast;
8use apollo_compiler::Node;
9use arbitrary::Result as ArbitraryResult;
10use indexmap::IndexMap;
11use indexmap::IndexSet;
12
13/// Input objects are composite types used as inputs into queries defined as a list of named input values..
14///
15/// InputObjectTypeDefinition
16///     Description? **input** Name Directives? FieldsDefinition?
17///
18/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-Input-Objects).
19///
20/// **Note**: At the moment InputObjectTypeDefinition differs slightly from the
21/// spec. Instead of accepting InputValues as `field` parameter, we accept
22/// InputField.
23#[derive(Debug, Clone)]
24pub struct InputObjectTypeDef {
25    pub(crate) name: Name,
26    pub(crate) description: Option<Description>,
27    // A vector of fields
28    pub(crate) fields: Vec<InputValueDef>,
29    /// Contains all directives.
30    pub(crate) directives: IndexMap<Name, Directive>,
31    pub(crate) extend: bool,
32}
33
34impl From<InputObjectTypeDef> for ast::Definition {
35    fn from(x: InputObjectTypeDef) -> Self {
36        if x.extend {
37            ast::InputObjectTypeExtension {
38                name: x.name.into(),
39                directives: Directive::to_ast(x.directives),
40                fields: x.fields.into_iter().map(|x| Node::new(x.into())).collect(),
41            }
42            .into()
43        } else {
44            ast::InputObjectTypeDefinition {
45                description: x.description.map(Into::into),
46                name: x.name.into(),
47                directives: Directive::to_ast(x.directives),
48                fields: x.fields.into_iter().map(|x| Node::new(x.into())).collect(),
49            }
50            .into()
51        }
52    }
53}
54
55impl TryFrom<apollo_parser::cst::InputObjectTypeDefinition> for InputObjectTypeDef {
56    type Error = crate::FromError;
57
58    fn try_from(
59        input_object: apollo_parser::cst::InputObjectTypeDefinition,
60    ) -> Result<Self, Self::Error> {
61        Ok(Self {
62            name: input_object
63                .name()
64                .expect("object type definition must have a name")
65                .into(),
66            description: input_object.description().map(Description::from),
67            directives: input_object
68                .directives()
69                .map(Directive::convert_directives)
70                .transpose()?
71                .unwrap_or_default(),
72            extend: false,
73            fields: input_object
74                .input_fields_definition()
75                .map(|input_fields| {
76                    input_fields
77                        .input_value_definitions()
78                        .map(InputValueDef::try_from)
79                        .collect::<Result<_, _>>()
80                })
81                .transpose()?
82                .unwrap_or_default(),
83        })
84    }
85}
86
87impl TryFrom<apollo_parser::cst::InputObjectTypeExtension> for InputObjectTypeDef {
88    type Error = crate::FromError;
89
90    fn try_from(
91        input_object: apollo_parser::cst::InputObjectTypeExtension,
92    ) -> Result<Self, Self::Error> {
93        Ok(Self {
94            name: input_object
95                .name()
96                .expect("object type definition must have a name")
97                .into(),
98            directives: input_object
99                .directives()
100                .map(Directive::convert_directives)
101                .transpose()?
102                .unwrap_or_default(),
103            extend: true,
104            fields: input_object
105                .input_fields_definition()
106                .map(|input_fields| {
107                    input_fields
108                        .input_value_definitions()
109                        .map(InputValueDef::try_from)
110                        .collect::<Result<Vec<_>, crate::FromError>>()
111                })
112                .transpose()?
113                .unwrap_or_default(),
114            description: None,
115        })
116    }
117}
118
119impl DocumentBuilder<'_> {
120    /// Create an arbitrary `InputObjectTypeDef`
121    pub fn input_object_type_definition(&mut self) -> ArbitraryResult<InputObjectTypeDef> {
122        let extend = !self.input_object_type_defs.is_empty() && self.u.arbitrary().unwrap_or(false);
123        let name = if extend {
124            let available_input_objects: Vec<&Name> = self
125                .input_object_type_defs
126                .iter()
127                .filter_map(|input_object| {
128                    if input_object.extend {
129                        None
130                    } else {
131                        Some(&input_object.name)
132                    }
133                })
134                .collect();
135            (*self.u.choose(&available_input_objects)?).clone()
136        } else {
137            self.type_name()?
138        };
139        let description = self
140            .u
141            .arbitrary()
142            .unwrap_or(false)
143            .then(|| self.description())
144            .transpose()?;
145        let exclude_fields: IndexSet<Name> = self
146            .input_object_type_defs
147            .iter()
148            .filter(|io| io.name == name)
149            .flat_map(|io| io.fields.iter().map(|f| f.name.clone()))
150            .collect();
151        let fields = self.input_values_def(
152            DirectiveLocation::InputFieldDefinition,
153            &exclude_fields,
154            Some(&name),
155        )?;
156
157        let directives = self.directives(DirectiveLocation::InputObject)?;
158
159        if extend && directives.is_empty() && fields.is_empty() {
160            return Err(arbitrary::Error::IncorrectFormat);
161        }
162
163        Ok(InputObjectTypeDef {
164            description,
165            directives,
166            name,
167            extend,
168            fields,
169        })
170    }
171}