bluejay_validator/value/
input_coercion.rs

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}