1+ import type { Path } from '../jsutils/Path' ;
2+ import { addPath , pathToArray } from '../jsutils/Path' ;
3+ import { didYouMean } from '../jsutils/didYouMean' ;
14import { 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' ;
210
311import { GraphQLError } from '../error/GraphQLError' ;
412import { locatedError } from '../error/locatedError' ;
@@ -20,18 +28,22 @@ import type {
2028 GraphQLUnionType ,
2129 GraphQLEnumType ,
2230 GraphQLInputObjectType ,
31+ GraphQLInputType ,
2332} from './definition' ;
2433import { assertSchema } from './schema' ;
2534import { isIntrospectionType } from './introspection' ;
2635import { isDirective , GraphQLDeprecatedDirective } from './directives' ;
2736import {
37+ getNamedType ,
2838 isObjectType ,
2939 isInterfaceType ,
3040 isUnionType ,
3141 isEnumType ,
3242 isInputObjectType ,
3343 isNamedType ,
44+ isListType ,
3445 isNonNullType ,
46+ isLeafType ,
3547 isInputType ,
3648 isOutputType ,
3749 isRequiredArgument ,
@@ -190,6 +202,15 @@ function validateDirectives(context: SchemaValidationContext): void {
190202 ] ,
191203 ) ;
192204 }
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+ }
193214 }
194215 }
195216}
@@ -306,6 +327,15 @@ function validateFields(
306327 ] ,
307328 ) ;
308329 }
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+ }
309339 }
310340 }
311341}
@@ -528,7 +558,7 @@ function validateInputFields(
528558 ) ;
529559 }
530560
531- // Ensure the arguments are valid
561+ // Ensure the input fields are valid
532562 for ( const field of fields ) {
533563 // Ensure they are named correctly.
534564 validateName ( context , field ) ;
@@ -552,6 +582,15 @@ function validateInputFields(
552582 ] ,
553583 ) ;
554584 }
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+ }
555594 }
556595}
557596
@@ -584,29 +623,43 @@ function createInputObjectCircularRefsValidator(
584623
585624 const fields = Object . values ( inputObj . getFields ( ) ) ;
586625 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 ( ) ;
601651 }
602- fieldPath . pop ( ) ;
603652 }
604653 }
605654
606655 fieldPathIndexByTypeName [ inputObj . name ] = undefined ;
607656 }
608657}
609658
659+ function isEmptyValue ( value : mixed ) {
660+ return value == null || ( Array . isArray ( value ) && value . length === 0 ) ;
661+ }
662+
610663function getAllImplementsInterfaceNodes (
611664 type : GraphQLObjectType | GraphQLInterfaceType ,
612665 iface : GraphQLInterfaceType ,
@@ -643,3 +696,126 @@ function getDeprecatedDirectiveNode(
643696 ( node ) => node . name . value === GraphQLDeprecatedDirective . name ,
644697 ) ;
645698}
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