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 }