1use crate::Path;
2use bluejay_core::definition::{
3 BaseInputTypeReference, EnumTypeDefinition, EnumValueDefinition, InputFieldsDefinition,
4 InputObjectTypeDefinition, InputType, InputTypeReference, InputValueDefinition,
5 ScalarTypeDefinition, SchemaDefinition,
6};
7use bluejay_core::{
8 AsIter, BuiltinScalarDefinition, Directive, ObjectValue, Value, ValueReference,
9};
10use std::collections::BTreeMap;
11
12mod error;
13
14pub use error::Error;
15
16pub trait CoerceInput: SchemaDefinition {
17 fn coerce_value<
18 'a,
19 const CONST: bool,
20 I: InputType<
21 CustomScalarTypeDefinition = Self::CustomScalarTypeDefinition,
22 InputObjectTypeDefinition = Self::InputObjectTypeDefinition,
23 EnumTypeDefinition = Self::EnumTypeDefinition,
24 >,
25 V: Value<CONST>,
26 >(
27 &'a self,
28 input_type: &'a I,
29 value: &'a V,
30 path: Path<'a>,
31 ) -> Result<(), Vec<Error<'a, CONST, V>>>;
32
33 fn coerce_const_value<
34 'a,
35 I: InputType<
36 CustomScalarTypeDefinition = Self::CustomScalarTypeDefinition,
37 InputObjectTypeDefinition = Self::InputObjectTypeDefinition,
38 EnumTypeDefinition = Self::EnumTypeDefinition,
39 >,
40 V: Value<true>,
41 >(
42 &'a self,
43 input_type: &'a I,
44 value: &'a V,
45 path: Path<'a>,
46 ) -> Result<(), Vec<Error<'a, true, V>>> {
47 self.coerce_value(input_type, value, path)
48 }
49}
50
51impl<S: SchemaDefinition> CoerceInput for S {
52 fn coerce_value<
53 'a,
54 const CONST: bool,
55 I: InputType<
56 CustomScalarTypeDefinition = Self::CustomScalarTypeDefinition,
57 InputObjectTypeDefinition = Self::InputObjectTypeDefinition,
58 EnumTypeDefinition = Self::EnumTypeDefinition,
59 >,
60 V: Value<CONST>,
61 >(
62 &'a self,
63 input_type: &'a I,
64 value: &'a V,
65 path: Path<'a>,
66 ) -> Result<(), Vec<Error<'a, CONST, V>>> {
67 coerce_value_for_input_type(self, input_type, value, path, true)
68 }
69}
70
71fn coerce_value_for_input_type<
72 'a,
73 const CONST: bool,
74 S: SchemaDefinition,
75 T: InputType<
76 CustomScalarTypeDefinition = S::CustomScalarTypeDefinition,
77 InputObjectTypeDefinition = S::InputObjectTypeDefinition,
78 EnumTypeDefinition = S::EnumTypeDefinition,
79 >,
80 V: Value<CONST>,
81>(
82 schema_definition: &'a S,
83 input_type: &'a T,
84 value: &'a V,
85 path: Path<'a>,
86 allow_implicit_list: bool,
87) -> Result<(), Vec<Error<'a, CONST, V>>> {
88 let core_type = input_type.as_ref(schema_definition);
89 let is_required = core_type.is_required();
90 match value.as_ref() {
91 ValueReference::Null if is_required => Err(vec![Error::NullValueForRequiredType {
92 value,
93 input_type_name: input_type.display_name(),
94 path,
95 }]),
96 ValueReference::Null | ValueReference::Variable(_) => Ok(()),
97 core_value => match core_type {
98 InputTypeReference::Base(_, _) => {
99 coerce_value_for_base_input_type(schema_definition, input_type, value, path)
100 }
101 InputTypeReference::List(inner, _) => {
102 if let ValueReference::List(values) = core_value {
103 let errors: Vec<Error<'a, CONST, V>> = values
104 .iter()
105 .enumerate()
106 .flat_map(|(idx, value)| {
107 coerce_value_for_input_type(
108 schema_definition,
109 inner,
110 value,
111 path.push(idx),
112 false,
113 )
114 .err()
115 .unwrap_or_default()
116 })
117 .collect();
118
119 if errors.is_empty() {
120 Ok(())
121 } else {
122 Err(errors)
123 }
124 } else if allow_implicit_list {
125 coerce_value_for_input_type(schema_definition, inner, value, path, true)
126 } else {
127 Err(vec![Error::NoImplicitConversion {
128 value,
129 input_type_name: input_type.display_name(),
130 path,
131 }])
132 }
133 }
134 },
135 }
136}
137
138fn coerce_value_for_base_input_type<
139 'a,
140 const CONST: bool,
141 S: SchemaDefinition,
142 T: InputType<
143 CustomScalarTypeDefinition = S::CustomScalarTypeDefinition,
144 InputObjectTypeDefinition = S::InputObjectTypeDefinition,
145 EnumTypeDefinition = S::EnumTypeDefinition,
146 >,
147 V: Value<CONST>,
148>(
149 schema_definition: &'a S,
150 input_type: &'a T,
151 value: &'a V,
152 path: Path<'a>,
153) -> Result<(), Vec<Error<'a, CONST, V>>> {
154 let base = input_type.base(schema_definition);
155 match base {
156 BaseInputTypeReference::BuiltinScalar(bstd) => {
157 coerce_builtin_scalar_value(input_type, bstd, value, path)
158 }
159 BaseInputTypeReference::CustomScalar(cstd) => coerce_custom_scalar_value(cstd, value, path),
160 BaseInputTypeReference::Enum(etd) => coerce_enum_value(input_type, etd, value, path),
161 BaseInputTypeReference::InputObject(iotd) => {
162 coerce_input_object_value(schema_definition, input_type, iotd, value, path)
163 }
164 }
165}
166
167fn coerce_builtin_scalar_value<'a, const CONST: bool, V: Value<CONST>, T: InputType>(
168 input_type: &'a T,
169 bstd: BuiltinScalarDefinition,
170 value: &'a V,
171 path: Path<'a>,
172) -> Result<(), Vec<Error<'a, CONST, V>>> {
173 match (bstd, value.as_ref()) {
174 (BuiltinScalarDefinition::Boolean, ValueReference::Boolean(_)) => Ok(()),
175 (BuiltinScalarDefinition::Float, ValueReference::Float(_)) => Ok(()),
176 (BuiltinScalarDefinition::Float, ValueReference::Integer(_)) => Ok(()),
177 (BuiltinScalarDefinition::ID, ValueReference::Integer(_)) => Ok(()),
178 (
179 BuiltinScalarDefinition::ID | BuiltinScalarDefinition::String,
180 ValueReference::String(_),
181 ) => Ok(()),
182 (BuiltinScalarDefinition::Int, ValueReference::Integer(_)) => Ok(()),
183 _ => Err(vec![Error::NoImplicitConversion {
184 value,
185 input_type_name: input_type.display_name(),
186 path,
187 }]),
188 }
189}
190
191fn coerce_custom_scalar_value<'a, const CONST: bool, V: Value<CONST>>(
192 cstd: &'a impl ScalarTypeDefinition,
193 value: &'a V,
194 path: Path<'a>,
195) -> Result<(), Vec<Error<'a, CONST, V>>> {
196 cstd.coerce_input(value).map_err(|message| {
197 vec![Error::CustomScalarInvalidValue {
198 value,
199 custom_scalar_type_name: cstd.name(),
200 message,
201 path,
202 }]
203 })
204}
205
206fn coerce_enum_value<'a, const CONST: bool, V: Value<CONST>, T: InputType>(
207 input_type: &'a T,
208 enum_type_definition: &'a T::EnumTypeDefinition,
209 value: &'a V,
210 path: Path<'a>,
211) -> Result<(), Vec<Error<'a, CONST, V>>> {
212 match value.as_ref() {
213 ValueReference::Enum(name) => {
214 coerce_enum_value_from_name(enum_type_definition, value, name, path)
215 }
216 ValueReference::String(name) if V::can_coerce_string_value_to_enum() => {
217 coerce_enum_value_from_name(enum_type_definition, value, name, path)
218 }
219 _ => Err(vec![Error::NoImplicitConversion {
220 value,
221 input_type_name: input_type.display_name(),
222 path,
223 }]),
224 }
225}
226
227fn coerce_enum_value_from_name<'a, const CONST: bool, V: Value<CONST>>(
228 enum_type_definition: &'a impl EnumTypeDefinition,
229 value: &'a V,
230 name: &'a str,
231 path: Path<'a>,
232) -> Result<(), Vec<Error<'a, CONST, V>>> {
233 if enum_type_definition
234 .enum_value_definitions()
235 .iter()
236 .any(|evd| evd.name() == name)
237 {
238 Ok(())
239 } else {
240 Err(vec![Error::NoEnumMemberWithName {
241 name,
242 value,
243 enum_type_name: enum_type_definition.name(),
244 path,
245 }])
246 }
247}
248
249fn coerce_input_object_value<
250 'a,
251 const CONST: bool,
252 S: SchemaDefinition,
253 T: InputType<
254 CustomScalarTypeDefinition = S::CustomScalarTypeDefinition,
255 InputObjectTypeDefinition = S::InputObjectTypeDefinition,
256 EnumTypeDefinition = S::EnumTypeDefinition,
257 >,
258 V: Value<CONST>,
259>(
260 schema_definition: &'a S,
261 input_type: &'a T,
262 input_object_type_definition: &'a T::InputObjectTypeDefinition,
263 value: &'a V,
264 path: Path<'a>,
265) -> Result<(), Vec<Error<'a, CONST, V>>> {
266 if let ValueReference::Object(object) = value.as_ref() {
267 let mut errors = Vec::new();
268 let mut missing_required_values = Vec::new();
269
270 type Entry<'a, const CONST: bool, V> = (
271 &'a <<V as Value<CONST>>::Object as ObjectValue<CONST>>::Key,
272 &'a V,
273 );
274 let indexed_object: BTreeMap<&'a str, Vec<Entry<'a, CONST, V>>> =
275 object
276 .iter()
277 .fold(BTreeMap::new(), |mut index, (key, value)| {
278 index.entry(key.as_ref()).or_default().push((key, value));
279 index
280 });
281
282 errors.extend(
283 indexed_object
284 .iter()
285 .filter(|(_, entries)| (entries.len() > 1))
286 .map(|(&field_name, entries)| Error::NonUniqueFieldNames {
287 value,
288 field_name,
289 keys: Vec::from_iter(entries.iter().map(|&(key, _)| key)),
290 path: path.clone(),
291 }),
292 );
293
294 errors.extend(
295 object
296 .iter()
297 .filter(|(field, _)| {
298 input_object_type_definition
299 .input_field_definitions()
300 .get(field.as_ref())
301 .is_none()
302 })
303 .map(|(field, _)| Error::NoInputFieldWithName {
304 field,
305 input_object_type_name: input_object_type_definition.name(),
306 path: path.push(field.as_ref()),
307 }),
308 );
309
310 input_object_type_definition
311 .input_field_definitions()
312 .iter()
313 .for_each(|ivd| {
314 let value_for_field = indexed_object
315 .get(ivd.name())
316 .and_then(|entries| entries.first().copied().map(|(_, value)| value));
317 let default_value = ivd.default_value();
318
319 match (value_for_field, default_value) {
320 (None, None) => {
321 if ivd.r#type().is_required() {
322 missing_required_values.push(ivd.name());
323 }
324 }
325 (None, Some(_)) => {}
326 (Some(value), _) => {
327 match schema_definition.coerce_value(
328 ivd.r#type(),
329 value,
330 path.push(ivd.name()),
331 ) {
332 Ok(_) => {}
333 Err(errs) => errors.extend(errs),
334 }
335 }
336 }
337 });
338
339 #[cfg(feature = "one-of-input-objects")]
340 if let Err(one_of_errors) = validate_one_of_input_object_value(
341 input_object_type_definition,
342 value,
343 object,
344 path.clone(),
345 ) {
346 errors.extend(one_of_errors);
347 }
348
349 if !missing_required_values.is_empty() {
350 errors.push(Error::NoValueForRequiredFields {
351 value,
352 field_names: missing_required_values,
353 input_object_type_name: input_object_type_definition.name(),
354 path,
355 });
356 }
357
358 if errors.is_empty() {
359 Ok(())
360 } else {
361 Err(errors)
362 }
363 } else {
364 Err(vec![Error::NoImplicitConversion {
365 value,
366 input_type_name: input_type.display_name(),
367 path,
368 }])
369 }
370}
371
372#[cfg(feature = "one-of-input-objects")]
373fn validate_one_of_input_object_value<'a, const CONST: bool, V: Value<CONST>>(
374 input_object_type_definition: &'a impl InputObjectTypeDefinition,
375 value: &'a V,
376 object: &'a V::Object,
377 path: Path<'a>,
378) -> Result<(), Vec<Error<'a, CONST, V>>> {
379 if input_object_type_definition
380 .directives()
381 .map(|directives| {
382 directives
383 .iter()
384 .any(|directive| directive.name() == "oneOf")
385 })
386 .unwrap_or(false)
387 {
388 let (null_entries, non_null_entries): (Vec<_>, Vec<_>) = object
389 .iter()
390 .partition(|(_, value)| matches!(value.as_ref(), ValueReference::Null));
391
392 let mut errors = Vec::new();
393
394 if !null_entries.is_empty() {
395 errors.push(Error::OneOfInputNullValues {
396 value,
397 input_object_type_name: input_object_type_definition.name(),
398 null_entries,
399 path: path.clone(),
400 });
401 }
402
403 if non_null_entries.len() != 1 {
404 errors.push(Error::OneOfInputNotSingleNonNullValue {
405 value,
406 input_object_type_name: input_object_type_definition.name(),
407 non_null_entries,
408 path,
409 });
410 }
411
412 if errors.is_empty() {
413 Ok(())
414 } else {
415 Err(errors)
416 }
417 } else {
418 Ok(())
419 }
420}
421
422#[cfg(test)]
423mod tests {
424 use super::{CoerceInput, Error};
425 use crate::Path;
426 use bluejay_core::definition::{
427 ArgumentsDefinition, FieldDefinition, FieldsDefinition, InputType, InputValueDefinition,
428 ObjectTypeDefinition, ScalarTypeDefinition, SchemaDefinition,
429 };
430 use bluejay_core::{Value, ValueReference};
431 use bluejay_parser::ast::{
432 definition::{
433 Context, CustomScalarTypeDefinition, DefinitionDocument, InputType as ParserInputType,
434 SchemaDefinition as ParserSchemaDefinition,
435 },
436 Parse,
437 };
438 use once_cell::sync::Lazy;
439 use serde_json::json;
440 use std::borrow::Cow;
441
442 #[derive(Debug)]
443 struct CustomContext;
444
445 impl Context for CustomContext {
446 fn coerce_custom_scalar_input<const CONST: bool>(
447 cstd: &CustomScalarTypeDefinition<Self>,
448 value: &impl Value<CONST>,
449 ) -> Result<(), Cow<'static, str>> {
450 let value = value.as_ref();
451 match cstd.name() {
452 "Decimal" => {
453 if let ValueReference::String(s) = value {
454 s.parse::<f64>()
455 .map_err(|_| Cow::Owned(format!("Unable to parse `{s}` to Decimal")))
456 .and_then(|f| {
457 if f.is_finite() {
458 Ok(())
459 } else {
460 Err(Cow::Borrowed("Decimal values must be finite"))
461 }
462 })
463 } else {
464 Err(Cow::Owned(format!(
465 "Cannot coerce {} to Decimal",
466 value.variant()
467 )))
468 }
469 }
470 _ => Ok(()),
471 }
472 }
473 }
474
475 const SCHEMA: &str = r#"
476 type Query {
477 field(
478 stringArg: String!
479 intArg: Int!
480 floatArg: Float!
481 idArg: ID!
482 booleanArg: Boolean!
483 optionalArg: Int
484 optionalListArg: [Int]
485 optionalListOfListArg: [[Int]]
486 enumArg: Choices!
487 inputObjectArg: CustomInput!
488 decimalArg: Decimal!
489 oneOfInputObjectArg: InputUnion!
490 ): Boolean!
491 }
492
493 enum Choices {
494 FIRST
495 SECOND
496 }
497
498 input CustomInput {
499 stringArg: String!
500 optionalStringArg: String
501 stringArgWithDefault: String! = ""
502 }
503
504 scalar Decimal
505
506 input InputUnion @oneOf {
507 first: String
508 second: Int
509 }
510 "#;
511
512 static DEFINITION_DOCUMENT: Lazy<DefinitionDocument<'static, CustomContext>> =
513 Lazy::new(|| DefinitionDocument::parse(SCHEMA).expect("Schema had parse errors"));
514 static SCHEMA_DEFINITION: Lazy<ParserSchemaDefinition<'static, CustomContext>> =
515 Lazy::new(|| {
516 ParserSchemaDefinition::try_from(&*DEFINITION_DOCUMENT).expect("Schema had errors")
517 });
518
519 fn input_type(
520 type_name: &str,
521 field_name: &str,
522 arg_name: &str,
523 ) -> &'static ParserInputType<'static, CustomContext> {
524 SCHEMA_DEFINITION
525 .get_type_definition(type_name)
526 .unwrap()
527 .into_object()
528 .unwrap()
529 .fields_definition()
530 .get(field_name)
531 .unwrap()
532 .arguments_definition()
533 .unwrap()
534 .get(arg_name)
535 .unwrap()
536 .r#type()
537 }
538
539 #[test]
540 fn test_string() {
541 let it = input_type("Query", "field", "stringArg");
542
543 assert_eq!(
544 Ok(()),
545 SCHEMA_DEFINITION.coerce_const_value(
546 it,
547 &json!("This is a string"),
548 Default::default()
549 )
550 );
551 assert_eq!(
552 Err(vec![Error::NoImplicitConversion {
553 value: &json!(123),
554 input_type_name: it.display_name(),
555 path: Default::default(),
556 }]),
557 SCHEMA_DEFINITION.coerce_const_value(it, &json!(123), Default::default()),
558 );
559 }
560
561 #[test]
562 fn test_int() {
563 let it = input_type("Query", "field", "intArg");
564
565 assert_eq!(
566 Ok(()),
567 SCHEMA_DEFINITION.coerce_const_value(it, &json!(123), Default::default())
568 );
569 assert_eq!(
570 Err(vec![Error::NoImplicitConversion {
571 value: &json!(123.4),
572 input_type_name: it.display_name(),
573 path: Default::default(),
574 }]),
575 SCHEMA_DEFINITION.coerce_const_value(it, &json!(123.4), Default::default()),
576 );
577 }
578
579 #[test]
580 fn test_float() {
581 let it = input_type("Query", "field", "floatArg");
582
583 assert_eq!(
584 Ok(()),
585 SCHEMA_DEFINITION.coerce_const_value(it, &json!(123.456), Default::default())
586 );
587 assert_eq!(
588 Ok(()),
589 SCHEMA_DEFINITION.coerce_const_value(it, &json!(123), Default::default())
590 );
591 assert_eq!(
592 Err(vec![Error::NoImplicitConversion {
593 value: &json!("123.4"),
594 input_type_name: it.display_name(),
595 path: Default::default(),
596 }]),
597 SCHEMA_DEFINITION.coerce_const_value(it, &json!("123.4"), Default::default()),
598 );
599 }
600
601 #[test]
602 fn test_id() {
603 let it = input_type("Query", "field", "idArg");
604
605 assert_eq!(
606 Ok(()),
607 SCHEMA_DEFINITION.coerce_const_value(it, &json!(1), Default::default())
608 );
609 assert_eq!(
610 Ok(()),
611 SCHEMA_DEFINITION.coerce_const_value(it, &json!("a"), Default::default())
612 );
613 assert_eq!(
614 Err(vec![Error::NoImplicitConversion {
615 value: &json!(123.4),
616 input_type_name: it.display_name(),
617 path: Default::default(),
618 }]),
619 SCHEMA_DEFINITION.coerce_const_value(it, &json!(123.4), Default::default()),
620 );
621 }
622
623 #[test]
624 fn test_boolean() {
625 let it = input_type("Query", "field", "booleanArg");
626
627 assert_eq!(
628 Ok(()),
629 SCHEMA_DEFINITION.coerce_const_value(it, &json!(true), Default::default())
630 );
631 assert_eq!(
632 Ok(()),
633 SCHEMA_DEFINITION.coerce_const_value(it, &json!(false), Default::default())
634 );
635 assert_eq!(
636 Err(vec![Error::NoImplicitConversion {
637 value: &json!(1),
638 input_type_name: it.display_name(),
639 path: Default::default(),
640 }]),
641 SCHEMA_DEFINITION.coerce_const_value(it, &json!(1), Default::default()),
642 );
643 assert_eq!(
644 Err(vec![Error::NoImplicitConversion {
645 value: &json!("true"),
646 input_type_name: it.display_name(),
647 path: Default::default(),
648 }]),
649 SCHEMA_DEFINITION.coerce_const_value(it, &json!("true"), Default::default()),
650 );
651 }
652
653 #[test]
654 fn test_optional() {
655 let it = input_type("Query", "field", "optionalArg");
656
657 assert_eq!(
658 Ok(()),
659 SCHEMA_DEFINITION.coerce_const_value(it, &json!(null), Default::default())
660 );
661 assert_eq!(
662 Ok(()),
663 SCHEMA_DEFINITION.coerce_const_value(it, &json!(123), Default::default())
664 );
665 assert_eq!(
666 Err(vec![Error::NoImplicitConversion {
667 value: &json!("123"),
668 input_type_name: it.display_name(),
669 path: Default::default(),
670 }]),
671 SCHEMA_DEFINITION.coerce_const_value(it, &json!("123"), Default::default()),
672 );
673 }
674
675 #[test]
676 fn test_optional_list() {
677 let it = input_type("Query", "field", "optionalListArg");
678
679 assert_eq!(
680 Ok(()),
681 SCHEMA_DEFINITION.coerce_const_value(it, &json!(null), Default::default())
682 );
683 assert_eq!(
684 Ok(()),
685 SCHEMA_DEFINITION.coerce_const_value(it, &json!(1), Default::default())
686 );
687 assert_eq!(
688 Ok(()),
689 SCHEMA_DEFINITION.coerce_const_value(it, &json!([1]), Default::default())
690 );
691 assert_eq!(
692 Ok(()),
693 SCHEMA_DEFINITION.coerce_const_value(it, &json!([1, 2, 3]), Default::default())
694 );
695 assert_eq!(
696 Err(vec![
697 Error::NoImplicitConversion {
698 value: &json!("b"),
699 input_type_name: "Int".to_string(),
700 path: Path::new(1),
701 },
702 Error::NoImplicitConversion {
703 value: &json!(true),
704 input_type_name: "Int".to_string(),
705 path: Path::new(2),
706 },
707 ]),
708 SCHEMA_DEFINITION.coerce_const_value(it, &json!([1, "b", true]), Default::default()),
709 );
710 }
711
712 #[test]
713 fn test_optional_list_of_list() {
714 let it = input_type("Query", "field", "optionalListOfListArg");
715
716 assert_eq!(
717 Ok(()),
718 SCHEMA_DEFINITION.coerce_const_value(it, &json!(null), Default::default())
719 );
720 assert_eq!(
721 Ok(()),
722 SCHEMA_DEFINITION.coerce_const_value(it, &json!(1), Default::default())
723 );
724 assert_eq!(
725 Ok(()),
726 SCHEMA_DEFINITION.coerce_const_value(it, &json!([[1], [2, 3]]), Default::default())
727 );
728 assert_eq!(
729 Err(vec![
730 Error::NoImplicitConversion {
731 value: &json!(1),
732 input_type_name: "[Int]".to_string(),
733 path: Path::new(0),
734 },
735 Error::NoImplicitConversion {
736 value: &json!(2),
737 input_type_name: "[Int]".to_string(),
738 path: Path::new(1),
739 },
740 Error::NoImplicitConversion {
741 value: &json!(3),
742 input_type_name: "[Int]".to_string(),
743 path: Path::new(2),
744 },
745 ]),
746 SCHEMA_DEFINITION.coerce_const_value(it, &json!([1, 2, 3]), Default::default()),
747 );
748 }
749
750 #[test]
751 fn test_enum() {
752 let it = input_type("Query", "field", "enumArg");
753
754 assert_eq!(
755 Ok(()),
756 SCHEMA_DEFINITION.coerce_const_value(it, &json!("FIRST"), Default::default())
757 );
758 assert_eq!(
759 Ok(()),
760 SCHEMA_DEFINITION.coerce_const_value(it, &json!("SECOND"), Default::default())
761 );
762 assert_eq!(
763 Err(vec![Error::NoEnumMemberWithName {
764 name: "first",
765 value: &json!("first"),
766 enum_type_name: "Choices",
767 path: Default::default(),
768 }]),
769 SCHEMA_DEFINITION.coerce_const_value(it, &json!("first"), Default::default()),
770 );
771 }
772
773 #[test]
774 fn test_input_object() {
775 let it = input_type("Query", "field", "inputObjectArg");
776
777 assert_eq!(
778 Ok(()),
779 SCHEMA_DEFINITION.coerce_const_value(
780 it,
781 &json!({ "stringArg": "abc" }),
782 Default::default()
783 ),
784 );
785 assert_eq!(
786 Ok(()),
787 SCHEMA_DEFINITION.coerce_const_value(it,
788 &json!({ "stringArg": "abc", "optionalStringArg": "def", "stringArgWithDefault": "ghi" }),
789 Default::default(),
790 ),
791 );
792 assert_eq!(
793 Err(vec![Error::NoImplicitConversion {
794 value: &json!(""),
795 input_type_name: it.display_name(),
796 path: Default::default(),
797 }]),
798 SCHEMA_DEFINITION.coerce_const_value(it, &json!(""), Default::default()),
799 );
800 assert_eq!(
801 Err(vec![Error::NoValueForRequiredFields {
802 value: &json!({}),
803 field_names: vec!["stringArg"],
804 input_object_type_name: "CustomInput",
805 path: Default::default(),
806 }]),
807 SCHEMA_DEFINITION.coerce_const_value(it, &json!({}), Default::default()),
808 );
809 assert_eq!(
810 Err(vec![Error::NoInputFieldWithName {
811 field: &"notDefined".to_owned(),
812 input_object_type_name: "CustomInput",
813 path: Path::new("notDefined"),
814 }]),
815 SCHEMA_DEFINITION.coerce_const_value(
816 it,
817 &json!({ "stringArg": "abc", "notDefined": "def" }),
818 Default::default()
819 ),
820 );
821 assert_eq!(
822 Err(vec![Error::NullValueForRequiredType {
823 value: &json!(null),
824 input_type_name: "String!".to_owned(),
825 path: Path::new("stringArgWithDefault"),
826 }]),
827 SCHEMA_DEFINITION.coerce_const_value(
828 it,
829 &json!({ "stringArg": "abc", "stringArgWithDefault": null }),
830 Default::default()
831 ),
832 );
833 }
834
835 #[test]
836 fn test_custom_scalar() {
837 let it = input_type("Query", "field", "decimalArg");
838
839 assert_eq!(
840 Ok(()),
841 SCHEMA_DEFINITION.coerce_const_value(it, &json!("123.456"), Default::default())
842 );
843 assert_eq!(
844 Err(vec![Error::CustomScalarInvalidValue {
845 value: &json!(123.456),
846 custom_scalar_type_name: "Decimal",
847 message: Cow::Owned("Cannot coerce float to Decimal".to_owned()),
848 path: Default::default(),
849 }]),
850 SCHEMA_DEFINITION.coerce_const_value(it, &json!(123.456), Default::default()),
851 );
852 }
853
854 #[test]
855 fn test_one_of_input_object() {
856 let it = input_type("Query", "field", "oneOfInputObjectArg");
857
858 assert_eq!(
859 Ok(()),
860 SCHEMA_DEFINITION.coerce_const_value(it, &json!({ "first": "s" }), Default::default())
861 );
862 assert_eq!(
863 Ok(()),
864 SCHEMA_DEFINITION.coerce_const_value(it, &json!({ "second": 1 }), Default::default())
865 );
866 assert_eq!(
867 Err(vec![Error::OneOfInputNullValues {
868 value: &json!({ "first": null, "second": 1 }),
869 input_object_type_name: "InputUnion",
870 null_entries: vec![(&"first".to_owned(), &json!(null))],
871 path: Default::default(),
872 }]),
873 SCHEMA_DEFINITION.coerce_const_value(
874 it,
875 &json!({ "first": null, "second": 1 }),
876 Default::default()
877 ),
878 );
879 assert_eq!(
880 Err(vec![Error::OneOfInputNotSingleNonNullValue {
881 value: &json!({ "first": "s", "second": 1 }),
882 input_object_type_name: "InputUnion",
883 non_null_entries: vec![
884 (&"first".to_owned(), &json!("s")),
885 (&"second".to_owned(), &json!(1))
886 ],
887 path: Default::default(),
888 }]),
889 SCHEMA_DEFINITION.coerce_const_value(
890 it,
891 &json!({ "first": "s", "second": 1 }),
892 Default::default()
893 ),
894 )
895 }
896}