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/September2025/#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
152        // Randomly apply @oneOf to this input object (~1-in-5 chance).  When
153        // we do, enforce the spec constraints on every field: all must be
154        // nullable and none may carry a default value.
155        let is_one_of: bool = self.u.int_in_range(0..=4usize).unwrap_or(1) == 0;
156        let mut fields = self.input_values_def(
157            DirectiveLocation::InputFieldDefinition,
158            &exclude_fields,
159            Some(&name),
160        )?;
161        if is_one_of {
162            for field in &mut fields {
163                field.ty = field.ty.clone().into_nullable();
164                field.default_value = None;
165            }
166        }
167
168        let mut directives = self.directives(DirectiveLocation::InputObject)?;
169        if is_one_of {
170            directives.insert(
171                Name::new(String::from("oneOf")),
172                Directive {
173                    name: Name::new(String::from("oneOf")),
174                    arguments: Vec::new(),
175                },
176            );
177        }
178
179        if extend && directives.is_empty() && fields.is_empty() {
180            return Err(arbitrary::Error::IncorrectFormat);
181        }
182
183        Ok(InputObjectTypeDef {
184            description,
185            directives,
186            name,
187            extend,
188            fields,
189        })
190    }
191}