Repository: jensneuse/graphql-go-tools
Pull request #112 (2019-11-18 23:02:46)

feature/add base schema merge func
This PR makes transforming a schema which misses GraphQL internal types etc. into a complete and integer schema easy.


Patch:
diff --git a/pkg/ast/ast.go b/pkg/ast/ast.go
index 33d7fc6b..554cda04 100644
--- a/pkg/ast/ast.go
+++ b/pkg/ast/ast.go
@@ -1721,6 +1721,10 @@ func (d *Document) ObjectTypeDefinitionNameBytes(ref int) ByteSlice {
 	return d.Input.ByteSlice(d.ObjectTypeDefinitions[ref].Name)
 }
 
+func (d *Document) ObjectTypeDefinitionNameRef(ref int) ByteSliceReference {
+	return d.ObjectTypeDefinitions[ref].Name
+}
+
 func (d *Document) ObjectTypeDefinitionNameString(ref int) string {
 	return unsafebytes.BytesToString(d.Input.ByteSlice(d.ObjectTypeDefinitions[ref].Name))
 }
diff --git a/pkg/asttransform/baseschema.go b/pkg/asttransform/baseschema.go
new file mode 100644
index 00000000..f6ab27d2
--- /dev/null
+++ b/pkg/asttransform/baseschema.go
@@ -0,0 +1,253 @@
+package asttransform
+
+import (
+	"bytes"
+	"github.com/jensneuse/graphql-go-tools/pkg/ast"
+	"github.com/jensneuse/graphql-go-tools/pkg/astparser"
+	"github.com/jensneuse/graphql-go-tools/pkg/operationreport"
+)
+
+func MergeDefinitionWithBaseSchema(definition *ast.Document) error {
+	definition.Input.AppendInputBytes(baseSchema)
+	parser := astparser.NewParser()
+	report := operationreport.Report{}
+	parser.Parse(definition, &report)
+	if report.HasErrors() {
+		return report
+	}
+	err := handleSchema(definition)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func handleSchema(definition *ast.Document) error {
+	for i := range definition.RootNodes {
+		if definition.RootNodes[i].Kind == ast.NodeKindSchemaDefinition {
+			return nil
+		}
+	}
+
+	schemaDefinition := ast.SchemaDefinition{}
+
+	for i := range definition.RootNodes {
+		if definition.RootNodes[i].Kind == ast.NodeKindObjectTypeDefinition {
+			typeName := definition.ObjectTypeDefinitionNameBytes(definition.RootNodes[i].Ref)
+			nameRef := definition.ObjectTypeDefinitionNameRef(definition.RootNodes[i].Ref)
+			var operationType ast.OperationType
+			switch {
+			case bytes.Equal(typeName, []byte("Query")):
+				operationType = ast.OperationTypeQuery
+			case bytes.Equal(typeName, []byte("Mutation")):
+				operationType = ast.OperationTypeMutation
+			case bytes.Equal(typeName, []byte("Subscription")):
+				operationType = ast.OperationTypeSubscription
+			default:
+				continue
+			}
+
+			definition.RootOperationTypeDefinitions = append(definition.RootOperationTypeDefinitions, ast.RootOperationTypeDefinition{
+				OperationType: operationType,
+				NamedType: ast.Type{
+					TypeKind: ast.TypeKindNamed,
+					Name:     nameRef,
+				},
+			})
+			ref := len(definition.RootOperationTypeDefinitions) - 1
+			schemaDefinition.RootOperationTypeDefinitions.Refs = append(schemaDefinition.RootOperationTypeDefinitions.Refs, ref)
+		}
+	}
+
+	definition.SchemaDefinitions = append(definition.SchemaDefinitions, schemaDefinition)
+	ref := len(definition.SchemaDefinitions) - 1
+	schemaNode := ast.Node{
+		Kind: ast.NodeKindSchemaDefinition,
+		Ref:  ref,
+	}
+	definition.RootNodes = append([]ast.Node{schemaNode}, definition.RootNodes...)
+	return nil
+}
+
+var baseSchema = []byte(`"The 'Int' scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1."
+scalar Int
+"The 'Float' scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point)."
+scalar Float
+"The 'String' scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text."
+scalar String
+"The 'Boolean' scalar type represents 'true' or 'false' ."
+scalar Boolean
+"The 'ID' scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as '4') or integer (such as 4) input value will be accepted as an ID."
+scalar ID
+"Directs the executor to include this field or fragment only when the argument is true."
+directive @include(
+    " Included when true."
+    if: Boolean!
+) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+"Directs the executor to skip this field or fragment when the argument is true."
+directive @skip(
+    "Skipped when true."
+    if: Boolean!
+) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+"Marks an element of a GraphQL schema as no longer supported."
+directive @deprecated(
+    """
+    Explains why this element was deprecated, usually also including a suggestion
+    for how to access supported similar data. Formatted in
+    [Markdown](https://daringfireball.net/projects/markdown/).
+    """
+    reason: String = "No longer supported"
+) on FIELD_DEFINITION | ENUM_VALUE
+
+"""
+A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.
+In some cases, you need to provide options to alter GraphQL's execution behavior
+in ways field arguments will not suffice, such as conditionally including or
+skipping a field. Directives provide this by describing additional information
+to the executor.
+"""
+type __Directive {
+    name: String!
+    description: String
+    locations: [__DirectiveLocation!]!
+    args: [__InputValue!]!
+}
+
+"""
+A Directive can be adjacent to many parts of the GraphQL language, a
+__DirectiveLocation describes one such possible adjacencies.
+"""
+enum __DirectiveLocation {
+    "Location adjacent to a query operation."
+    QUERY
+    "Location adjacent to a mutation operation."
+    MUTATION
+    "Location adjacent to a subscription operation."
+    SUBSCRIPTION
+    "Location adjacent to a field."
+    FIELD
+    "Location adjacent to a fragment definition."
+    FRAGMENT_DEFINITION
+    "Location adjacent to a fragment spread."
+    FRAGMENT_SPREAD
+    "Location adjacent to an inline fragment."
+    INLINE_FRAGMENT
+    "Location adjacent to a schema definition."
+    SCHEMA
+    "Location adjacent to a scalar definition."
+    SCALAR
+    "Location adjacent to an object type definition."
+    OBJECT
+    "Location adjacent to a field definition."
+    FIELD_DEFINITION
+    "Location adjacent to an argument definition."
+    ARGUMENT_DEFINITION
+    "Location adjacent to an interface definition."
+    INTERFACE
+    "Location adjacent to a union definition."
+    UNION
+    "Location adjacent to an enum definition."
+    ENUM
+    "Location adjacent to an enum value definition."
+    ENUM_VALUE
+    "Location adjacent to an input object type definition."
+    INPUT_OBJECT
+    "Location adjacent to an input object field definition."
+    INPUT_FIELD_DEFINITION
+}
+"""
+One possible value for a given Enum. Enum values are unique values, not a
+placeholder for a string or numeric value. However an Enum value is returned in
+a JSON response as a string.
+"""
+type __EnumValue {
+    name: String!
+    description: String
+    isDeprecated: Boolean!
+    deprecationReason: String
+}
+
+"""
+Object and Interface types are described by a list of Fields, each of which has
+a name, potentially a list of arguments, and a return type.
+"""
+type __Field {
+    name: String!
+    description: String
+    args: [__InputValue!]!
+    type: __Type!
+    isDeprecated: Boolean!
+    deprecationReason: String
+}
+
+"""Arguments provided to Fields or Directives and the input fields of an
+InputObject are represented as Input Values which describe their type and
+optionally a default value.
+"""
+type __InputValue {
+    name: String!
+    description: String
+    type: __Type!
+    "A GraphQL-formatted string representing the default value for this input value."
+    defaultValue: String
+}
+
+"""
+A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all
+available types and directives on the server, as well as the entry points for
+query, mutation, and subscription operations.
+"""
+type __Schema {
+    "A list of all types supported by this server."
+    types: [__Type!]!
+    "The type that query operations will be rooted at."
+    queryType: __Type!
+    "If this server supports mutation, the type that mutation operations will be rooted at."
+    mutationType: __Type
+    "If this server support subscription, the type that subscription operations will be rooted at."
+    subscriptionType: __Type
+    "A list of all directives supported by this server."
+    directives: [__Directive!]!
+}
+
+"""
+The fundamental unit of any GraphQL Schema is the type. There are many kinds of
+types in GraphQL as represented by the '__TypeKind' enum.
+
+Depending on the kind of a type, certain fields describe information about that
+type. Scalar types provide no information beyond a name and description, while
+Enum types provide their values. Object and Interface types provide the fields
+they describe. Abstract types, Union and Interface, provide the Object types
+possible at runtime. List and NonNull types compose other types.
+"""
+type __Type {
+    kind: __TypeKind!
+    name: String
+    description: String
+    fields(includeDeprecated: Boolean = false): [__Field!]
+    interfaces: [__Type!]
+    possibleTypes: [__Type!]
+    enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
+    inputFields: [__InputValue!]
+    ofType: __Type
+}
+
+"An enum describing what kind of type a given '__Type' is."
+enum __TypeKind {
+    "Indicates this type is a scalar."
+    SCALAR
+    "Indicates this type is an object. 'fields' and 'interfaces' are valid fields."
+    OBJECT
+    "Indicates this type is an interface. 'fields' ' and ' 'possibleTypes' are valid fields."
+    INTERFACE
+    "Indicates this type is a union. 'possibleTypes' is a valid field."
+    UNION
+    "Indicates this type is an enum. 'enumValues' is a valid field."
+    ENUM
+    "Indicates this type is an input object. 'inputFields' is a valid field."
+    INPUT_OBJECT
+    "Indicates this type is a list. 'ofType' is a valid field."
+    LIST
+    "Indicates this type is a non-null. 'ofType' is a valid field."
+    NON_NULL
+}`)
diff --git a/pkg/asttransform/fixtures/schema_missing.golden b/pkg/asttransform/fixtures/schema_missing.golden
new file mode 100644
index 00000000..9f11ab99
--- /dev/null
+++ b/pkg/asttransform/fixtures/schema_missing.golden
@@ -0,0 +1,205 @@
+schema {
+    query: Query
+}
+
+type Query {
+    hello(name: String): Hello!
+}
+
+type Hello {
+    hello: String!
+    object: String!
+    adminInformation: String!
+}
+
+"The 'Int' scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1."
+scalar Int
+
+"The 'Float' scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point)."
+scalar Float
+
+"The 'String' scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text."
+scalar String
+
+"The 'Boolean' scalar type represents 'true' or 'false' ."
+scalar Boolean
+
+"The 'ID' scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as '4') or integer (such as 4) input value will be accepted as an ID."
+scalar ID
+
+"Directs the executor to include this field or fragment only when the argument is true."
+directive @include(
+    "Included when true."
+    if: Boolean!
+) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+
+"Directs the executor to skip this field or fragment when the argument is true."
+directive @skip(
+    "Skipped when true."
+    if: Boolean!
+) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+
+"Marks an element of a GraphQL schema as no longer supported."
+directive @deprecated(
+    """
+    Explains why this element was deprecated, usually also including a suggestion
+    for how to access supported similar data. Formatted in
+    [Markdown](https://daringfireball.net/projects/markdown/).
+    """
+    reason: String = "No longer supported"
+) on FIELD_DEFINITION | ENUM_VALUE
+
+"""
+A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.
+In some cases, you need to provide options to alter GraphQL's execution behavior
+in ways field arguments will not suffice, such as conditionally including or
+skipping a field. Directives provide this by describing additional information
+to the executor.
+"""
+type __Directive {
+    name: String!
+    description: String
+    locations: [__DirectiveLocation!]!
+    args: [__InputValue!]!
+}
+
+"""
+A Directive can be adjacent to many parts of the GraphQL language, a
+__DirectiveLocation describes one such possible adjacencies.
+"""
+enum __DirectiveLocation {
+    "Location adjacent to a query operation."
+    QUERY
+    "Location adjacent to a mutation operation."
+    MUTATION
+    "Location adjacent to a subscription operation."
+    SUBSCRIPTION
+    "Location adjacent to a field."
+    FIELD
+    "Location adjacent to a fragment definition."
+    FRAGMENT_DEFINITION
+    "Location adjacent to a fragment spread."
+    FRAGMENT_SPREAD
+    "Location adjacent to an inline fragment."
+    INLINE_FRAGMENT
+    "Location adjacent to a schema definition."
+    SCHEMA
+    "Location adjacent to a scalar definition."
+    SCALAR
+    "Location adjacent to an object type definition."
+    OBJECT
+    "Location adjacent to a field definition."
+    FIELD_DEFINITION
+    "Location adjacent to an argument definition."
+    ARGUMENT_DEFINITION
+    "Location adjacent to an interface definition."
+    INTERFACE
+    "Location adjacent to a union definition."
+    UNION
+    "Location adjacent to an enum definition."
+    ENUM
+    "Location adjacent to an enum value definition."
+    ENUM_VALUE
+    "Location adjacent to an input object type definition."
+    INPUT_OBJECT
+    "Location adjacent to an input object field definition."
+    INPUT_FIELD_DEFINITION
+}
+
+"""
+One possible value for a given Enum. Enum values are unique values, not a
+placeholder for a string or numeric value. However an Enum value is returned in
+a JSON response as a string.
+"""
+type __EnumValue {
+    name: String!
+    description: String
+    isDeprecated: Boolean!
+    deprecationReason: String
+}
+
+"""
+Object and Interface types are described by a list of Fields, each of which has
+a name, potentially a list of arguments, and a return type.
+"""
+type __Field {
+    name: String!
+    description: String
+    args: [__InputValue!]!
+    type: __Type!
+    isDeprecated: Boolean!
+    deprecationReason: String
+}
+
+"""
+Arguments provided to Fields or Directives and the input fields of an
+InputObject are represented as Input Values which describe their type and
+optionally a default value.
+"""
+type __InputValue {
+    name: String!
+    description: String
+    type: __Type!
+    "A GraphQL-formatted string representing the default value for this input value."
+    defaultValue: String
+}
+
+"""
+A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all
+available types and directives on the server, as well as the entry points for
+query, mutation, and subscription operations.
+"""
+type __Schema {
+    "A list of all types supported by this server."
+    types: [__Type!]!
+    "The type that query operations will be rooted at."
+    queryType: __Type!
+    "If this server supports mutation, the type that mutation operations will be rooted at."
+    mutationType: __Type
+    "If this server support subscription, the type that subscription operations will be rooted at."
+    subscriptionType: __Type
+    "A list of all directives supported by this server."
+    directives: [__Directive!]!
+}
+
+"""
+The fundamental unit of any GraphQL Schema is the type. There are many kinds of
+types in GraphQL as represented by the '__TypeKind' enum.
+
+Depending on the kind of a type, certain fields describe information about that
+type. Scalar types provide no information beyond a name and description, while
+Enum types provide their values. Object and Interface types provide the fields
+they describe. Abstract types, Union and Interface, provide the Object types
+possible at runtime. List and NonNull types compose other types.
+"""
+type __Type {
+    kind: __TypeKind!
+    name: String
+    description: String
+    fields(includeDeprecated: Boolean = false): [__Field!]
+    interfaces: [__Type!]
+    possibleTypes: [__Type!]
+    enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
+    inputFields: [__InputValue!]
+    ofType: __Type
+}
+
+"An enum describing what kind of type a given '__Type' is."
+enum __TypeKind {
+    "Indicates this type is a scalar."
+    SCALAR
+    "Indicates this type is an object. 'fields' and 'interfaces' are valid fields."
+    OBJECT
+    "Indicates this type is an interface. 'fields' ' and ' 'possibleTypes' are valid fields."
+    INTERFACE
+    "Indicates this type is a union. 'possibleTypes' is a valid field."
+    UNION
+    "Indicates this type is an enum. 'enumValues' is a valid field."
+    ENUM
+    "Indicates this type is an input object. 'inputFields' is a valid field."
+    INPUT_OBJECT
+    "Indicates this type is a list. 'ofType' is a valid field."
+    LIST
+    "Indicates this type is a non-null. 'ofType' is a valid field."
+    NON_NULL
+}
\ No newline at end of file
diff --git a/pkg/asttransform/fixtures/simple.golden b/pkg/asttransform/fixtures/simple.golden
new file mode 100644
index 00000000..9f11ab99
--- /dev/null
+++ b/pkg/asttransform/fixtures/simple.golden
@@ -0,0 +1,205 @@
+schema {
+    query: Query
+}
+
+type Query {
+    hello(name: String): Hello!
+}
+
+type Hello {
+    hello: String!
+    object: String!
+    adminInformation: String!
+}
+
+"The 'Int' scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1."
+scalar Int
+
+"The 'Float' scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point)."
+scalar Float
+
+"The 'String' scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text."
+scalar String
+
+"The 'Boolean' scalar type represents 'true' or 'false' ."
+scalar Boolean
+
+"The 'ID' scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as '4') or integer (such as 4) input value will be accepted as an ID."
+scalar ID
+
+"Directs the executor to include this field or fragment only when the argument is true."
+directive @include(
+    "Included when true."
+    if: Boolean!
+) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+
+"Directs the executor to skip this field or fragment when the argument is true."
+directive @skip(
+    "Skipped when true."
+    if: Boolean!
+) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+
+"Marks an element of a GraphQL schema as no longer supported."
+directive @deprecated(
+    """
+    Explains why this element was deprecated, usually also including a suggestion
+    for how to access supported similar data. Formatted in
+    [Markdown](https://daringfireball.net/projects/markdown/).
+    """
+    reason: String = "No longer supported"
+) on FIELD_DEFINITION | ENUM_VALUE
+
+"""
+A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.
+In some cases, you need to provide options to alter GraphQL's execution behavior
+in ways field arguments will not suffice, such as conditionally including or
+skipping a field. Directives provide this by describing additional information
+to the executor.
+"""
+type __Directive {
+    name: String!
+    description: String
+    locations: [__DirectiveLocation!]!
+    args: [__InputValue!]!
+}
+
+"""
+A Directive can be adjacent to many parts of the GraphQL language, a
+__DirectiveLocation describes one such possible adjacencies.
+"""
+enum __DirectiveLocation {
+    "Location adjacent to a query operation."
+    QUERY
+    "Location adjacent to a mutation operation."
+    MUTATION
+    "Location adjacent to a subscription operation."
+    SUBSCRIPTION
+    "Location adjacent to a field."
+    FIELD
+    "Location adjacent to a fragment definition."
+    FRAGMENT_DEFINITION
+    "Location adjacent to a fragment spread."
+    FRAGMENT_SPREAD
+    "Location adjacent to an inline fragment."
+    INLINE_FRAGMENT
+    "Location adjacent to a schema definition."
+    SCHEMA
+    "Location adjacent to a scalar definition."
+    SCALAR
+    "Location adjacent to an object type definition."
+    OBJECT
+    "Location adjacent to a field definition."
+    FIELD_DEFINITION
+    "Location adjacent to an argument definition."
+    ARGUMENT_DEFINITION
+    "Location adjacent to an interface definition."
+    INTERFACE
+    "Location adjacent to a union definition."
+    UNION
+    "Location adjacent to an enum definition."
+    ENUM
+    "Location adjacent to an enum value definition."
+    ENUM_VALUE
+    "Location adjacent to an input object type definition."
+    INPUT_OBJECT
+    "Location adjacent to an input object field definition."
+    INPUT_FIELD_DEFINITION
+}
+
+"""
+One possible value for a given Enum. Enum values are unique values, not a
+placeholder for a string or numeric value. However an Enum value is returned in
+a JSON response as a string.
+"""
+type __EnumValue {
+    name: String!
+    description: String
+    isDeprecated: Boolean!
+    deprecationReason: String
+}
+
+"""
+Object and Interface types are described by a list of Fields, each of which has
+a name, potentially a list of arguments, and a return type.
+"""
+type __Field {
+    name: String!
+    description: String
+    args: [__InputValue!]!
+    type: __Type!
+    isDeprecated: Boolean!
+    deprecationReason: String
+}
+
+"""
+Arguments provided to Fields or Directives and the input fields of an
+InputObject are represented as Input Values which describe their type and
+optionally a default value.
+"""
+type __InputValue {
+    name: String!
+    description: String
+    type: __Type!
+    "A GraphQL-formatted string representing the default value for this input value."
+    defaultValue: String
+}
+
+"""
+A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all
+available types and directives on the server, as well as the entry points for
+query, mutation, and subscription operations.
+"""
+type __Schema {
+    "A list of all types supported by this server."
+    types: [__Type!]!
+    "The type that query operations will be rooted at."
+    queryType: __Type!
+    "If this server supports mutation, the type that mutation operations will be rooted at."
+    mutationType: __Type
+    "If this server support subscription, the type that subscription operations will be rooted at."
+    subscriptionType: __Type
+    "A list of all directives supported by this server."
+    directives: [__Directive!]!
+}
+
+"""
+The fundamental unit of any GraphQL Schema is the type. There are many kinds of
+types in GraphQL as represented by the '__TypeKind' enum.
+
+Depending on the kind of a type, certain fields describe information about that
+type. Scalar types provide no information beyond a name and description, while
+Enum types provide their values. Object and Interface types provide the fields
+they describe. Abstract types, Union and Interface, provide the Object types
+possible at runtime. List and NonNull types compose other types.
+"""
+type __Type {
+    kind: __TypeKind!
+    name: String
+    description: String
+    fields(includeDeprecated: Boolean = false): [__Field!]
+    interfaces: [__Type!]
+    possibleTypes: [__Type!]
+    enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
+    inputFields: [__InputValue!]
+    ofType: __Type
+}
+
+"An enum describing what kind of type a given '__Type' is."
+enum __TypeKind {
+    "Indicates this type is a scalar."
+    SCALAR
+    "Indicates this type is an object. 'fields' and 'interfaces' are valid fields."
+    OBJECT
+    "Indicates this type is an interface. 'fields' ' and ' 'possibleTypes' are valid fields."
+    INTERFACE
+    "Indicates this type is a union. 'possibleTypes' is a valid field."
+    UNION
+    "Indicates this type is an enum. 'enumValues' is a valid field."
+    ENUM
+    "Indicates this type is an input object. 'inputFields' is a valid field."
+    INPUT_OBJECT
+    "Indicates this type is a list. 'ofType' is a valid field."
+    LIST
+    "Indicates this type is a non-null. 'ofType' is a valid field."
+    NON_NULL
+}
\ No newline at end of file


Test patch:
diff --git a/pkg/asttransform/baseschema_test.go b/pkg/asttransform/baseschema_test.go
new file mode 100644
index 00000000..e69be07a
--- /dev/null
+++ b/pkg/asttransform/baseschema_test.go
@@ -0,0 +1,61 @@
+package asttransform
+
+import (
+	"bytes"
+	"github.com/jensneuse/diffview"
+	"github.com/jensneuse/graphql-go-tools/internal/pkg/unsafeparser"
+	"github.com/jensneuse/graphql-go-tools/pkg/astprinter"
+	"github.com/sebdah/goldie"
+	"io/ioutil"
+	"testing"
+)
+
+func runTestMerge(definition, fixtureName string) func(t *testing.T) {
+	return func(t *testing.T) {
+		doc := unsafeparser.ParseGraphqlDocumentString(definition)
+		err := MergeDefinitionWithBaseSchema(&doc)
+		if err != nil {
+			panic(err)
+		}
+		buf := bytes.Buffer{}
+		err = astprinter.PrintIndent(&doc, nil, []byte("  "), &buf)
+		if err != nil {
+			panic(err)
+		}
+		got := buf.Bytes()
+		goldie.Assert(t, fixtureName, got)
+		if t.Failed() {
+			want, err := ioutil.ReadFile("./fixtures/" + fixtureName + ".golden")
+			if err != nil {
+				panic(err)
+			}
+			diffview.NewGoland().DiffViewBytes(fixtureName, want, got)
+		}
+	}
+}
+
+func TestMergeDefinitionWithBaseSchema(t *testing.T) {
+	t.Run("simple", runTestMerge(`
+			schema {
+				query: Query
+			}
+			type Query {
+				hello(name: String): Hello!
+			}
+			type Hello {
+				hello: String!
+				object: String!
+				adminInformation: String!
+			}
+	`, "simple"))
+	t.Run("schema missing", runTestMerge(`
+			type Query {
+				hello(name: String): Hello!
+			}
+			type Hello {
+				hello: String!
+				object: String!
+				adminInformation: String!
+			}
+	`, "schema_missing"))
+}