import { ArgumentDefinition , InputObjectType , InputType , isBooleanType , isCustomScalarType , isEnumType , isFloatType , isIDType , isInputObjectType , isIntType , isListType , isNonNullType , isScalarType , isStringType , isVariable , Variable , VariableDefinition , VariableDefinitions , Variables , } from './definitions' ; import { ArgumentNode , GraphQLError , Kind , print , ValueNode , ObjectFieldNode , ConstValueNode , ConstObjectFieldNode , } from 'graphql' ; import { didYouMean , suggestionList } from './suggestions' ; import { inspect } from 'util' ; import { sameType } from './types' ; import { assert , assertUnreachable } from './utils' ; const MAX_INT = 2147483647 ; const MIN_INT = - 2147483648 ; export function valueToString ( v , expectedType ? ) { if ( v === undefined || v === null ) { if ( expectedType && isNonNullType ( expectedType ) ) { throw buildError ( `Invalid undefined/null value for non-null type ${ expectedType }` ) ; } return "null" ; } if ( expectedType && isNonNullType ( expectedType ) ) { expectedType = expectedType . ofType ; } if ( expectedType && isCustomScalarType ( expectedType ) ) { expectedType = undefined ; } if ( isVariable ( v ) ) { return v . toString ( ) ; } if ( Array . isArray ( v ) ) { let elementsType = undefined ; if ( expectedType ) { if ( ! isListType ( expectedType ) ) { throw buildError ( `Invalid list value for non-list type ${ expectedType }` ) ; } elementsType = expectedType . ofType ; } return '[' + v . map ( e => valueToString ( e , elementsType ) ) . join ( ', ' ) + ']' ; } if ( typeof v === 'object' ) { if ( expectedType && ! isInputObjectType ( expectedType ) ) { throw buildError ( `Invalid object value for non-input-object type ${ expectedType } (isCustomScalar? ${ isCustomScalarType ( expectedType ) })` ) ; } return '{' + Object . keys ( v ) . map ( k => { const valueType = expectedType ? ( expectedType as InputObjectType ) . field ( k ) ?. type : undefined ; return `${ k }: ${ valueToString ( v [ k ] , valueType ) }` ; } ) . join ( ', ' ) + '}' ; } if ( typeof v === 'string' ) { if ( expectedType ) { if ( isEnumType ( expectedType ) ) { return v ; } if ( expectedType === expectedType . schema ( ) . idType ( ) && integerStringRegExp . test ( v ) ) { return v ; } } return JSON . stringify ( v ) ; } return String ( v ) ; } export function valueEquals ( a , b ) { if ( a === b ) { return true ; } if ( Array . isArray ( a ) ) { return Array . isArray ( b ) && arrayValueEquals ( a , b ) ; } if ( typeof a === 'object' ) { return typeof b === 'object' && objectEquals ( a , b ) ; } return a === b ; } function arrayValueEquals ( a , b ) { if ( a . length !== b . length ) { return false ; } for ( let i = 0 ; i < a . length ; ++ i ) { if ( ! valueEquals ( a [ i ] , b [ i ] ) ) { return false ; } } return true ; } function objectEquals ( a , b ) { const keys1 = Object . keys ( a ) ; const keys2 = Object . keys ( b ) ; if ( keys1 . length != keys2 . length ) { return false ; } for ( const key of keys1 ) { const v1 = a [ key ] ; const v2 = b [ key ] ; if ( v2 === undefined ) { return v1 === undefined && b . hasOwnProperty ( key ) ; } if ( ! valueEquals ( v1 , v2 ) ) { return false ; } } return true ; } export function argumentsEquals ( args1 , args2 ) { if ( args1 === args2 ) { return true ; } return objectEquals ( args1 , args2 ) ; } function buildError ( message ) { return new Error ( message ) ; } function applyDefaultValues ( value , type ) { if ( isVariable ( value ) ) { return value ; } if ( value === null ) { if ( isNonNullType ( type ) ) { throw new GraphQLError ( `Invalid null value for non-null type ${ type } while computing default values` ) ; } return null ; } if ( isNonNullType ( type ) ) { return applyDefaultValues ( value , type . ofType ) ; } if ( isListType ( type ) ) { if ( Array . isArray ( value ) ) { return value . map ( v => applyDefaultValues ( v , type . ofType ) ) ; } else { return applyDefaultValues ( value , type . ofType ) ; } } if ( isInputObjectType ( type ) ) { if ( typeof value !== 'object' ) { throw new GraphQLError ( `Expected value for type ${ type } to be an object, but is ${ typeof value }.` ) ; } const updated = Object . create ( null ) ; for ( const field of type . fields ( ) ) { if ( ! field . type ) { throw buildError ( `Cannot compute default value for field ${ field . name } of ${ type } as the field type is undefined` ) ; } const fieldValue = value [ field . name ] ; if ( fieldValue === undefined ) { if ( field . defaultValue !== undefined ) { updated [ field . name ] = applyDefaultValues ( field . defaultValue , field . type ) ; } else if ( isNonNullType ( field . type ) ) { throw new GraphQLError ( `Field "${ field . name }" of required type ${ type } was not provided.` ) ; } } else { updated [ field . name ] = applyDefaultValues ( fieldValue , field . type ) ; } } for ( const fieldName of Object . keys ( value ) ) { if ( ! type . field ( fieldName ) ) { const suggestions = suggestionList ( fieldName , type . fields ( ) . map ( f => f . name ) ) ; throw new GraphQLError ( `Field "${ fieldName }" is not defined by type "${ type }".` + didYouMean ( suggestions ) ) ; } } return updated ; } return value ; } export function withDefaultValues ( value , argument ) { if ( ! argument . type ) { throw buildError ( `Cannot compute default value for argument ${ argument } as the type is undefined` ) ; } if ( value === undefined ) { if ( argument . defaultValue ) { return applyDefaultValues ( argument . defaultValue , argument . type ) ; } } return applyDefaultValues ( value , argument . type ) ; } const integerStringRegExp = /^-?(?:0|[1-9][0-9]*)$/ ; function objectFieldNodeToConst ( field ) { return { ... field , value : valueNodeToConstValueNode ( field . value ) } ; } /**
 * Transforms a ValueNode to a ConstValueNode. This should only be invoked when we know that the value node can be const
 * as it will result in an exception if it contains a VariableNode
 */ export function valueNodeToConstValueNode ( value ) { if ( value . kind === Kind . NULL || value . kind === Kind . INT || value . kind === Kind . FLOAT || value . kind === Kind . STRING || value . kind === Kind . BOOLEAN || value . kind === Kind . ENUM ) { return value ; } if ( value . kind === Kind . LIST ) { const constValues = value . values . map ( v => valueNodeToConstValueNode ( v ) ) ; return { ... value , values : constValues } ; } if ( value . kind === Kind . OBJECT ) { const constFields = value . fields . map ( f => objectFieldNodeToConst ( f ) ) ; return { ... value , fields : constFields } ; } if ( value . kind === Kind . VARIABLE ) { throw new Error ( 'Unexpected VariableNode in const AST' ) ; } assertUnreachable ( value ) ; } export function valueToAST ( value , type ) { if ( value === undefined ) { return undefined ; } if ( isNonNullType ( type ) ) { const astValue = valueToAST ( value , type . ofType ) ; if ( astValue ?. kind === Kind . NULL ) { throw buildError ( `Invalid null value ${ valueToString ( value ) } for non-null type ${ type }` ) ; } return astValue ; } if ( value === null ) { return { kind : Kind . NULL } ; } if ( isVariable ( value ) ) { return { kind : Kind . VARIABLE , name : { kind : Kind . NAME , value : value . name } } ; } if ( isCustomScalarType ( type ) ) { return valueToASTUntyped ( value ) ; } if ( isListType ( type ) ) { const itemType = type . ofType ; const items = Array . from ( value ) ; if ( items != null ) { const valuesNodes = [ ] ; for ( const item of items ) { const itemNode = valueToAST ( item , itemType ) ; if ( itemNode != null ) { valuesNodes . push ( itemNode ) ; } } return { kind : Kind . LIST , values : valuesNodes } ; } return valueToAST ( value , itemType ) ; } if ( isInputObjectType ( type ) ) { if ( typeof value !== 'object' ) { throw buildError ( `Invalid non-objet value for input type ${ type }, cannot be converted to AST: ${ inspect ( value , true , 10 , true ) }` ) ; } const fieldNodes = [ ] ; for ( const field of type . fields ( ) ) { if ( ! field . type ) { throw buildError ( `Cannot convert value ${ valueToString ( value ) } as field ${ field } has no type set` ) ; } const fieldValue = valueToAST ( value [ field . name ] , field . type ) ; if ( fieldValue ) { fieldNodes . push ( { kind : Kind . OBJECT_FIELD , name : { kind : Kind . NAME , value : field . name } , value : fieldValue , } ) ; } } return { kind : Kind . OBJECT , fields : fieldNodes } ; } if ( typeof value === 'boolean' ) { return { kind : Kind . BOOLEAN , value : value } ; } if ( typeof value === 'number' && isFinite ( value ) ) { const stringNum = String ( value ) ; return integerStringRegExp . test ( stringNum ) ? { kind : Kind . INT , value : stringNum } : { kind : Kind . FLOAT , value : stringNum } ; } if ( typeof value === 'string' ) { if ( isEnumType ( type ) ) { return { kind : Kind . ENUM , value : value } ; } if ( type === type . schema ( ) . idType ( ) && integerStringRegExp . test ( value ) ) { return { kind : Kind . INT , value : value } ; } return { kind : Kind . STRING , value : value , } ; } throw buildError ( `Invalid value for type ${ type }, cannot be converted to AST: ${ inspect ( value ) }` ) ; } function valueToASTUntyped ( value ) { if ( value === undefined ) { return undefined ; } if ( value === null ) { return { kind : Kind . NULL } ; } if ( isVariable ( value ) ) { return { kind : Kind . VARIABLE , name : { kind : Kind . NAME , value : value . name } } ; } if ( Array . isArray ( value ) ) { const valuesNodes = [ ] ; for ( const item of value ) { const itemNode = valueToASTUntyped ( item ) ; if ( itemNode !== undefined ) { valuesNodes . push ( itemNode ) ; } } return { kind : Kind . LIST , values : valuesNodes } ; } if ( typeof value === 'object' ) { const fieldNodes = [ ] ; for ( const key of Object . keys ( value ) ) { const fieldValue = valueToASTUntyped ( value [ key ] ) ; if ( fieldValue ) { fieldNodes . push ( { kind : Kind . OBJECT_FIELD , name : { kind : Kind . NAME , value : key } , value : fieldValue , } ) ; } } return { kind : Kind . OBJECT , fields : fieldNodes } ; } if ( typeof value === 'boolean' ) { return { kind : Kind . BOOLEAN , value : value } ; } if ( typeof value === 'number' && isFinite ( value ) ) { const stringNum = String ( value ) ; return integerStringRegExp . test ( stringNum ) ? { kind : Kind . INT , value : stringNum } : { kind : Kind . FLOAT , value : stringNum } ; } if ( typeof value === 'string' ) { return { kind : Kind . STRING , value : value } ; } throw buildError ( `Invalid value, cannot be converted to AST: ${ inspect ( value , true , 10 , true ) }` ) ; } function isValidVariable ( variable , locationType , locationDefault ) { const variableType = variable . type ; if ( isNonNullType ( locationType ) && ! isNonNullType ( variableType ) ) { const hasVariableDefault = variable . defaultValue !== undefined && variable . defaultValue !== null ; const hasLocationDefault = locationDefault !== undefined ; if ( ! hasVariableDefault && ! hasLocationDefault ) { return false ; } return areTypesCompatible ( variableType , locationType . ofType ) ; } return areTypesCompatible ( variableType , locationType ) ; } function areTypesCompatible ( variableType , locationType ) { if ( isNonNullType ( locationType ) ) { if ( ! isNonNullType ( variableType ) ) { return false ; } return areTypesCompatible ( variableType . ofType , locationType . ofType ) ; } if ( isNonNullType ( variableType ) ) { return areTypesCompatible ( variableType . ofType , locationType ) ; } if ( isListType ( locationType ) ) { if ( ! isListType ( variableType ) ) { return false ; } return areTypesCompatible ( variableType . ofType , locationType . ofType ) ; } return ! isListType ( variableType ) && sameType ( variableType , locationType ) ; } export function isValidValue ( value , argument , variableDefinitions ) { return isValidValueApplication ( value , argument . type ! , argument . defaultValue , variableDefinitions ) ; } function isValidValueApplication ( value , locationType , locationDefault , variableDefinitions ) { if ( isVariable ( value ) ) { const definition = variableDefinitions . definition ( value ) ; return ! ! definition && isValidVariable ( definition , locationType , locationDefault ) ; } if ( isNonNullType ( locationType ) ) { return value !== null && isValidValueApplication ( value , locationType . ofType , undefined , variableDefinitions ) ; } if ( value === null || value === undefined ) { return true ; } if ( isCustomScalarType ( locationType ) ) { return true ; } if ( isListType ( locationType ) ) { const itemType = locationType . ofType ; if ( Array . isArray ( value ) ) { return value . every ( item => isValidValueApplication ( item , itemType , undefined , variableDefinitions ) ) ; } return isValidValueApplication ( value , itemType , locationDefault , variableDefinitions ) ; } if ( isInputObjectType ( locationType ) ) { if ( typeof value !== 'object' ) { return false ; } const isValid = locationType . fields ( ) . every ( field => isValidValueApplication ( value [ field . name ] , field . type ! , undefined , variableDefinitions ) ) ; return isValid ; } const schema = locationType . schema ( ) ; if ( typeof value === 'boolean' ) { return locationType === schema . booleanType ( ) ; } if ( typeof value === 'number' && isFinite ( value ) ) { const stringNum = String ( value ) ; if ( locationType === schema . intType ( ) || locationType === schema . idType ( ) ) { return integerStringRegExp . test ( stringNum ) ; } return locationType === schema . floatType ( ) ; } if ( typeof value === 'string' ) { if ( isEnumType ( locationType ) ) { return locationType . value ( value ) !== undefined ; } return isScalarType ( locationType ) && locationType !== schema . booleanType ( ) && locationType !== schema . intType ( ) && locationType !== schema . floatType ( ) ; } return false ; } export function valueFromAST ( node , expectedType ) { if ( node . kind === Kind . NULL ) { if ( isNonNullType ( expectedType ) ) { throw new GraphQLError ( `Invalid null value for non-null type "${ expectedType }"` ) ; } return null ; } if ( node . kind === Kind . VARIABLE ) { return new Variable ( node . name . value ) ; } if ( isNonNullType ( expectedType ) ) { expectedType = expectedType . ofType ; } if ( isListType ( expectedType ) ) { const baseType = expectedType . ofType ; if ( node . kind === Kind . LIST ) { return node . values . map ( v => valueFromAST ( v , baseType ) ) ; } return [ valueFromAST ( node , baseType ) ] ; } if ( isIntType ( expectedType ) ) { if ( node . kind !== Kind . INT ) { throw new GraphQLError ( `Int cannot represent non-integer value ${ print ( node ) }.` ) ; } const i = parseInt ( node . value , 10 ) ; if ( i > MAX_INT || i < MIN_INT ) { throw new GraphQLError ( `Int cannot represent non 32-bit signed integer value ${ i }.` ) ; } return i ; } if ( isFloatType ( expectedType ) ) { let parsed ; if ( node . kind === Kind . INT ) { parsed = parseInt ( node . value , 10 ) ; } else if ( node . kind === Kind . FLOAT ) { parsed = parseFloat ( node . value ) ; } else { throw new GraphQLError ( `Float can only represent integer or float value, but got a ${ node . kind }.` ) ; } if ( ! isFinite ( parsed ) ) { throw new GraphQLError ( `Float cannot represent non numeric value ${ parsed }.` ) ; } return parsed ; } if ( isBooleanType ( expectedType ) ) { if ( node . kind !== Kind . BOOLEAN ) { throw new GraphQLError ( `Boolean cannot represent a non boolean value ${ print ( node ) }.` ) ; } return node . value ; } if ( isStringType ( expectedType ) ) { if ( node . kind !== Kind . STRING ) { throw new GraphQLError ( `String cannot represent non string value ${ print ( node ) }.` ) ; } return node . value ; } if ( isIDType ( expectedType ) ) { if ( node . kind !== Kind . STRING && node . kind !== Kind . INT ) { throw new GraphQLError ( `ID cannot represent value ${ print ( node ) }.` ) ; } return node . value ; } if ( isScalarType ( expectedType ) ) { return valueFromASTUntyped ( node ) ; } if ( isInputObjectType ( expectedType ) ) { if ( node . kind !== Kind . OBJECT ) { throw new GraphQLError ( `Input Object Type ${ expectedType } cannot represent non-object value ${ print ( node ) }.` ) ; } const obj = Object . create ( null ) ; for ( const f of node . fields ) { const name = f . name . value ; const field = expectedType . field ( name ) ; if ( ! field ) { throw new GraphQLError ( `Unknown field "${ name }" found in value for Input Object Type "${ expectedType }".` ) ; } obj [ name ] = valueFromAST ( f . value , field . type ! ) ; } return obj ; } if ( isEnumType ( expectedType ) ) { if ( node . kind !== Kind . STRING && node . kind !== Kind . ENUM ) { throw new GraphQLError ( `Enum Type ${ expectedType } cannot represent value ${ print ( node ) }.` ) ; } if ( ! expectedType . value ( node . value ) ) { throw new GraphQLError ( `Enum Type ${ expectedType } has no value ${ node . value }.` ) ; } return node . value ; } assert ( false , ( ) => `Unexpected input type ${ expectedType } of kind ${ expectedType . kind }.` ) ; } function valueFromASTUntyped ( node ) { switch ( node . kind ) { case Kind . NULL : return null ; case Kind . INT : return parseInt ( node . value , 10 ) ; case Kind . FLOAT : return parseFloat ( node . value ) ; case Kind . STRING : case Kind . ENUM : case Kind . BOOLEAN : return node . value ; case Kind . LIST : return node . values . map ( valueFromASTUntyped ) ; case Kind . OBJECT : const obj = Object . create ( null ) ; node . fields . forEach ( f => obj [ f . name . value ] = valueFromASTUntyped ( f . value ) ) ; return obj ; case Kind . VARIABLE : return new Variable ( node . name . value ) ; } } export function argumentsFromAST ( context , args , argsDefiner ) { const values = Object . create ( null ) ; if ( args ) { for ( const argNode of args ) { const name = argNode . name . value ; const expectedType = argsDefiner . argument ( name ) ?. type ; if ( ! expectedType ) { throw new GraphQLError ( `Unknown argument "${ name }" found in value: ${ context } has no argument named "${ name }"` ) ; } try { values [ name ] = valueFromAST ( argNode . value , expectedType ) ; } catch ( e ) { if ( e instanceof GraphQLError ) { throw new GraphQLError ( `Invalid value for argument ${ name }: ${ e . message }` ) ; } throw e ; } } } return values ; } export function variablesInValue ( value ) { const variables = [ ] ; collectVariables ( value , variables ) ; return variables ; } function collectVariables ( value , variables ) { if ( isVariable ( value ) ) { if ( ! variables . some ( v => v . name === value . name ) ) { variables . push ( value ) ; } return ; } if ( ! value ) { return ; } if ( Array . isArray ( value ) ) { value . forEach ( v => collectVariables ( v , variables ) ) ; } if ( typeof value === 'object' ) { Object . keys ( value ) . forEach ( k => collectVariables ( value [ k ] , variables ) ) ; } }