github.com/dgraph-io/dgraph@v1.2.8/graphql/schema/gqlschema.go (about)

     1  /*
     2   * Copyright 2019 Dgraph Labs, Inc. and Contributors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package schema
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"strings"
    23  
    24  	"github.com/dgraph-io/dgraph/x"
    25  	"github.com/vektah/gqlparser/ast"
    26  	"github.com/vektah/gqlparser/gqlerror"
    27  	"github.com/vektah/gqlparser/parser"
    28  )
    29  
    30  const (
    31  	inverseDirective = "hasInverse"
    32  	inverseArg       = "field"
    33  
    34  	searchDirective = "search"
    35  	searchArgs      = "by"
    36  
    37  	dgraphDirective = "dgraph"
    38  	dgraphTypeArg   = "type"
    39  	dgraphPredArg   = "pred"
    40  	idDirective     = "id"
    41  
    42  	Typename = "__typename"
    43  
    44  	// schemaExtras is everything that gets added to an input schema to make it
    45  	// GraphQL valid and for the completion algorithm to use to build in search
    46  	// capability into the schema.
    47  	schemaExtras = `
    48  scalar DateTime
    49  
    50  enum DgraphIndex {
    51  	int
    52  	float
    53  	bool
    54  	hash
    55  	exact
    56  	term
    57  	fulltext
    58  	trigram
    59  	regexp
    60  	year
    61  	month
    62  	day
    63  	hour
    64  }
    65  
    66  directive @hasInverse(field: String!) on FIELD_DEFINITION
    67  directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION
    68  directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION
    69  directive @id on FIELD_DEFINITION
    70  
    71  input IntFilter {
    72  	eq: Int
    73  	le: Int
    74  	lt: Int
    75  	ge: Int
    76  	gt: Int
    77  }
    78  
    79  input FloatFilter {
    80  	eq: Float
    81  	le: Float
    82  	lt: Float
    83  	ge: Float
    84  	gt: Float
    85  }
    86  
    87  input DateTimeFilter {
    88  	eq: DateTime
    89  	le: DateTime
    90  	lt: DateTime
    91  	ge: DateTime
    92  	gt: DateTime
    93  }
    94  
    95  input StringTermFilter {
    96  	allofterms: String
    97  	anyofterms: String
    98  }
    99  
   100  input StringRegExpFilter {
   101  	regexp: String
   102  }
   103  
   104  input StringFullTextFilter {
   105  	alloftext: String
   106  	anyoftext: String
   107  }
   108  
   109  input StringExactFilter {
   110  	eq: String
   111  	le: String
   112  	lt: String
   113  	ge: String
   114  	gt: String
   115  }
   116  
   117  input StringHashFilter {
   118  	eq: String
   119  }
   120  `
   121  )
   122  
   123  // Filters for Boolean and enum aren't needed in here schemaExtras because they are
   124  // generated directly for the bool field / enum.  E.g. if
   125  // `type T { b: Boolean @search }`,
   126  // then the filter allows `filter: {b: true}`.  That's better than having
   127  // `input BooleanFilter { eq: Boolean }`, which would require writing
   128  // `filter: {b: {eq: true}}`.
   129  //
   130  // It'd be nice to be able to just write `filter: isPublished` for say a Post
   131  // with a Boolean isPublished field, but there's no way to get that in GraphQL
   132  // because input union types aren't yet sorted out in GraphQL.  So it's gotta
   133  // be `filter: {isPublished: true}`
   134  
   135  type directiveValidator func(
   136  	sch *ast.Schema,
   137  	typ *ast.Definition,
   138  	field *ast.FieldDefinition,
   139  	dir *ast.Directive) *gqlerror.Error
   140  
   141  type searchTypeIndex struct {
   142  	gqlType string
   143  	dgIndex string
   144  }
   145  
   146  // search arg -> supported GraphQL type
   147  // == supported Dgraph index -> GraphQL type it applies to
   148  var supportedSearches = map[string]searchTypeIndex{
   149  	"int":      {"Int", "int"},
   150  	"float":    {"Float", "float"},
   151  	"bool":     {"Boolean", "bool"},
   152  	"hash":     {"String", "hash"},
   153  	"exact":    {"String", "exact"},
   154  	"term":     {"String", "term"},
   155  	"fulltext": {"String", "fulltext"},
   156  	"trigram":  {"String", "trigram"},
   157  	"regexp":   {"String", "trigram"},
   158  	"year":     {"DateTime", "year"},
   159  	"month":    {"DateTime", "month"},
   160  	"day":      {"DateTime", "day"},
   161  	"hour":     {"DateTime", "hour"},
   162  }
   163  
   164  // GraphQL scalar type -> default Dgraph index (/search)
   165  // used if the schema specifies @search without an arg
   166  var defaultSearches = map[string]string{
   167  	"Boolean":  "bool",
   168  	"Int":      "int",
   169  	"Float":    "float",
   170  	"String":   "term",
   171  	"DateTime": "year",
   172  }
   173  
   174  // Dgraph index filters that have contains intersecting filter
   175  // directive.
   176  var filtersCollisions = map[string][]string{
   177  	"StringHashFilter":  {"StringExactFilter"},
   178  	"StringExactFilter": {"StringHashFilter"},
   179  }
   180  
   181  // GraphQL types that can be used for ordering in orderasc and orderdesc.
   182  var orderable = map[string]bool{
   183  	"Int":      true,
   184  	"Float":    true,
   185  	"String":   true,
   186  	"DateTime": true,
   187  }
   188  
   189  var enumDirectives = map[string]bool{
   190  	"trigram": true,
   191  	"hash":    true,
   192  	"exact":   true,
   193  	"regexp":  true,
   194  }
   195  
   196  // index name -> GraphQL input filter for that index
   197  var builtInFilters = map[string]string{
   198  	"bool":     "Boolean",
   199  	"int":      "IntFilter",
   200  	"float":    "FloatFilter",
   201  	"year":     "DateTimeFilter",
   202  	"month":    "DateTimeFilter",
   203  	"day":      "DateTimeFilter",
   204  	"hour":     "DateTimeFilter",
   205  	"term":     "StringTermFilter",
   206  	"trigram":  "StringRegExpFilter",
   207  	"regexp":   "StringRegExpFilter",
   208  	"fulltext": "StringFullTextFilter",
   209  	"exact":    "StringExactFilter",
   210  	"hash":     "StringHashFilter",
   211  }
   212  
   213  // GraphQL scalar -> Dgraph scalar
   214  var scalarToDgraph = map[string]string{
   215  	"ID":       "uid",
   216  	"Boolean":  "bool",
   217  	"Int":      "int",
   218  	"Float":    "float",
   219  	"String":   "string",
   220  	"DateTime": "dateTime",
   221  }
   222  
   223  var directiveValidators = map[string]directiveValidator{
   224  	inverseDirective: hasInverseValidation,
   225  	searchDirective:  searchValidation,
   226  	dgraphDirective:  dgraphDirectiveValidation,
   227  	idDirective:      idValidation,
   228  }
   229  
   230  var defnValidations, typeValidations []func(defn *ast.Definition) *gqlerror.Error
   231  var fieldValidations []func(typ *ast.Definition, field *ast.FieldDefinition) *gqlerror.Error
   232  
   233  func copyAstFieldDef(src *ast.FieldDefinition) *ast.FieldDefinition {
   234  	var dirs ast.DirectiveList
   235  	for _, d := range src.Directives {
   236  		if d.Name != inverseDirective {
   237  			dirs = append(dirs, d)
   238  		}
   239  	}
   240  
   241  	// Lets leave out copying the arguments as types in input schemas are not supposed to contain
   242  	// them. We add arguments for filters and order statements later.
   243  	dst := &ast.FieldDefinition{
   244  		Name:         src.Name,
   245  		DefaultValue: src.DefaultValue,
   246  		Type:         src.Type,
   247  		Directives:   dirs,
   248  		Position:     src.Position,
   249  	}
   250  	return dst
   251  }
   252  
   253  func expandSchema(doc *ast.SchemaDocument) {
   254  	docExtras, gqlErr := parser.ParseSchema(&ast.Source{Input: schemaExtras})
   255  	if gqlErr != nil {
   256  		panic(gqlErr)
   257  	}
   258  
   259  	// Cache the interface definitions in a map. They could also be defined after types which
   260  	// implement them.
   261  	interfaces := make(map[string]*ast.Definition)
   262  	for _, defn := range doc.Definitions {
   263  		if defn.Kind == ast.Interface {
   264  			interfaces[defn.Name] = defn
   265  		}
   266  	}
   267  
   268  	// Walk through type definitions which implement an interface and fill in the fields from the
   269  	// interface.
   270  	for _, defn := range doc.Definitions {
   271  		if defn.Kind == ast.Object && len(defn.Interfaces) > 0 {
   272  			for _, implements := range defn.Interfaces {
   273  				i, ok := interfaces[implements]
   274  				if !ok {
   275  					// This would fail schema validation later.
   276  					continue
   277  				}
   278  				fields := make([]*ast.FieldDefinition, 0, len(i.Fields))
   279  				for _, field := range i.Fields {
   280  					// Creating a copy here is important, otherwise arguments like filter, order
   281  					// etc. are added multiple times if the pointer is shared.
   282  					fields = append(fields, copyAstFieldDef(field))
   283  				}
   284  				defn.Fields = append(fields, defn.Fields...)
   285  			}
   286  		}
   287  	}
   288  
   289  	doc.Definitions = append(doc.Definitions, docExtras.Definitions...)
   290  	doc.Directives = append(doc.Directives, docExtras.Directives...)
   291  }
   292  
   293  // preGQLValidation validates schema before GraphQL validation.  Validation
   294  // before GraphQL validation means the schema only has allowed structures, and
   295  // means we can give better errors than GrqphQL validation would give if their
   296  // schema contains something that will fail because of the extras we inject into
   297  // the schema.
   298  func preGQLValidation(schema *ast.SchemaDocument) gqlerror.List {
   299  	var errs []*gqlerror.Error
   300  
   301  	for _, defn := range schema.Definitions {
   302  		if defn.BuiltIn {
   303  			// prelude definitions are built in and we don't want to validate them.
   304  			continue
   305  		}
   306  		errs = append(errs, applyDefnValidations(defn, defnValidations)...)
   307  	}
   308  
   309  	return errs
   310  }
   311  
   312  // postGQLValidation validates schema after gql validation.  Some validations
   313  // are easier to run once we know that the schema is GraphQL valid and that validation
   314  // has fleshed out the schema structure; we just need to check if it also satisfies
   315  // the extra rules.
   316  func postGQLValidation(schema *ast.Schema, definitions []string) gqlerror.List {
   317  	var errs []*gqlerror.Error
   318  
   319  	for _, defn := range definitions {
   320  		typ := schema.Types[defn]
   321  
   322  		errs = append(errs, applyDefnValidations(typ, typeValidations)...)
   323  
   324  		for _, field := range typ.Fields {
   325  			errs = append(errs, applyFieldValidations(typ, field)...)
   326  
   327  			for _, dir := range field.Directives {
   328  				errs = appendIfNotNull(errs,
   329  					directiveValidators[dir.Name](schema, typ, field, dir))
   330  			}
   331  		}
   332  	}
   333  
   334  	return errs
   335  }
   336  
   337  func applyDefnValidations(defn *ast.Definition,
   338  	rules []func(defn *ast.Definition) *gqlerror.Error) gqlerror.List {
   339  	var errs []*gqlerror.Error
   340  
   341  	for _, rule := range rules {
   342  		errs = appendIfNotNull(errs, rule(defn))
   343  	}
   344  
   345  	return errs
   346  }
   347  
   348  func applyFieldValidations(typ *ast.Definition, field *ast.FieldDefinition) gqlerror.List {
   349  	var errs []*gqlerror.Error
   350  
   351  	for _, rule := range fieldValidations {
   352  		errs = appendIfNotNull(errs, rule(typ, field))
   353  	}
   354  
   355  	return errs
   356  }
   357  
   358  // completeSchema generates all the required types and fields for
   359  // query/mutation/update for all the types mentioned the the schema.
   360  func completeSchema(sch *ast.Schema, definitions []string) {
   361  
   362  	sch.Query = &ast.Definition{
   363  		Kind:   ast.Object,
   364  		Name:   "Query",
   365  		Fields: make([]*ast.FieldDefinition, 0),
   366  	}
   367  
   368  	sch.Mutation = &ast.Definition{
   369  		Kind:   ast.Object,
   370  		Name:   "Mutation",
   371  		Fields: make([]*ast.FieldDefinition, 0),
   372  	}
   373  
   374  	for _, key := range definitions {
   375  		defn := sch.Types[key]
   376  
   377  		if defn.Kind != ast.Interface && defn.Kind != ast.Object {
   378  			continue
   379  		}
   380  
   381  		// Common types to both Interface and Object.
   382  		addReferenceType(sch, defn)
   383  		addPatchType(sch, defn)
   384  		addUpdateType(sch, defn)
   385  		addUpdatePayloadType(sch, defn)
   386  		addDeletePayloadType(sch, defn)
   387  
   388  		switch defn.Kind {
   389  		case ast.Interface:
   390  			// addInputType doesn't make sense as interface is like an abstract class and we can't
   391  			// create objects of its type.
   392  			addUpdateMutation(sch, defn)
   393  			addDeleteMutation(sch, defn)
   394  
   395  		case ast.Object:
   396  			// types and inputs needed for mutations
   397  			addInputType(sch, defn)
   398  			addAddPayloadType(sch, defn)
   399  			addMutations(sch, defn)
   400  		}
   401  
   402  		// types and inputs needed for query and search
   403  		addFilterType(sch, defn)
   404  		addTypeOrderable(sch, defn)
   405  		addFieldFilters(sch, defn)
   406  		addQueries(sch, defn)
   407  	}
   408  }
   409  
   410  func addInputType(schema *ast.Schema, defn *ast.Definition) {
   411  	schema.Types["Add"+defn.Name+"Input"] = &ast.Definition{
   412  		Kind:   ast.InputObject,
   413  		Name:   "Add" + defn.Name + "Input",
   414  		Fields: getFieldsWithoutIDType(schema, defn),
   415  	}
   416  }
   417  
   418  func addReferenceType(schema *ast.Schema, defn *ast.Definition) {
   419  	var flds ast.FieldList
   420  	if defn.Kind == ast.Interface {
   421  		if !hasID(defn) && !hasXID(defn) {
   422  			return
   423  		}
   424  		flds = append(getIDField(defn), getXIDField(defn)...)
   425  	} else {
   426  		flds = append(getIDField(defn), getFieldsWithoutIDType(schema, defn)...)
   427  	}
   428  
   429  	if len(flds) == 1 && (hasID(defn) || hasXID(defn)) {
   430  		flds[0].Type.NonNull = true
   431  	} else {
   432  		for _, fld := range flds {
   433  			fld.Type.NonNull = false
   434  		}
   435  	}
   436  
   437  	schema.Types[defn.Name+"Ref"] = &ast.Definition{
   438  		Kind:   ast.InputObject,
   439  		Name:   defn.Name + "Ref",
   440  		Fields: flds,
   441  	}
   442  }
   443  
   444  func addUpdateType(schema *ast.Schema, defn *ast.Definition) {
   445  	if !hasFilterable(defn) {
   446  		return
   447  	}
   448  	if _, ok := schema.Types[defn.Name+"Patch"]; !ok {
   449  		return
   450  	}
   451  
   452  	updType := &ast.Definition{
   453  		Kind: ast.InputObject,
   454  		Name: "Update" + defn.Name + "Input",
   455  		Fields: append(
   456  			ast.FieldList{&ast.FieldDefinition{
   457  				Name: "filter",
   458  				Type: &ast.Type{
   459  					NamedType: defn.Name + "Filter",
   460  					NonNull:   true,
   461  				},
   462  			}},
   463  			&ast.FieldDefinition{
   464  				Name: "set",
   465  				Type: &ast.Type{
   466  					NamedType: defn.Name + "Patch",
   467  				},
   468  			},
   469  			&ast.FieldDefinition{
   470  				Name: "remove",
   471  				Type: &ast.Type{
   472  					NamedType: defn.Name + "Patch",
   473  				},
   474  			}),
   475  	}
   476  	schema.Types["Update"+defn.Name+"Input"] = updType
   477  }
   478  
   479  func addPatchType(schema *ast.Schema, defn *ast.Definition) {
   480  	if !hasFilterable(defn) {
   481  		return
   482  	}
   483  
   484  	nonIDFields := getNonIDFields(schema, defn)
   485  	if len(nonIDFields) == 0 {
   486  		// The user might just have an external id field and nothing else. We don't generate patch
   487  		// type in that case.
   488  		return
   489  	}
   490  
   491  	patchDefn := &ast.Definition{
   492  		Kind:   ast.InputObject,
   493  		Name:   defn.Name + "Patch",
   494  		Fields: nonIDFields,
   495  	}
   496  	schema.Types[defn.Name+"Patch"] = patchDefn
   497  
   498  	for _, fld := range patchDefn.Fields {
   499  		fld.Type.NonNull = false
   500  	}
   501  }
   502  
   503  // addFieldFilters adds field arguments that allow filtering to all fields of
   504  // defn that can be searched.  For example, if there's another type
   505  // `type R { ... f: String @search(by: [term]) ... }`
   506  // and defn has a field of type R, e.g. if defn is like
   507  // `type T { ... g: R ... }`
   508  // then a query should be able to filter on g by term search on f, like
   509  // query {
   510  //   getT(id: 0x123) {
   511  //     ...
   512  //     g(filter: { f: { anyofterms: "something" } }, first: 10) { ... }
   513  //     ...
   514  //   }
   515  // }
   516  func addFieldFilters(schema *ast.Schema, defn *ast.Definition) {
   517  	for _, fld := range defn.Fields {
   518  		// Filtering makes sense both for lists (= return only items that match
   519  		// this filter) and for singletons (= only have this value in the result
   520  		// if it satisfies this filter)
   521  		addFilterArgument(schema, fld)
   522  
   523  		// Ordering and pagination, however, only makes sense for fields of
   524  		// list types (not scalar lists).
   525  		if _, scalar := scalarToDgraph[fld.Type.Name()]; !scalar && fld.Type.Elem != nil {
   526  			addOrderArgument(schema, fld)
   527  
   528  			// Pagination even makes sense when there's no orderables because
   529  			// Dgraph will do UID order by default.
   530  			addPaginationArguments(fld)
   531  		}
   532  	}
   533  }
   534  
   535  func addFilterArgument(schema *ast.Schema, fld *ast.FieldDefinition) {
   536  	fldType := fld.Type.Name()
   537  	if hasFilterable(schema.Types[fldType]) {
   538  		fld.Arguments = append(fld.Arguments,
   539  			&ast.ArgumentDefinition{
   540  				Name: "filter",
   541  				Type: &ast.Type{NamedType: fldType + "Filter"},
   542  			})
   543  	}
   544  }
   545  
   546  func addOrderArgument(schema *ast.Schema, fld *ast.FieldDefinition) {
   547  	fldType := fld.Type.Name()
   548  	if hasOrderables(schema.Types[fldType]) {
   549  		fld.Arguments = append(fld.Arguments,
   550  			&ast.ArgumentDefinition{
   551  				Name: "order",
   552  				Type: &ast.Type{NamedType: fldType + "Order"},
   553  			})
   554  	}
   555  }
   556  
   557  func addPaginationArguments(fld *ast.FieldDefinition) {
   558  	fld.Arguments = append(fld.Arguments,
   559  		&ast.ArgumentDefinition{Name: "first", Type: &ast.Type{NamedType: "Int"}},
   560  		&ast.ArgumentDefinition{Name: "offset", Type: &ast.Type{NamedType: "Int"}},
   561  	)
   562  }
   563  
   564  // getFilterTypes converts search arguments of a field to graphql filter types.
   565  func getFilterTypes(schema *ast.Schema, fld *ast.FieldDefinition, filterName string) []string {
   566  	searchArgs := getSearchArgs(fld)
   567  	filterNames := make([]string, len(searchArgs))
   568  
   569  	for i, search := range searchArgs {
   570  		filterNames[i] = builtInFilters[search]
   571  
   572  		if (search == "hash" || search == "exact") && schema.Types[fld.Type.Name()].Kind == ast.Enum {
   573  			stringFilterName := fmt.Sprintf("String%sFilter", strings.Title(search))
   574  			var l ast.FieldList
   575  
   576  			for _, i := range schema.Types[stringFilterName].Fields {
   577  				l = append(l, &ast.FieldDefinition{
   578  					Name:         i.Name,
   579  					Type:         fld.Type,
   580  					Description:  i.Description,
   581  					DefaultValue: i.DefaultValue,
   582  				})
   583  			}
   584  
   585  			filterNames[i] = fld.Type.Name() + "_" + search
   586  			schema.Types[filterNames[i]] = &ast.Definition{
   587  				Kind:   ast.InputObject,
   588  				Name:   filterNames[i],
   589  				Fields: l,
   590  			}
   591  		}
   592  	}
   593  
   594  	return filterNames
   595  }
   596  
   597  // mergeAndAddFilters merges multiple filterTypes into one and adds it to the schema.
   598  func mergeAndAddFilters(filterTypes []string, schema *ast.Schema, filterName string) {
   599  	if len(filterTypes) <= 1 {
   600  		// Filters only require to be merged if there are alteast 2
   601  		return
   602  	}
   603  
   604  	var fieldList ast.FieldList
   605  	for _, typeName := range filterTypes {
   606  		fieldList = append(fieldList, schema.Types[typeName].Fields...)
   607  	}
   608  
   609  	schema.Types[filterName] = &ast.Definition{
   610  		Kind:   ast.InputObject,
   611  		Name:   filterName,
   612  		Fields: fieldList,
   613  	}
   614  }
   615  
   616  // addFilterType add a `input TFilter { ... }` type to the schema, if defn
   617  // is a type that has fields that can be filtered on.  This type filter is used
   618  // in constructing the corresponding query
   619  // queryT(filter: TFilter, ... )
   620  // and in adding search to any fields of this type, like:
   621  // type R {
   622  //   f(filter: TFilter, ... ): T
   623  //   ...
   624  // }
   625  func addFilterType(schema *ast.Schema, defn *ast.Definition) {
   626  	if !hasFilterable(defn) {
   627  		return
   628  	}
   629  
   630  	filterName := defn.Name + "Filter"
   631  	filter := &ast.Definition{
   632  		Kind: ast.InputObject,
   633  		Name: filterName,
   634  	}
   635  
   636  	for _, fld := range defn.Fields {
   637  		if isID(fld) {
   638  			filter.Fields = append(filter.Fields,
   639  				&ast.FieldDefinition{
   640  					Name: fld.Name,
   641  					Type: ast.ListType(&ast.Type{
   642  						NamedType: IDType,
   643  						NonNull:   true,
   644  					}, nil),
   645  				})
   646  			continue
   647  		}
   648  
   649  		filterTypes := getFilterTypes(schema, fld, filterName)
   650  		if len(filterTypes) > 0 {
   651  			filterName := strings.Join(filterTypes, "_")
   652  			filter.Fields = append(filter.Fields,
   653  				&ast.FieldDefinition{
   654  					Name: fld.Name,
   655  					Type: &ast.Type{
   656  						NamedType: filterName,
   657  					},
   658  				})
   659  
   660  			mergeAndAddFilters(filterTypes, schema, filterName)
   661  		}
   662  	}
   663  
   664  	// Not filter makes sense even if the filter has only one field. And/Or would only make sense
   665  	// if the filter has more than one field or if it has one non-id field.
   666  	if (len(filter.Fields) == 1 && !isID(filter.Fields[0])) || len(filter.Fields) > 1 {
   667  		filter.Fields = append(filter.Fields,
   668  			&ast.FieldDefinition{Name: "and", Type: &ast.Type{NamedType: filterName}},
   669  			&ast.FieldDefinition{Name: "or", Type: &ast.Type{NamedType: filterName}},
   670  		)
   671  	}
   672  
   673  	filter.Fields = append(filter.Fields,
   674  		&ast.FieldDefinition{Name: "not", Type: &ast.Type{NamedType: filterName}})
   675  	schema.Types[filterName] = filter
   676  }
   677  
   678  func hasFilterable(defn *ast.Definition) bool {
   679  	return fieldAny(defn.Fields,
   680  		func(fld *ast.FieldDefinition) bool {
   681  			return len(getSearchArgs(fld)) != 0 || isID(fld)
   682  		})
   683  }
   684  
   685  func hasOrderables(defn *ast.Definition) bool {
   686  	return fieldAny(defn.Fields,
   687  		func(fld *ast.FieldDefinition) bool { return orderable[fld.Type.Name()] })
   688  }
   689  
   690  func hasID(defn *ast.Definition) bool {
   691  	return fieldAny(defn.Fields,
   692  		func(fld *ast.FieldDefinition) bool { return isID(fld) })
   693  }
   694  
   695  func hasXID(defn *ast.Definition) bool {
   696  	return fieldAny(defn.Fields,
   697  		func(fld *ast.FieldDefinition) bool { return hasIDDirective(fld) })
   698  }
   699  
   700  // fieldAny returns true if any field in fields satisfies pred
   701  func fieldAny(fields ast.FieldList, pred func(*ast.FieldDefinition) bool) bool {
   702  	for _, fld := range fields {
   703  		if pred(fld) {
   704  			return true
   705  		}
   706  	}
   707  	return false
   708  }
   709  
   710  func addHashIfRequired(fld *ast.FieldDefinition, indexes []string) []string {
   711  	id := fld.Directives.ForName(idDirective)
   712  	if id != nil {
   713  		// If @id directive is applied along with @search, we check if the search has hash as an
   714  		// arg. If it doesn't, then we add it.
   715  		containsHash := false
   716  		for _, index := range indexes {
   717  			if index == "hash" {
   718  				containsHash = true
   719  			}
   720  		}
   721  		if !containsHash {
   722  			indexes = append(indexes, "hash")
   723  		}
   724  	}
   725  	return indexes
   726  }
   727  
   728  func getDefaultSearchIndex(fldName string) string {
   729  	if search, ok := defaultSearches[fldName]; ok {
   730  		return search
   731  	}
   732  	// it's an enum - always has hash index
   733  	return "hash"
   734  
   735  }
   736  
   737  // getSearchArgs returns the name of the search applied to fld, or ""
   738  // if fld doesn't have a search directive.
   739  func getSearchArgs(fld *ast.FieldDefinition) []string {
   740  	search := fld.Directives.ForName(searchDirective)
   741  	id := fld.Directives.ForName(idDirective)
   742  	if search == nil {
   743  		if id == nil {
   744  			return nil
   745  		}
   746  		// If search directive wasn't supplied but id was, then hash is the only index
   747  		// that we apply.
   748  		return []string{"hash"}
   749  	}
   750  	if len(search.Arguments) == 0 ||
   751  		len(search.Arguments.ForName(searchArgs).Value.Children) == 0 {
   752  		return []string{getDefaultSearchIndex(fld.Type.Name())}
   753  	}
   754  	val := search.Arguments.ForName(searchArgs).Value
   755  	res := make([]string, len(val.Children))
   756  
   757  	for i, child := range val.Children {
   758  		res[i] = child.Value.Raw
   759  	}
   760  
   761  	res = addHashIfRequired(fld, res)
   762  	sort.Strings(res)
   763  	return res
   764  }
   765  
   766  // addTypeOrderable adds an input type that allows ordering in query.
   767  // Two things are added: an enum with the names of all the orderable fields,
   768  // for a type T that's called TOrderable; and an input type that allows saying
   769  // order asc or desc, for type T that's called TOrder.
   770  // TOrder's fields are TOrderable's.  So you
   771  // might get:
   772  // enum PostOrderable { datePublished, numLikes, ... }, and
   773  // input PostOrder { asc : PostOrderable, desc: PostOrderable ...}
   774  // Together they allow things like
   775  // order: { asc: datePublished }
   776  // and
   777  // order: { asc: datePublished, then: { desc: title } }
   778  //
   779  // Dgraph allows multiple orderings `orderasc: datePublished, orderasc: title`
   780  // to order by datePublished and then by title when dataPublished is the same.
   781  // GraphQL doesn't allow the same field to be repeated, so
   782  // `orderasc: datePublished, orderasc: title` wouldn't be valid.  Instead, our
   783  // GraphQL orderings are given by the structure
   784  // `order: { asc: datePublished, then: { asc: title } }`.
   785  // a further `then` would be a third ordering, etc.
   786  func addTypeOrderable(schema *ast.Schema, defn *ast.Definition) {
   787  	if !hasOrderables(defn) {
   788  		return
   789  	}
   790  
   791  	orderName := defn.Name + "Order"
   792  	orderableName := defn.Name + "Orderable"
   793  
   794  	schema.Types[orderName] = &ast.Definition{
   795  		Kind: ast.InputObject,
   796  		Name: orderName,
   797  		Fields: ast.FieldList{
   798  			&ast.FieldDefinition{Name: "asc", Type: &ast.Type{NamedType: orderableName}},
   799  			&ast.FieldDefinition{Name: "desc", Type: &ast.Type{NamedType: orderableName}},
   800  			&ast.FieldDefinition{Name: "then", Type: &ast.Type{NamedType: orderName}},
   801  		},
   802  	}
   803  
   804  	order := &ast.Definition{
   805  		Kind: ast.Enum,
   806  		Name: orderableName,
   807  	}
   808  
   809  	for _, fld := range defn.Fields {
   810  		if orderable[fld.Type.Name()] {
   811  			order.EnumValues = append(order.EnumValues,
   812  				&ast.EnumValueDefinition{Name: fld.Name})
   813  		}
   814  	}
   815  
   816  	schema.Types[orderableName] = order
   817  }
   818  
   819  func addAddPayloadType(schema *ast.Schema, defn *ast.Definition) {
   820  	qry := &ast.FieldDefinition{
   821  		Name: strings.ToLower(defn.Name),
   822  		Type: ast.ListType(&ast.Type{
   823  			NamedType: defn.Name,
   824  		}, nil),
   825  	}
   826  
   827  	addFilterArgument(schema, qry)
   828  	addOrderArgument(schema, qry)
   829  	addPaginationArguments(qry)
   830  
   831  	schema.Types["Add"+defn.Name+"Payload"] = &ast.Definition{
   832  		Kind:   ast.Object,
   833  		Name:   "Add" + defn.Name + "Payload",
   834  		Fields: []*ast.FieldDefinition{qry},
   835  	}
   836  }
   837  
   838  func addUpdatePayloadType(schema *ast.Schema, defn *ast.Definition) {
   839  	if !hasFilterable(defn) {
   840  		return
   841  	}
   842  
   843  	// This covers the case where the Type only had one field (which had @id directive).
   844  	// Since we don't allow updating the field with @id directive we don't need to generate any
   845  	// update payload.
   846  	if _, ok := schema.Types[defn.Name+"Patch"]; !ok {
   847  		return
   848  	}
   849  
   850  	qry := &ast.FieldDefinition{
   851  		Name: strings.ToLower(defn.Name),
   852  		Type: &ast.Type{
   853  			Elem: &ast.Type{
   854  				NamedType: defn.Name,
   855  			},
   856  		},
   857  	}
   858  
   859  	addFilterArgument(schema, qry)
   860  	addOrderArgument(schema, qry)
   861  	addPaginationArguments(qry)
   862  
   863  	schema.Types["Update"+defn.Name+"Payload"] = &ast.Definition{
   864  		Kind: ast.Object,
   865  		Name: "Update" + defn.Name + "Payload",
   866  		Fields: []*ast.FieldDefinition{
   867  			qry,
   868  		},
   869  	}
   870  }
   871  
   872  func addDeletePayloadType(schema *ast.Schema, defn *ast.Definition) {
   873  	if !hasFilterable(defn) {
   874  		return
   875  	}
   876  
   877  	schema.Types["Delete"+defn.Name+"Payload"] = &ast.Definition{
   878  		Kind: ast.Object,
   879  		Name: "Delete" + defn.Name + "Payload",
   880  		Fields: []*ast.FieldDefinition{
   881  			{
   882  				Name: "msg",
   883  				Type: &ast.Type{
   884  					NamedType: "String",
   885  				},
   886  			},
   887  		},
   888  	}
   889  }
   890  
   891  func addGetQuery(schema *ast.Schema, defn *ast.Definition) {
   892  	hasIDField := hasID(defn)
   893  	hasXIDField := hasXID(defn)
   894  	if !hasIDField && !hasXIDField {
   895  		return
   896  	}
   897  
   898  	qry := &ast.FieldDefinition{
   899  		Name: "get" + defn.Name,
   900  		Type: &ast.Type{
   901  			NamedType: defn.Name,
   902  		},
   903  	}
   904  
   905  	// If the defn, only specified one of ID/XID field, they they are mandatory. If it specified
   906  	// both, then they are optional.
   907  	if hasIDField {
   908  		fields := getIDField(defn)
   909  		qry.Arguments = append(qry.Arguments, &ast.ArgumentDefinition{
   910  			Name: fields[0].Name,
   911  			Type: &ast.Type{
   912  				NamedType: idTypeFor(defn),
   913  				NonNull:   !hasXIDField,
   914  			},
   915  		})
   916  	}
   917  	if hasXIDField {
   918  		name := xidTypeFor(defn)
   919  		qry.Arguments = append(qry.Arguments, &ast.ArgumentDefinition{
   920  			Name: name,
   921  			Type: &ast.Type{
   922  				NamedType: "String",
   923  				NonNull:   !hasIDField,
   924  			},
   925  		})
   926  	}
   927  	schema.Query.Fields = append(schema.Query.Fields, qry)
   928  }
   929  
   930  func addFilterQuery(schema *ast.Schema, defn *ast.Definition) {
   931  	qry := &ast.FieldDefinition{
   932  		Name: "query" + defn.Name,
   933  		Type: &ast.Type{
   934  			Elem: &ast.Type{
   935  				NamedType: defn.Name,
   936  			},
   937  		},
   938  	}
   939  	addFilterArgument(schema, qry)
   940  	addOrderArgument(schema, qry)
   941  	addPaginationArguments(qry)
   942  
   943  	schema.Query.Fields = append(schema.Query.Fields, qry)
   944  }
   945  
   946  func addQueries(schema *ast.Schema, defn *ast.Definition) {
   947  	addGetQuery(schema, defn)
   948  	addFilterQuery(schema, defn)
   949  }
   950  
   951  func addAddMutation(schema *ast.Schema, defn *ast.Definition) {
   952  	add := &ast.FieldDefinition{
   953  		Name: "add" + defn.Name,
   954  		Type: &ast.Type{
   955  			NamedType: "Add" + defn.Name + "Payload",
   956  		},
   957  		Arguments: []*ast.ArgumentDefinition{
   958  			{
   959  				Name: "input",
   960  				Type: &ast.Type{
   961  					NamedType: "[Add" + defn.Name + "Input!]",
   962  					NonNull:   true,
   963  				},
   964  			},
   965  		},
   966  	}
   967  	schema.Mutation.Fields = append(schema.Mutation.Fields, add)
   968  }
   969  
   970  func addUpdateMutation(schema *ast.Schema, defn *ast.Definition) {
   971  	if !hasFilterable(defn) {
   972  		return
   973  	}
   974  
   975  	if _, ok := schema.Types[defn.Name+"Patch"]; !ok {
   976  		return
   977  	}
   978  
   979  	upd := &ast.FieldDefinition{
   980  		Name: "update" + defn.Name,
   981  		Type: &ast.Type{
   982  			NamedType: "Update" + defn.Name + "Payload",
   983  		},
   984  		Arguments: []*ast.ArgumentDefinition{
   985  			{
   986  				Name: "input",
   987  				Type: &ast.Type{
   988  					NamedType: "Update" + defn.Name + "Input",
   989  					NonNull:   true,
   990  				},
   991  			},
   992  		},
   993  	}
   994  	schema.Mutation.Fields = append(schema.Mutation.Fields, upd)
   995  }
   996  
   997  func addDeleteMutation(schema *ast.Schema, defn *ast.Definition) {
   998  	if !hasFilterable(defn) {
   999  		return
  1000  	}
  1001  
  1002  	del := &ast.FieldDefinition{
  1003  		Name: "delete" + defn.Name,
  1004  		Type: &ast.Type{
  1005  			NamedType: "Delete" + defn.Name + "Payload",
  1006  		},
  1007  		Arguments: []*ast.ArgumentDefinition{
  1008  			{
  1009  				Name: "filter",
  1010  				Type: &ast.Type{NamedType: defn.Name + "Filter", NonNull: true},
  1011  			},
  1012  		},
  1013  	}
  1014  	schema.Mutation.Fields = append(schema.Mutation.Fields, del)
  1015  }
  1016  
  1017  func addMutations(schema *ast.Schema, defn *ast.Definition) {
  1018  	addAddMutation(schema, defn)
  1019  	addUpdateMutation(schema, defn)
  1020  	addDeleteMutation(schema, defn)
  1021  }
  1022  
  1023  func createField(schema *ast.Schema, fld *ast.FieldDefinition) *ast.FieldDefinition {
  1024  	if schema.Types[fld.Type.Name()].Kind == ast.Object ||
  1025  		schema.Types[fld.Type.Name()].Kind == ast.Interface {
  1026  		newDefn := &ast.FieldDefinition{
  1027  			Name: fld.Name,
  1028  		}
  1029  
  1030  		newDefn.Type = &ast.Type{}
  1031  		newDefn.Type.NonNull = fld.Type.NonNull
  1032  		if fld.Type.NamedType != "" {
  1033  			newDefn.Type.NamedType = fld.Type.Name() + "Ref"
  1034  		} else {
  1035  			newDefn.Type.Elem = &ast.Type{
  1036  				NamedType: fld.Type.Name() + "Ref",
  1037  				NonNull:   fld.Type.Elem.NonNull,
  1038  			}
  1039  		}
  1040  
  1041  		return newDefn
  1042  	}
  1043  
  1044  	newFld := *fld
  1045  	newFldType := *fld.Type
  1046  	newFld.Type = &newFldType
  1047  	newFld.Directives = nil
  1048  	return &newFld
  1049  }
  1050  
  1051  func getNonIDFields(schema *ast.Schema, defn *ast.Definition) ast.FieldList {
  1052  	fldList := make([]*ast.FieldDefinition, 0)
  1053  	for _, fld := range defn.Fields {
  1054  		if isIDField(defn, fld) || hasIDDirective(fld) {
  1055  			continue
  1056  		}
  1057  
  1058  		// Even if a field isn't referenceable with an ID or XID, it can still go into an
  1059  		// input/update type because it can be created (but not linked by reference) as
  1060  		// part of the mutation.
  1061  		//
  1062  		// But if it's an interface, that can't happen because you can't directly create
  1063  		// interfaces - only the types that implement them
  1064  		if schema.Types[fld.Type.Name()].Kind == ast.Interface &&
  1065  			(!hasID(schema.Types[fld.Type.Name()]) && !hasXID(schema.Types[fld.Type.Name()])) {
  1066  			continue
  1067  		}
  1068  
  1069  		fldList = append(fldList, createField(schema, fld))
  1070  	}
  1071  	return fldList
  1072  }
  1073  
  1074  func getFieldsWithoutIDType(schema *ast.Schema, defn *ast.Definition) ast.FieldList {
  1075  	fldList := make([]*ast.FieldDefinition, 0)
  1076  	for _, fld := range defn.Fields {
  1077  		if isIDField(defn, fld) {
  1078  			continue
  1079  		}
  1080  
  1081  		// see also comment in getNonIDFields
  1082  		if schema.Types[fld.Type.Name()].Kind == ast.Interface &&
  1083  			(!hasID(schema.Types[fld.Type.Name()]) && !hasXID(schema.Types[fld.Type.Name()])) {
  1084  			continue
  1085  		}
  1086  
  1087  		fldList = append(fldList, createField(schema, fld))
  1088  	}
  1089  	return fldList
  1090  }
  1091  
  1092  func getIDField(defn *ast.Definition) ast.FieldList {
  1093  	fldList := make([]*ast.FieldDefinition, 0)
  1094  	for _, fld := range defn.Fields {
  1095  		if isIDField(defn, fld) {
  1096  			newFld := *fld
  1097  			newFldType := *fld.Type
  1098  			newFld.Type = &newFldType
  1099  			fldList = append(fldList, &newFld)
  1100  			break
  1101  		}
  1102  	}
  1103  	return fldList
  1104  }
  1105  
  1106  func getXIDField(defn *ast.Definition) ast.FieldList {
  1107  	fldList := make([]*ast.FieldDefinition, 0)
  1108  	for _, fld := range defn.Fields {
  1109  		if hasIDDirective(fld) {
  1110  			newFld := *fld
  1111  			newFldType := *fld.Type
  1112  			newFld.Type = &newFldType
  1113  			fldList = append(fldList, &newFld)
  1114  			break
  1115  		}
  1116  	}
  1117  	return fldList
  1118  }
  1119  
  1120  func genArgumentsDefnString(args ast.ArgumentDefinitionList) string {
  1121  	if len(args) == 0 {
  1122  		return ""
  1123  	}
  1124  
  1125  	argStrs := make([]string, len(args))
  1126  	for i, arg := range args {
  1127  		argStrs[i] = genArgumentDefnString(arg)
  1128  	}
  1129  
  1130  	return fmt.Sprintf("(%s)", strings.Join(argStrs, ", "))
  1131  }
  1132  
  1133  func genArgumentsString(args ast.ArgumentList) string {
  1134  	if len(args) == 0 {
  1135  		return ""
  1136  	}
  1137  
  1138  	argStrs := make([]string, len(args))
  1139  	for i, arg := range args {
  1140  		argStrs[i] = genArgumentString(arg)
  1141  	}
  1142  
  1143  	return fmt.Sprintf("(%s)", strings.Join(argStrs, ", "))
  1144  }
  1145  
  1146  func genDirectivesString(direcs ast.DirectiveList) string {
  1147  	if len(direcs) == 0 {
  1148  		return ""
  1149  	}
  1150  
  1151  	direcArgs := make([]string, len(direcs))
  1152  
  1153  	for idx, dir := range direcs {
  1154  		direcArgs[idx] = fmt.Sprintf("@%s%s", dir.Name, genArgumentsString(dir.Arguments))
  1155  	}
  1156  
  1157  	return " " + strings.Join(direcArgs, " ")
  1158  }
  1159  
  1160  func genFieldsString(flds ast.FieldList) string {
  1161  	if flds == nil {
  1162  		return ""
  1163  	}
  1164  
  1165  	var sch strings.Builder
  1166  
  1167  	for _, fld := range flds {
  1168  		// Some extra types are generated by gqlparser for internal purpose.
  1169  		if !strings.HasPrefix(fld.Name, "__") {
  1170  			if d := generateDescription(fld.Description); d != "" {
  1171  				x.Check2(sch.WriteString(fmt.Sprintf("\t%s", d)))
  1172  			}
  1173  			x.Check2(sch.WriteString(genFieldString(fld)))
  1174  		}
  1175  	}
  1176  
  1177  	return sch.String()
  1178  }
  1179  
  1180  func genFieldString(fld *ast.FieldDefinition) string {
  1181  	return fmt.Sprintf(
  1182  		"\t%s%s: %s%s\n", fld.Name, genArgumentsDefnString(fld.Arguments),
  1183  		fld.Type.String(), genDirectivesString(fld.Directives))
  1184  }
  1185  
  1186  func genArgumentDefnString(arg *ast.ArgumentDefinition) string {
  1187  	return fmt.Sprintf("%s: %s", arg.Name, arg.Type.String())
  1188  }
  1189  
  1190  func genArgumentString(arg *ast.Argument) string {
  1191  	return fmt.Sprintf("%s: %s", arg.Name, arg.Value.String())
  1192  }
  1193  
  1194  func generateInputString(typ *ast.Definition) string {
  1195  	return fmt.Sprintf("input %s {\n%s}\n", typ.Name, genFieldsString(typ.Fields))
  1196  }
  1197  
  1198  func generateEnumString(typ *ast.Definition) string {
  1199  	var sch strings.Builder
  1200  
  1201  	x.Check2(sch.WriteString(fmt.Sprintf("%senum %s {\n", generateDescription(typ.Description),
  1202  		typ.Name)))
  1203  	for _, val := range typ.EnumValues {
  1204  		if !strings.HasPrefix(val.Name, "__") {
  1205  			if d := generateDescription(val.Description); d != "" {
  1206  				x.Check2(sch.WriteString(fmt.Sprintf("\t%s", d)))
  1207  			}
  1208  			x.Check2(sch.WriteString(fmt.Sprintf("\t%s\n", val.Name)))
  1209  		}
  1210  	}
  1211  	x.Check2(sch.WriteString("}\n"))
  1212  
  1213  	return sch.String()
  1214  }
  1215  
  1216  func generateDescription(description string) string {
  1217  	if description == "" {
  1218  		return ""
  1219  	}
  1220  
  1221  	return fmt.Sprintf("\"\"\"%s\"\"\"\n", description)
  1222  }
  1223  
  1224  func generateInterfaceString(typ *ast.Definition) string {
  1225  	return fmt.Sprintf("%sinterface %s%s {\n%s}\n",
  1226  		generateDescription(typ.Description), typ.Name, genDirectivesString(typ.Directives),
  1227  		genFieldsString(typ.Fields))
  1228  }
  1229  
  1230  func generateObjectString(typ *ast.Definition) string {
  1231  	if len(typ.Interfaces) > 0 {
  1232  		interfaces := strings.Join(typ.Interfaces, " & ")
  1233  		return fmt.Sprintf("%stype %s implements %s%s {\n%s}\n",
  1234  			generateDescription(typ.Description), typ.Name, interfaces,
  1235  			genDirectivesString(typ.Directives), genFieldsString(typ.Fields))
  1236  	}
  1237  	return fmt.Sprintf("%stype %s%s {\n%s}\n",
  1238  		generateDescription(typ.Description), typ.Name, genDirectivesString(typ.Directives),
  1239  		genFieldsString(typ.Fields))
  1240  }
  1241  
  1242  // Stringify the schema as a GraphQL SDL string.  It's assumed that the schema was
  1243  // built by completeSchema, and so contains an original set of definitions, the
  1244  // definitions from schemaExtras and generated types, queries and mutations.
  1245  //
  1246  // Any types in originalTypes are printed first, followed by the schemaExtras,
  1247  // and then all generated types, scalars, enums, directives, query and
  1248  // mutations all in alphabetical order.
  1249  func Stringify(schema *ast.Schema, originalTypes []string) string {
  1250  	var sch, original, object, input, enum strings.Builder
  1251  
  1252  	if schema.Types == nil {
  1253  		return ""
  1254  	}
  1255  
  1256  	printed := make(map[string]bool)
  1257  
  1258  	// original defs can only be types and enums, print those in the same order
  1259  	// as the original schema.
  1260  	for _, typName := range originalTypes {
  1261  		typ := schema.Types[typName]
  1262  		switch typ.Kind {
  1263  		case ast.Interface:
  1264  			x.Check2(original.WriteString(generateInterfaceString(typ) + "\n"))
  1265  		case ast.Object:
  1266  			x.Check2(original.WriteString(generateObjectString(typ) + "\n"))
  1267  		case ast.Enum:
  1268  			x.Check2(original.WriteString(generateEnumString(typ) + "\n"))
  1269  		}
  1270  		printed[typName] = true
  1271  	}
  1272  
  1273  	// schemaExtras gets added to the result as a string, but we need to mark
  1274  	// off all it's contents as printed, so nothing in there gets printed with
  1275  	// the generated definitions.
  1276  	docExtras, gqlErr := parser.ParseSchema(&ast.Source{Input: schemaExtras})
  1277  	if gqlErr != nil {
  1278  		panic(gqlErr)
  1279  	}
  1280  	for _, defn := range docExtras.Definitions {
  1281  		printed[defn.Name] = true
  1282  	}
  1283  
  1284  	// schema.Types is all type names (types, inputs, enums, etc.).
  1285  	// The original schema defs have already been printed, and everything in
  1286  	// schemaExtras is marked as printed.  So build typeNames as anything
  1287  	// left to be printed.
  1288  	typeNames := make([]string, 0, len(schema.Types)-len(printed))
  1289  	for typName, typDef := range schema.Types {
  1290  		if typDef.BuiltIn {
  1291  			// These are the types that are coming from ast.Prelude
  1292  			continue
  1293  		}
  1294  		if !printed[typName] {
  1295  			typeNames = append(typeNames, typName)
  1296  		}
  1297  	}
  1298  	sort.Strings(typeNames)
  1299  
  1300  	// Now consider the types generated by completeSchema, which can only be
  1301  	// types, inputs and enums
  1302  	for _, typName := range typeNames {
  1303  		typ := schema.Types[typName]
  1304  		switch typ.Kind {
  1305  		case ast.Object:
  1306  			x.Check2(object.WriteString(generateObjectString(typ) + "\n"))
  1307  		case ast.InputObject:
  1308  			x.Check2(input.WriteString(generateInputString(typ) + "\n"))
  1309  		case ast.Enum:
  1310  			x.Check2(enum.WriteString(generateEnumString(typ) + "\n"))
  1311  		}
  1312  	}
  1313  
  1314  	x.Check2(sch.WriteString(
  1315  		"#######################\n# Input Schema\n#######################\n\n"))
  1316  	x.Check2(sch.WriteString(original.String()))
  1317  	x.Check2(sch.WriteString(
  1318  		"#######################\n# Extended Definitions\n#######################\n"))
  1319  	x.Check2(sch.WriteString(schemaExtras))
  1320  	x.Check2(sch.WriteString("\n"))
  1321  	x.Check2(sch.WriteString(
  1322  		"#######################\n# Generated Types\n#######################\n\n"))
  1323  	x.Check2(sch.WriteString(object.String()))
  1324  	x.Check2(sch.WriteString(
  1325  		"#######################\n# Generated Enums\n#######################\n\n"))
  1326  	x.Check2(sch.WriteString(enum.String()))
  1327  	x.Check2(sch.WriteString(
  1328  		"#######################\n# Generated Inputs\n#######################\n\n"))
  1329  	x.Check2(sch.WriteString(input.String()))
  1330  	x.Check2(sch.WriteString(
  1331  		"#######################\n# Generated Query\n#######################\n\n"))
  1332  	x.Check2(sch.WriteString(generateObjectString(schema.Query) + "\n"))
  1333  	x.Check2(sch.WriteString(
  1334  		"#######################\n# Generated Mutations\n#######################\n\n"))
  1335  	x.Check2(sch.WriteString(generateObjectString(schema.Mutation)))
  1336  
  1337  	return sch.String()
  1338  }
  1339  
  1340  func isIDField(defn *ast.Definition, fld *ast.FieldDefinition) bool {
  1341  	return fld.Type.Name() == idTypeFor(defn)
  1342  }
  1343  
  1344  func idTypeFor(defn *ast.Definition) string {
  1345  	return "ID"
  1346  }
  1347  
  1348  func xidTypeFor(defn *ast.Definition) string {
  1349  	for _, fld := range defn.Fields {
  1350  		if hasIDDirective(fld) {
  1351  			return fld.Name
  1352  		}
  1353  	}
  1354  	return ""
  1355  }
  1356  
  1357  func appendIfNotNull(errs []*gqlerror.Error, err *gqlerror.Error) gqlerror.List {
  1358  	if err != nil {
  1359  		errs = append(errs, err)
  1360  	}
  1361  
  1362  	return errs
  1363  }