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

     1  package schema
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"strconv"
     8  
     9  	"github.com/99designs/gqlgen/graphql"
    10  	"github.com/99designs/gqlgen/graphql/introspection"
    11  	"github.com/dgraph-io/dgraph/x"
    12  	"github.com/vektah/gqlparser/ast"
    13  )
    14  
    15  // Introspection works by walking through the selection set which are part of ast.Operation
    16  // and populating values for different fields. We have a dependency on gqlgen packages because
    17  // a) they define some useful types like introspection.Type, introspection.InputValue,
    18  // introspection.Directive etc.
    19  // b) CollectFields function which can recursively expand fragments and convert them to fields
    20  // and selection sets.
    21  // We might be able to get rid of this dependency in the future as we support fragments in other
    22  // queries or we might get rid of the types defined in wrappers.go and use the types defined in
    23  // gqlgen instead if they make more sense.
    24  
    25  // Introspect performs an introspection query given a query that's expected to be either
    26  // __schema or __type.
    27  func Introspect(q Query) (json.RawMessage, error) {
    28  	if q.Name() != "__schema" && q.Name() != "__type" {
    29  		return nil, errors.New("call to introspect for field that isn't an introspection query " +
    30  			"this indicates bug (Please let us know : https://github.com/dgraph-io/dgraph/issues)")
    31  	}
    32  
    33  	sch, ok := q.Operation().Schema().(*schema)
    34  	if !ok {
    35  		return nil, errors.New("couldn't convert schema to internal type " +
    36  			"this indicates bug (Please let us know : https://github.com/dgraph-io/dgraph/issues)")
    37  	}
    38  
    39  	op, ok := q.Operation().(*operation)
    40  	if !ok {
    41  		return nil, errors.New("couldn't convert operation to internal type " +
    42  			"this indicates bug (Please let us know : https://github.com/dgraph-io/dgraph/issues)")
    43  	}
    44  
    45  	qu, ok := q.(*query)
    46  	if !ok {
    47  		return nil, errors.New("couldn't convert query to internal type " +
    48  			"this indicates bug (Please let us know : https://github.com/dgraph-io/dgraph/issues)")
    49  	}
    50  
    51  	reqCtx := &requestContext{
    52  		RawQuery:  op.query,
    53  		Variables: op.vars,
    54  		Doc:       op.doc,
    55  	}
    56  	ec := executionContext{reqCtx, sch.schema, new(bytes.Buffer)}
    57  	return ec.handleQuery(qu.sel), nil
    58  }
    59  
    60  type requestContext struct {
    61  	RawQuery  string
    62  	Variables map[string]interface{}
    63  	Doc       *ast.QueryDocument
    64  }
    65  
    66  type executionContext struct {
    67  	*requestContext
    68  	*ast.Schema
    69  	b *bytes.Buffer // we build the JSON response and write it to b.
    70  }
    71  
    72  func (ec *executionContext) writeKey(k string) {
    73  	x.Check2(ec.b.WriteRune('"'))
    74  	x.Check2(ec.b.WriteString(k))
    75  	x.Check2(ec.b.WriteRune('"'))
    76  	x.Check2(ec.b.WriteRune(':'))
    77  }
    78  
    79  func (ec *executionContext) writeBoolValue(val bool) {
    80  	if val {
    81  		x.Check2(ec.b.WriteString("true"))
    82  	} else {
    83  		x.Check2(ec.b.WriteString("false"))
    84  	}
    85  }
    86  
    87  func (ec *executionContext) writeStringValue(val string) {
    88  	x.Check2(ec.b.WriteString(strconv.Quote(val)))
    89  }
    90  
    91  func (ec *executionContext) writeOptionalStringValue(val *string) {
    92  	if val == nil {
    93  		x.Check2(ec.b.WriteString("null"))
    94  	} else {
    95  		ec.writeStringValue(*val)
    96  	}
    97  }
    98  
    99  func (ec *executionContext) writeStringSlice(v []string) {
   100  	x.Check2(ec.b.WriteRune('['))
   101  	for i := range v {
   102  		if i != 0 {
   103  			x.Check2(ec.b.WriteRune(','))
   104  		}
   105  		ec.writeStringValue(v[i])
   106  	}
   107  	x.Check2(ec.b.WriteRune(']'))
   108  }
   109  
   110  // collectFields is our wrapper around graphql.CollectFields which is able to build a tree (after
   111  // expanding fragments) represented by []graphql.CollectorField. It requires passing the
   112  // graphql.requestContext to work correctly.
   113  func collectFields(reqCtx *requestContext, selSet ast.SelectionSet,
   114  	satisfies []string) []graphql.CollectedField {
   115  	ctx := &graphql.RequestContext{
   116  		RawQuery:  reqCtx.RawQuery,
   117  		Variables: reqCtx.Variables,
   118  		Doc:       reqCtx.Doc,
   119  	}
   120  	return graphql.CollectFields(ctx, selSet, satisfies)
   121  }
   122  
   123  func (ec *executionContext) queryType(field graphql.CollectedField) {
   124  	args := field.ArgumentMap(ec.Variables)
   125  	name := args["name"].(string)
   126  	res := introspection.WrapTypeFromDef(ec.Schema, ec.Schema.Types[name])
   127  	ec.marshalType(field.Selections, res)
   128  }
   129  
   130  func (ec *executionContext) querySchema(field graphql.CollectedField) {
   131  	res := introspection.WrapSchema(ec.Schema)
   132  	if res == nil {
   133  		return
   134  	}
   135  	ec.handleSchema(field.Selections, res)
   136  }
   137  
   138  func (ec *executionContext) handleTypeFields(field graphql.CollectedField,
   139  	obj *introspection.Type) {
   140  	args := field.ArgumentMap(ec.Variables)
   141  	res := obj.Fields(args["includeDeprecated"].(bool))
   142  	ec.marshalIntrospectionFieldSlice(field.Selections, res)
   143  }
   144  
   145  func (ec *executionContext) handleTypeEnumValues(field graphql.CollectedField,
   146  	obj *introspection.Type) {
   147  	args := field.ArgumentMap(ec.Variables)
   148  	res := obj.EnumValues(args["includeDeprecated"].(bool))
   149  	if res == nil {
   150  		// TODO - Verify we handle types that can/cannot be null properly. Also add test cases for
   151  		// them.
   152  		return
   153  	}
   154  	ec.marshalOptionalEnumValueSlice(field.Selections, res)
   155  }
   156  
   157  func (ec *executionContext) handleQuery(sel ast.Selection) []byte {
   158  	fields := collectFields(ec.requestContext, ast.SelectionSet{sel}, []string{"Query"})
   159  
   160  	x.Check2(ec.b.WriteRune('{'))
   161  	for i, field := range fields {
   162  		if i != 0 {
   163  			x.Check2(ec.b.WriteRune(','))
   164  		}
   165  		ec.writeKey(field.Alias)
   166  		switch field.Name {
   167  		// TODO - Add tests for __typename.
   168  		case Typename:
   169  			x.Check2(ec.b.WriteString(`"Query"`))
   170  		case "__type":
   171  			ec.queryType(field)
   172  		case "__schema":
   173  			ec.querySchema(field)
   174  		default:
   175  		}
   176  	}
   177  	x.Check2(ec.b.WriteRune('}'))
   178  	return ec.b.Bytes()
   179  }
   180  
   181  func (ec *executionContext) handleDirective(sel ast.SelectionSet, obj *introspection.Directive) {
   182  	fields := collectFields(ec.requestContext, sel, []string{"__Directive"})
   183  
   184  	x.Check2(ec.b.WriteRune('{'))
   185  	for i, field := range fields {
   186  		if i != 0 {
   187  			x.Check2(ec.b.WriteRune(','))
   188  		}
   189  		ec.writeKey(field.Alias)
   190  		switch field.Name {
   191  		case Typename:
   192  			x.Check2(ec.b.WriteString(`"__Directive"`))
   193  		case "name":
   194  			ec.writeStringValue(obj.Name)
   195  		case "description":
   196  			ec.writeStringValue(obj.Description)
   197  		case "locations":
   198  			ec.writeStringSlice(obj.Locations)
   199  		case "args":
   200  			ec.marshalInputValueSlice(field.Selections, obj.Args)
   201  		default:
   202  		}
   203  	}
   204  	x.Check2(ec.b.WriteRune('}'))
   205  }
   206  
   207  func (ec *executionContext) handleEnumValue(sel ast.SelectionSet, obj *introspection.EnumValue) {
   208  	fields := collectFields(ec.requestContext, sel, []string{"__EnumValue"})
   209  
   210  	x.Check2(ec.b.WriteRune('{'))
   211  	for i, field := range fields {
   212  		if i != 0 {
   213  			x.Check2(ec.b.WriteRune(','))
   214  		}
   215  		ec.writeKey(field.Name)
   216  		switch field.Name {
   217  		case Typename:
   218  			ec.writeStringValue("__EnumValue")
   219  		case "name":
   220  			ec.writeStringValue(obj.Name)
   221  		case "description":
   222  			ec.writeStringValue(obj.Description)
   223  		case "isDeprecated":
   224  			ec.writeBoolValue(obj.IsDeprecated())
   225  		case "deprecationReason":
   226  			ec.writeOptionalStringValue(obj.DeprecationReason())
   227  		default:
   228  		}
   229  	}
   230  	x.Check2(ec.b.WriteRune('}'))
   231  }
   232  
   233  func (ec *executionContext) handleField(sel ast.SelectionSet, obj *introspection.Field) {
   234  	fields := collectFields(ec.requestContext, sel, []string{"__Field"})
   235  
   236  	x.Check2(ec.b.WriteRune('{'))
   237  	for i, field := range fields {
   238  		if i != 0 {
   239  			x.Check2(ec.b.WriteRune(','))
   240  		}
   241  		ec.writeKey(field.Alias)
   242  		switch field.Name {
   243  		case Typename:
   244  			ec.writeStringValue("__Field")
   245  		case "name":
   246  			ec.writeStringValue(obj.Name)
   247  		case "description":
   248  			ec.writeStringValue(obj.Description)
   249  		case "args":
   250  			ec.marshalInputValueSlice(field.Selections, obj.Args)
   251  		case "type":
   252  			ec.marshalIntrospectionType(field.Selections, obj.Type)
   253  		case "isDeprecated":
   254  			ec.writeBoolValue(obj.IsDeprecated())
   255  		case "deprecationReason":
   256  			ec.writeOptionalStringValue(obj.DeprecationReason())
   257  		default:
   258  		}
   259  	}
   260  	x.Check2(ec.b.WriteRune('}'))
   261  }
   262  
   263  func (ec *executionContext) handleInputValue(sel ast.SelectionSet, obj *introspection.InputValue) {
   264  	fields := collectFields(ec.requestContext, sel, []string{"__InputValue"})
   265  
   266  	x.Check2(ec.b.WriteRune('{'))
   267  	for i, field := range fields {
   268  		if i != 0 {
   269  			x.Check2(ec.b.WriteRune(','))
   270  		}
   271  		ec.writeKey(field.Alias)
   272  		switch field.Name {
   273  		case Typename:
   274  			ec.writeStringValue("__InputValue")
   275  		case "name":
   276  			ec.writeStringValue(obj.Name)
   277  		case "description":
   278  			ec.writeStringValue(obj.Description)
   279  		case "type":
   280  			ec.marshalIntrospectionType(field.Selections, obj.Type)
   281  		case "defaultValue":
   282  			ec.writeOptionalStringValue(obj.DefaultValue)
   283  		default:
   284  		}
   285  	}
   286  	x.Check2(ec.b.WriteRune('}'))
   287  }
   288  
   289  func (ec *executionContext) handleSchema(sel ast.SelectionSet, obj *introspection.Schema) {
   290  	fields := collectFields(ec.requestContext, sel, []string{"__Schema"})
   291  
   292  	x.Check2(ec.b.WriteRune('{'))
   293  	for i, field := range fields {
   294  		if i != 0 {
   295  			x.Check2(ec.b.WriteRune(','))
   296  		}
   297  		ec.writeKey(field.Name)
   298  		switch field.Name {
   299  		case Typename:
   300  			ec.writeStringValue("__Schema")
   301  		case "types":
   302  			ec.marshalIntrospectionTypeSlice(field.Selections, obj.Types())
   303  		case "queryType":
   304  			ec.marshalIntrospectionType(field.Selections, obj.QueryType())
   305  		case "mutationType":
   306  			ec.marshalType(field.Selections, obj.MutationType())
   307  		case "subscriptionType":
   308  			ec.marshalType(field.Selections, obj.SubscriptionType())
   309  		case "directives":
   310  			ec.marshalDirectiveSlice(field.Selections, obj.Directives())
   311  		default:
   312  		}
   313  	}
   314  	x.Check2(ec.b.WriteRune('}'))
   315  }
   316  
   317  func (ec *executionContext) handleType(sel ast.SelectionSet, obj *introspection.Type) {
   318  	fields := collectFields(ec.requestContext, sel, []string{"__Type"})
   319  
   320  	x.Check2(ec.b.WriteRune('{'))
   321  	for i, field := range fields {
   322  		if i != 0 {
   323  			x.Check2(ec.b.WriteRune(','))
   324  		}
   325  		ec.writeKey(field.Alias)
   326  		switch field.Name {
   327  		case Typename:
   328  			x.Check2(ec.b.WriteString(`"__Type`))
   329  		case "kind":
   330  			ec.writeStringValue(obj.Kind())
   331  		case "name":
   332  			ec.writeOptionalStringValue(obj.Name())
   333  		case "description":
   334  			ec.writeStringValue(obj.Description())
   335  		case "fields":
   336  			ec.handleTypeFields(field, obj)
   337  		case "interfaces":
   338  			ec.marshalOptionalItypeSlice(field.Selections, obj.Interfaces())
   339  		case "possibleTypes":
   340  			ec.marshalOptionalItypeSlice(field.Selections, obj.PossibleTypes())
   341  		case "enumValues":
   342  			ec.handleTypeEnumValues(field, obj)
   343  		case "inputFields":
   344  			ec.marshalOptionalInputValueSlice(field.Selections, obj.InputFields())
   345  		case "ofType":
   346  			ec.marshalType(field.Selections, obj.OfType())
   347  		default:
   348  		}
   349  	}
   350  	x.Check2(ec.b.WriteRune('}'))
   351  }
   352  
   353  func (ec *executionContext) marshalDirectiveSlice(sel ast.SelectionSet,
   354  	v []introspection.Directive) {
   355  	x.Check2(ec.b.WriteRune('['))
   356  	for i := range v {
   357  		if i != 0 {
   358  			x.Check2(ec.b.WriteRune(','))
   359  		}
   360  		ec.handleDirective(sel, &v[i])
   361  	}
   362  	x.Check2(ec.b.WriteRune(']'))
   363  }
   364  
   365  func (ec *executionContext) marshalInputValueSlice(sel ast.SelectionSet,
   366  	v []introspection.InputValue) {
   367  	x.Check2(ec.b.WriteRune('['))
   368  	for i := range v {
   369  		if i != 0 {
   370  			x.Check2(ec.b.WriteRune(','))
   371  		}
   372  		ec.handleInputValue(sel, &v[i])
   373  	}
   374  	x.Check2(ec.b.WriteRune(']'))
   375  }
   376  
   377  func (ec *executionContext) marshalIntrospectionTypeSlice(sel ast.SelectionSet,
   378  	v []introspection.Type) {
   379  	x.Check2(ec.b.WriteRune('['))
   380  	for i := range v {
   381  		if i != 0 {
   382  			x.Check2(ec.b.WriteRune(','))
   383  		}
   384  		ec.handleType(sel, &v[i])
   385  	}
   386  	x.Check2(ec.b.WriteRune(']'))
   387  }
   388  
   389  func (ec *executionContext) marshalIntrospectionType(sel ast.SelectionSet, v *introspection.Type) {
   390  	if v == nil {
   391  		// TODO - This should be an error as this field is mandatory.
   392  		x.Check2(ec.b.WriteString("null"))
   393  		return
   394  	}
   395  	ec.handleType(sel, v)
   396  }
   397  
   398  func (ec *executionContext) marshalOptionalEnumValueSlice(sel ast.SelectionSet,
   399  	v []introspection.EnumValue) {
   400  	if v == nil {
   401  		x.Check2(ec.b.WriteString("null"))
   402  		return
   403  	}
   404  	x.Check2(ec.b.WriteRune('['))
   405  	for i := range v {
   406  		if i != 0 {
   407  			x.Check2(ec.b.WriteRune(','))
   408  		}
   409  		ec.handleEnumValue(sel, &v[i])
   410  	}
   411  	x.Check2(ec.b.WriteRune(']'))
   412  }
   413  
   414  func (ec *executionContext) marshalIntrospectionFieldSlice(sel ast.SelectionSet,
   415  	v []introspection.Field) {
   416  	if v == nil {
   417  		x.Check2(ec.b.WriteString("null"))
   418  		return
   419  	}
   420  	x.Check2(ec.b.WriteRune('['))
   421  	for i := range v {
   422  		if i != 0 {
   423  			x.Check2(ec.b.WriteRune(','))
   424  		}
   425  		ec.handleField(sel, &v[i])
   426  	}
   427  	x.Check2(ec.b.WriteRune(']'))
   428  }
   429  
   430  func (ec *executionContext) marshalOptionalInputValueSlice(sel ast.SelectionSet,
   431  	v []introspection.InputValue) {
   432  	if v == nil {
   433  		x.Check2(ec.b.WriteString(`null`))
   434  		return
   435  	}
   436  	x.Check2(ec.b.WriteRune('['))
   437  	for i := range v {
   438  		if i != 0 {
   439  			x.Check2(ec.b.WriteRune(','))
   440  		}
   441  		ec.handleInputValue(sel, &v[i])
   442  	}
   443  	x.Check2(ec.b.WriteRune(']'))
   444  }
   445  
   446  func (ec *executionContext) marshalOptionalItypeSlice(sel ast.SelectionSet,
   447  	v []introspection.Type) {
   448  	if v == nil {
   449  		x.Check2(ec.b.WriteString("null"))
   450  		return
   451  	}
   452  
   453  	x.Check2(ec.b.WriteRune('['))
   454  	for i := range v {
   455  		if i != 0 {
   456  			x.Check2(ec.b.WriteRune(','))
   457  		}
   458  		ec.handleType(sel, &v[i])
   459  	}
   460  	x.Check2(ec.b.WriteRune(']'))
   461  }
   462  
   463  func (ec *executionContext) marshalType(sel ast.SelectionSet, v *introspection.Type) {
   464  	if v == nil {
   465  		x.Check2(ec.b.WriteString("null"))
   466  		return
   467  	}
   468  	ec.handleType(sel, v)
   469  }