import { DefinitionNode , FieldDefinitionNode , InputObjectTypeDefinitionNode , InputValueDefinitionNode , InterfaceTypeDefinitionNode , ListTypeNode , NamedTypeNode , NonNullTypeNode , ObjectTypeDefinitionNode , ObjectTypeExtensionNode , InterfaceTypeExtensionNode , InputObjectTypeExtensionNode , DirectiveNode , ArgumentNode , ObjectFieldNode , ValueNode , defaultFieldResolver , } from 'graphql' ; import { Metadata } from '../metadata/MetadataStorage' ; import { GraphQLFieldResolver } from 'graphql/type/definition' ; import { SubscriptionType } from '../definition/SubscriptionType' ; import { ObjectType } from '../definition/ObjectType' ; import { InterfaceType } from '../definition/InterfaceType' ; import { InputType } from '../definition/InputType' ; import { TypeExpression } from '../type-expression/TypeExpression' ; import { coerceType } from '../type-expression/coerceType' ; import { List , NonNull } from '../type-expression/structure' ; import { Field } from '../reference/Field' ; import { InputField } from '../reference/InputField' ; import { Implement } from '../reference/Implement' ; import { TypeArg } from '../type-expression/types' ; import { ResolverMap } from '../types' ; export interface SubstitutionMap { [ tempName ] : TypeExpression | TypeArg ; } export class DefinitionParser { private metadata : Metadata [ ] ; constructor ( protected rootNode , protected subsMap ) { } public parseWithResolverMap ( definitionClass ) { const { rootNode } = this ; this . metadata = [ ] ; if ( rootNode . kind === 'ObjectTypeDefinition' ) { this . handleObjectTypeDefinition ( rootNode , definitionClass ) ; } else if ( rootNode . kind === 'ObjectTypeExtension' ) { this . handleObjectTypeExtension ( rootNode , definitionClass ) ; } else if ( rootNode . kind === 'InterfaceTypeDefinition' ) { this . handleInterfaceTypeDefinition ( rootNode , definitionClass ) ; } else if ( rootNode . kind === 'InterfaceTypeExtension' ) { this . handleInterfaceTypeExtension ( rootNode , definitionClass ) ; } else if ( rootNode . kind === 'InputObjectTypeDefinition' ) { this . handleInputObjectTypeDefinition ( rootNode , definitionClass ) ; } else if ( rootNode . kind === 'InputObjectTypeExtension' ) { this . handleInputObjectExtension ( rootNode , definitionClass ) ; } else { throw new Error ( `Node type not supported: ${ rootNode . kind }` ) ; } return this . metadata ; } protected completeTypeExpression ( typeNode ) { const { subsMap } = this ; if ( typeNode . kind === 'ListType' ) { return new List ( this . completeTypeExpression ( typeNode . type ) ) ; } else if ( typeNode . kind === 'NonNullType' ) { return new NonNull ( this . completeTypeExpression ( typeNode . type ) ) ; } const subType = subsMap [ typeNode . name . value ] ; return coerceType ( subType || typeNode . name . value ) ; } protected handleObjectTypeDefinition ( rootNode , definitionClass ) { const { name , interfaces , fields , description } = rootNode ; if ( fields ) { fields . forEach ( fieldNode => this . appendFieldMetadataConfig ( fieldNode , definitionClass ) ) ; } if ( interfaces ) { interfaces . forEach ( interfaceNode => this . appendImplementTypeExpression ( interfaceNode , definitionClass ) ) ; } let metadata ; if ( name . value === 'Subscription' ) { metadata = new SubscriptionType ( { definitionClass , definitionName : name . value , description : description && description . value , directives : rootNode . directives && this . completeDirectives ( rootNode . directives ) , } ) ; } else { metadata = new ObjectType ( { definitionClass , definitionName : name . value , description : description && description . value , directives : rootNode . directives && this . completeDirectives ( rootNode . directives ) , } ) ; } this . metadata . push ( metadata ) ; } protected handleObjectTypeExtension ( rootNode , definitionClass ) { const { name , interfaces , fields } = rootNode ; if ( fields ) { fields . forEach ( fieldNode => this . appendFieldMetadataConfig ( fieldNode , definitionClass , name . value ) ) ; } if ( interfaces ) { interfaces . forEach ( interfaceNode => this . appendImplementTypeExpression ( interfaceNode , definitionClass , name . value ) ) ; } } protected handleInterfaceTypeDefinition ( rootNode , definitionClass ) { const { name , description , fields } = rootNode ; if ( fields ) { fields . forEach ( fieldNode => this . appendFieldMetadataConfig ( fieldNode , definitionClass ) ) ; } this . metadata . push ( new InterfaceType ( { definitionClass , definitionName : name . value , description : description && description . value , directives : rootNode . directives && this . completeDirectives ( rootNode . directives ) , } ) ) ; } protected handleInterfaceTypeExtension ( rootNode , definitionClass ) { const { name , fields } = rootNode ; if ( fields ) { fields . forEach ( fieldNode => this . appendFieldMetadataConfig ( fieldNode , definitionClass , name . value ) ) ; } } protected handleInputObjectTypeDefinition ( node , definitionClass ) { const { name , description , fields } = node ; if ( fields ) { fields . forEach ( fieldNode => this . appendInputFieldMetadataConfig ( fieldNode , definitionClass ) ) ; } this . metadata . push ( new InputType ( { definitionClass , definitionName : name . value , description : description && description . value , directives : node . directives && this . completeDirectives ( node . directives ) , } ) ) ; } protected handleInputObjectExtension ( node , definitionClass ) { const { name , fields } = node ; if ( fields ) { fields . forEach ( fieldNode => this . appendInputFieldMetadataConfig ( fieldNode , definitionClass , name . value ) ) ; } } protected appendFieldMetadataConfig ( node , definitionClass , extendingTypeName ? ) { const { name , type , description , directives , arguments : args } = node ; const argumentRefs = args && args . map ( argumentNode => ( this . createInputField ( argumentNode , definitionClass ) ) ) ; const resolver = bindStaticResolver ( definitionClass , name . value ) || defaultFieldResolver ; this . metadata . push ( new Field ( { source : extendingTypeName || definitionClass , target : this . completeTypeExpression ( type ) , fieldName : name . value , args : argumentRefs || [ ] , description : description && description . value , directives : directives && this . completeDirectives ( directives ) , extendingTypeName , resolver , } ) ) ; } protected createInputField ( node , definitionClass , extendingTypeName ? ) { const { name , type , description , defaultValue , directives } = node ; return new InputField ( { source : extendingTypeName || definitionClass , target : this . completeTypeExpression ( type ) , fieldName : name . value , directives : directives && this . completeDirectives ( directives ) , defaultValue : defaultValue && this . completeValueNode ( defaultValue ) , description : description && description . value , extendingTypeName , } ) ; } protected appendInputFieldMetadataConfig ( node , definitionClass , extendingTypeName ? ) { const field = this . createInputField ( node , definitionClass , extendingTypeName ) ; this . metadata . push ( field ) ; } protected appendImplementTypeExpression ( node , definitionClass , extendingTypeName ? ) { this . metadata . push ( new Implement ( { source : extendingTypeName || definitionClass , target : this . completeTypeExpression ( node ) , } ) ) ; } protected completeDirectives ( directiveNodes ) { return directiveNodes . reduce ( ( results , node ) => { if ( node . arguments instanceof Array ) { results [ node . name . value ] = this . completeArgumentsOrObjectFields ( node . arguments ) ; } else { results [ node . name . value ] = { } ; } return results ; } , { } as { [ key ] : any ; } ) ; } protected completeArgumentsOrObjectFields ( nodes ) { return nodes . reduce ( ( results , node ) => { results [ node . name . value ] = this . completeValueNode ( node . value ) ; return results ; } , { } as any ) ; } protected completeValueNode ( node ) { if ( node . kind === 'ObjectValue' ) { return this . completeArgumentsOrObjectFields ( node . fields ) ; } else if ( node . kind === 'NullValue' ) { return null ; } else if ( node . kind === 'ListValue' ) { return node . values . map ( this . completeValueNode . bind ( this ) ) ; } else if ( node . kind === 'Variable' ) { throw new Error ( `Cannot use variable in schema directives: ${ node . name }` ) ; } else if ( node . kind === 'IntValue' || node . kind === 'FloatValue' ) { return Number ( node . value ) ; } return node . value ; } } export type DirectiveMap = { [ key ] : any ; } ; export function bindStaticResolver ( definitionClass , fieldName ) { const maybeStaticResolver = ( definitionClass as any ) [ fieldName ] ; return maybeStaticResolver instanceof Function ? maybeStaticResolver . bind ( definitionClass ) : null ; }