1use indexmap::IndexMap;
2
3use super::{directive::to_meta_directive_invocation, Directive};
4use crate::{
5 dynamic::InputValue,
6 registry::{MetaInputValue, MetaType, Registry},
7};
8
9#[derive(Debug)]
54pub struct InputObject {
55 pub(crate) name: String,
56 pub(crate) description: Option<String>,
57 pub(crate) fields: IndexMap<String, InputValue>,
58 pub(crate) oneof: bool,
59 inaccessible: bool,
60 tags: Vec<String>,
61 directives: Vec<Directive>,
62}
63
64impl InputObject {
65 #[inline]
67 pub fn new(name: impl Into<String>) -> Self {
68 Self {
69 name: name.into(),
70 description: None,
71 fields: Default::default(),
72 oneof: false,
73 inaccessible: false,
74 tags: Vec::new(),
75 directives: Vec::new(),
76 }
77 }
78
79 impl_set_description!();
80 impl_set_inaccessible!();
81 impl_set_tags!();
82 impl_directive!();
83
84 #[inline]
86 pub fn field(mut self, field: InputValue) -> Self {
87 assert!(
88 !self.fields.contains_key(&field.name),
89 "Field `{}` already exists",
90 field.name
91 );
92 self.fields.insert(field.name.clone(), field);
93 self
94 }
95
96 pub fn oneof(self) -> Self {
98 Self {
99 oneof: true,
100 ..self
101 }
102 }
103
104 #[inline]
106 pub fn type_name(&self) -> &str {
107 &self.name
108 }
109
110 pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), super::SchemaError> {
111 let mut input_fields = IndexMap::new();
112
113 for field in self.fields.values() {
114 input_fields.insert(
115 field.name.clone(),
116 MetaInputValue {
117 name: field.name.clone(),
118 description: field.description.clone(),
119 ty: field.ty.to_string(),
120 deprecation: field.deprecation.clone(),
121 default_value: field.default_value.as_ref().map(ToString::to_string),
122 visible: None,
123 inaccessible: self.inaccessible,
124 tags: self.tags.clone(),
125 is_secret: false,
126 directive_invocations: to_meta_directive_invocation(field.directives.clone()),
127 },
128 );
129 }
130
131 registry.types.insert(
132 self.name.clone(),
133 MetaType::InputObject {
134 name: self.name.clone(),
135 description: self.description.clone(),
136 input_fields,
137 visible: None,
138 inaccessible: self.inaccessible,
139 tags: self.tags.clone(),
140 rust_typename: None,
141 oneof: self.oneof,
142 directive_invocations: to_meta_directive_invocation(self.directives.clone()),
143 },
144 );
145
146 Ok(())
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use crate::{dynamic::*, value, Pos, ServerError, Value};
153
154 #[tokio::test]
155 async fn input_object() {
156 let myinput = InputObject::new("MyInput")
157 .field(InputValue::new("a", TypeRef::named_nn(TypeRef::INT)))
158 .field(InputValue::new("b", TypeRef::named_nn(TypeRef::INT)));
159 let query = Object::new("Query").field(
160 Field::new("add", TypeRef::named_nn(TypeRef::INT), |ctx| {
161 FieldFuture::new(async move {
162 let input = ctx.args.try_get("input")?;
163 let input = input.object()?;
164 let a = input.try_get("a")?.i64()?;
165 let b = input.try_get("b")?.i64()?;
166 Ok(Some(Value::from(a + b)))
167 })
168 })
169 .argument(InputValue::new(
170 "input",
171 TypeRef::named_nn(myinput.type_name()),
172 )),
173 );
174
175 let schema = Schema::build(query.type_name(), None, None)
176 .register(query)
177 .register(myinput)
178 .finish()
179 .unwrap();
180
181 assert_eq!(
182 schema
183 .execute("{ add(input: {a: 10, b: 20}) }")
184 .await
185 .into_result()
186 .unwrap()
187 .data,
188 value!({
189 "add": 30
190 })
191 );
192 }
193
194 #[tokio::test]
195 async fn oneof_input_object() {
196 let myinput = InputObject::new("MyInput")
197 .oneof()
198 .field(InputValue::new("a", TypeRef::named(TypeRef::INT)))
199 .field(InputValue::new("b", TypeRef::named(TypeRef::INT)));
200
201 let query = Object::new("Query").field(
202 Field::new("add10", TypeRef::named_nn(TypeRef::INT), |ctx| {
203 FieldFuture::new(async move {
204 let input = ctx.args.try_get("input")?;
205 let input = input.object()?;
206 Ok(Some(Value::from(if let Some(a) = input.get("a") {
207 a.i64()? + 10
208 } else if let Some(b) = input.get("b") {
209 b.i64()? + 10
210 } else {
211 unreachable!()
212 })))
213 })
214 })
215 .argument(InputValue::new(
216 "input",
217 TypeRef::named_nn(myinput.type_name()),
218 )),
219 );
220
221 let schema = Schema::build(query.type_name(), None, None)
222 .register(query)
223 .register(myinput)
224 .finish()
225 .unwrap();
226
227 assert_eq!(
228 schema
229 .execute("{ add10(input: {a: 10}) }")
230 .await
231 .into_result()
232 .unwrap()
233 .data,
234 value!({
235 "add10": 20
236 })
237 );
238
239 assert_eq!(
240 schema
241 .execute("{ add10(input: {b: 20}) }")
242 .await
243 .into_result()
244 .unwrap()
245 .data,
246 value!({
247 "add10": 30
248 })
249 );
250
251 assert_eq!(
252 schema
253 .execute("{ add10(input: {}) }")
254 .await
255 .into_result()
256 .unwrap_err(),
257 vec![ServerError {
258 message: "Invalid value for argument \"input\", Oneof input objects requires have exactly one field".to_owned(),
259 source: None,
260 locations: vec![Pos { column: 9, line: 1 }],
261 path: vec![],
262 extensions: None,
263 }]
264 );
265
266 assert_eq!(
267 schema
268 .execute("{ add10(input: { a: 10, b: 20 }) }")
269 .await
270 .into_result()
271 .unwrap_err(),
272 vec![ServerError {
273 message: "Invalid value for argument \"input\", Oneof input objects requires have exactly one field".to_owned(),
274 source: None,
275 locations: vec![Pos { column: 9, line: 1 }],
276 path: vec![],
277 extensions: None,
278 }]
279 );
280 }
281
282 #[tokio::test]
283 async fn invalid_oneof_input_object() {
284 let myinput = InputObject::new("MyInput")
285 .oneof()
286 .field(InputValue::new("a", TypeRef::named(TypeRef::INT)))
287 .field(InputValue::new("b", TypeRef::named_nn(TypeRef::INT)));
288
289 let query = Object::new("Query").field(
290 Field::new("value", TypeRef::named_nn(TypeRef::INT), |_| {
291 FieldFuture::new(async move { Ok(Some(Value::from(10))) })
292 })
293 .argument(InputValue::new(
294 "input",
295 TypeRef::named_nn(myinput.type_name()),
296 )),
297 );
298
299 let err = Schema::build(query.type_name(), None, None)
300 .register(query)
301 .register(myinput)
302 .finish()
303 .unwrap_err();
304 assert_eq!(err.0, "Field \"MyInput.b\" must be nullable".to_string());
305
306 let myinput = InputObject::new("MyInput")
307 .oneof()
308 .field(InputValue::new("a", TypeRef::named(TypeRef::INT)))
309 .field(InputValue::new("b", TypeRef::named(TypeRef::INT)).default_value(value!(10)));
310
311 let query = Object::new("Query").field(
312 Field::new("value", TypeRef::named_nn(TypeRef::INT), |_| {
313 FieldFuture::new(async move { Ok(Some(Value::from(10))) })
314 })
315 .argument(InputValue::new(
316 "input",
317 TypeRef::named_nn(myinput.type_name()),
318 )),
319 );
320
321 let err = Schema::build(query.type_name(), None, None)
322 .register(query)
323 .register(myinput)
324 .finish()
325 .unwrap_err();
326 assert_eq!(
327 err.0,
328 "Field \"MyInput.b\" must not have a default value".to_string()
329 );
330 }
331}