1
+ import type { Path } from '../jsutils/Path' ;
2
+ import { addPath , pathToArray } from '../jsutils/Path' ;
3
+ import { didYouMean } from '../jsutils/didYouMean' ;
1
4
import { inspect } from '../jsutils/inspect' ;
5
+ import { invariant } from '../jsutils/invariant' ;
6
+ import { isIterableObject } from '../jsutils/isIterableObject' ;
7
+ import { isObjectLike } from '../jsutils/isObjectLike' ;
8
+ import { printPathArray } from '../jsutils/printPathArray' ;
9
+ import { suggestionList } from '../jsutils/suggestionList' ;
2
10
3
11
import { GraphQLError } from '../error/GraphQLError' ;
4
12
import { locatedError } from '../error/locatedError' ;
@@ -20,18 +28,22 @@ import type {
20
28
GraphQLUnionType ,
21
29
GraphQLEnumType ,
22
30
GraphQLInputObjectType ,
31
+ GraphQLInputType ,
23
32
} from './definition' ;
24
33
import { assertSchema } from './schema' ;
25
34
import { isIntrospectionType } from './introspection' ;
26
35
import { isDirective , GraphQLDeprecatedDirective } from './directives' ;
27
36
import {
37
+ getNamedType ,
28
38
isObjectType ,
29
39
isInterfaceType ,
30
40
isUnionType ,
31
41
isEnumType ,
32
42
isInputObjectType ,
33
43
isNamedType ,
44
+ isListType ,
34
45
isNonNullType ,
46
+ isLeafType ,
35
47
isInputType ,
36
48
isOutputType ,
37
49
isRequiredArgument ,
@@ -190,6 +202,15 @@ function validateDirectives(context: SchemaValidationContext): void {
190
202
] ,
191
203
) ;
192
204
}
205
+
206
+ if ( arg . defaultValue !== undefined ) {
207
+ validateDefaultValue ( arg . defaultValue , arg . type ) . forEach ( ( error ) => {
208
+ context . reportError (
209
+ `Argument @${ directive . name } (${ arg . name } :) has invalid default value: ${ error } ` ,
210
+ arg . astNode ?. defaultValue ,
211
+ ) ;
212
+ } ) ;
213
+ }
193
214
}
194
215
}
195
216
}
@@ -306,6 +327,15 @@ function validateFields(
306
327
] ,
307
328
) ;
308
329
}
330
+
331
+ if ( arg . defaultValue !== undefined ) {
332
+ validateDefaultValue ( arg . defaultValue , arg . type ) . forEach ( ( error ) => {
333
+ context . reportError (
334
+ `Argument ${ type . name } .${ field . name } (${ argName } :) has invalid default value: ${ error } ` ,
335
+ arg . astNode ?. defaultValue ,
336
+ ) ;
337
+ } ) ;
338
+ }
309
339
}
310
340
}
311
341
}
@@ -528,7 +558,7 @@ function validateInputFields(
528
558
) ;
529
559
}
530
560
531
- // Ensure the arguments are valid
561
+ // Ensure the input fields are valid
532
562
for ( const field of fields ) {
533
563
// Ensure they are named correctly.
534
564
validateName ( context , field ) ;
@@ -552,6 +582,15 @@ function validateInputFields(
552
582
] ,
553
583
) ;
554
584
}
585
+
586
+ if ( field . defaultValue !== undefined ) {
587
+ validateDefaultValue ( field . defaultValue , field . type ) . forEach ( ( error ) => {
588
+ context . reportError (
589
+ `Input field ${ inputObj . name } .${ field . name } has invalid default value: ${ error } ` ,
590
+ field . astNode ?. defaultValue ,
591
+ ) ;
592
+ } ) ;
593
+ }
555
594
}
556
595
}
557
596
@@ -584,29 +623,43 @@ function createInputObjectCircularRefsValidator(
584
623
585
624
const fields = Object . values ( inputObj . getFields ( ) ) ;
586
625
for ( const field of fields ) {
587
- if ( isNonNullType ( field . type ) && isInputObjectType ( field . type . ofType ) ) {
588
- const fieldType = field . type . ofType ;
589
- const cycleIndex = fieldPathIndexByTypeName [ fieldType . name ] ;
590
-
591
- fieldPath . push ( field ) ;
592
- if ( cycleIndex === undefined ) {
593
- detectCycleRecursive ( fieldType ) ;
594
- } else {
595
- const cyclePath = fieldPath . slice ( cycleIndex ) ;
596
- const pathStr = cyclePath . map ( ( fieldObj ) => fieldObj . name ) . join ( '.' ) ;
597
- context . reportError (
598
- `Cannot reference Input Object "${ fieldType . name } " within itself through a series of non-null fields: "${ pathStr } ".` ,
599
- cyclePath . map ( ( fieldObj ) => fieldObj . astNode ) ,
600
- ) ;
626
+ const fieldType = getNamedType ( field . type ) ;
627
+ if ( isInputObjectType ( fieldType ) ) {
628
+ const isNonNullField =
629
+ isNonNullType ( field . type ) && field . type . ofType === fieldType ;
630
+ if ( isNonNullField || ! isEmptyValue ( field . defaultValue ) ) {
631
+ const cycleIndex = fieldPathIndexByTypeName [ fieldType . name ] ;
632
+
633
+ fieldPath . push ( field ) ;
634
+ if ( cycleIndex === undefined ) {
635
+ detectCycleRecursive ( fieldType ) ;
636
+ } else {
637
+ const cyclePath = fieldPath . slice ( cycleIndex ) ;
638
+ const pathStr = cyclePath
639
+ . map ( ( fieldObj ) => fieldObj . name )
640
+ . join ( '.' ) ;
641
+ context . reportError (
642
+ `Cannot reference Input Object "${
643
+ fieldType . name
644
+ } " within itself through a series of ${
645
+ isNonNullField ? 'non-null fields' : 'non-empty default values'
646
+ } : "${ pathStr } ".`,
647
+ cyclePath . map ( ( fieldObj ) => fieldObj . astNode ) ,
648
+ ) ;
649
+ }
650
+ fieldPath . pop ( ) ;
601
651
}
602
- fieldPath . pop ( ) ;
603
652
}
604
653
}
605
654
606
655
fieldPathIndexByTypeName [ inputObj . name ] = undefined ;
607
656
}
608
657
}
609
658
659
+ function isEmptyValue ( value : mixed ) {
660
+ return value == null || ( Array . isArray ( value ) && value . length === 0 ) ;
661
+ }
662
+
610
663
function getAllImplementsInterfaceNodes (
611
664
type : GraphQLObjectType | GraphQLInterfaceType ,
612
665
iface : GraphQLInterfaceType ,
@@ -643,3 +696,126 @@ function getDeprecatedDirectiveNode(
643
696
( node ) => node . name . value === GraphQLDeprecatedDirective . name ,
644
697
) ;
645
698
}
699
+
700
+ /**
701
+ * Coerce an internal JavaScript value given a GraphQL Input Type.
702
+ */
703
+ function validateDefaultValue (
704
+ inputValue : mixed ,
705
+ type : GraphQLInputType ,
706
+ path ?: Path ,
707
+ ) : Array < string > {
708
+ if ( isNonNullType ( type ) ) {
709
+ if ( inputValue !== null ) {
710
+ return validateDefaultValue ( inputValue , type . ofType , path ) ;
711
+ }
712
+ return invalidDefaultValue (
713
+ `Expected non-nullable type "${ inspect ( type ) } " not to be null.` ,
714
+ path ,
715
+ ) ;
716
+ }
717
+
718
+ if ( inputValue === null ) {
719
+ return [ ] ;
720
+ }
721
+
722
+ if ( isListType ( type ) ) {
723
+ const itemType = type . ofType ;
724
+ if ( isIterableObject ( inputValue ) ) {
725
+ const errors = [ ] ;
726
+ Array . from ( inputValue ) . forEach ( ( itemValue , index ) => {
727
+ errors . push (
728
+ ...validateDefaultValue (
729
+ itemValue ,
730
+ itemType ,
731
+ addPath ( path , index , undefined ) ,
732
+ ) ,
733
+ ) ;
734
+ } ) ;
735
+ return errors ;
736
+ }
737
+ // Lists accept a non-list value as a list of one.
738
+ return validateDefaultValue ( inputValue , itemType , path ) ;
739
+ }
740
+
741
+ if ( isInputObjectType ( type ) ) {
742
+ if ( ! isObjectLike ( inputValue ) ) {
743
+ return invalidDefaultValue (
744
+ `Expected type "${ type . name } " to be an object.` ,
745
+ path ,
746
+ ) ;
747
+ }
748
+
749
+ const errors = [ ] ;
750
+ const fieldDefs = type . getFields ( ) ;
751
+
752
+ for ( const field of Object . values ( fieldDefs ) ) {
753
+ const fieldPath = addPath ( path , field . name , type . name ) ;
754
+ const fieldValue = inputValue [ field . name ] ;
755
+
756
+ if ( fieldValue === undefined ) {
757
+ if ( field . defaultValue === undefined && isNonNullType ( field . type ) ) {
758
+ return invalidDefaultValue (
759
+ `Field "${ field . name } " of required type "${ inspect (
760
+ field . type ,
761
+ ) } " was not provided.`,
762
+ fieldPath ,
763
+ ) ;
764
+ }
765
+ continue ;
766
+ }
767
+
768
+ errors . push ( ...validateDefaultValue ( fieldValue , field . type , fieldPath ) ) ;
769
+ }
770
+
771
+ // Ensure every provided field is defined.
772
+ for ( const fieldName of Object . keys ( inputValue ) ) {
773
+ if ( ! fieldDefs [ fieldName ] ) {
774
+ const suggestions = suggestionList (
775
+ fieldName ,
776
+ Object . keys ( type . getFields ( ) ) ,
777
+ ) ;
778
+ errors . push (
779
+ ...invalidDefaultValue (
780
+ `Field "${ fieldName } " is not defined by type "${ type . name } ".` +
781
+ didYouMean ( suggestions ) ,
782
+ path ,
783
+ ) ,
784
+ ) ;
785
+ }
786
+ }
787
+ return errors ;
788
+ }
789
+
790
+ // istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618')
791
+ if ( isLeafType ( type ) ) {
792
+ let parseResult ;
793
+ let caughtError ;
794
+
795
+ // Scalars and Enums determine if a input value is valid via serialize(),
796
+ // which can throw to indicate failure. If it throws, maintain a reference
797
+ // to the original error.
798
+ try {
799
+ parseResult = type . serialize ( inputValue ) ;
800
+ } catch ( error ) {
801
+ caughtError = error ;
802
+ }
803
+ if ( parseResult === undefined ) {
804
+ return invalidDefaultValue (
805
+ caughtError ?. message ?? `Expected type "${ type . name } ".` ,
806
+ path ,
807
+ ) ;
808
+ }
809
+ return [ ] ;
810
+ }
811
+
812
+ // istanbul ignore next (Not reachable. All possible input types have been considered)
813
+ invariant ( false , 'Unexpected input type: ' + inspect ( ( type : empty ) ) ) ;
814
+ }
815
+
816
+ function invalidDefaultValue ( message , path ) {
817
+ return [
818
+ ( path ? `(at defaultValue${ printPathArray ( pathToArray ( path ) ) } ) ` : '' ) +
819
+ message ,
820
+ ] ;
821
+ }
0 commit comments