github.com/dgraph-io/dgraph@v1.2.8/graphql/resolve/resolver.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 resolve 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "sync" 24 25 "github.com/dgraph-io/dgraph/graphql/dgraph" 26 27 dgoapi "github.com/dgraph-io/dgo/v2/protos/api" 28 "github.com/dgraph-io/dgraph/gql" 29 "github.com/dgraph-io/dgraph/graphql/api" 30 "github.com/dgraph-io/dgraph/x" 31 "github.com/pkg/errors" 32 "go.opencensus.io/trace" 33 otrace "go.opencensus.io/trace" 34 35 "github.com/golang/glog" 36 37 "github.com/dgraph-io/dgraph/graphql/schema" 38 ) 39 40 const ( 41 methodResolve = "RequestResolver.Resolve" 42 43 resolverFailed = false 44 resolverSucceeded = true 45 ) 46 47 // A ResolverFactory finds the right resolver for a query/mutation. 48 type ResolverFactory interface { 49 queryResolverFor(query schema.Query) QueryResolver 50 mutationResolverFor(mutation schema.Mutation) MutationResolver 51 52 // WithQueryResolver adds a new query resolver. Each time query name is resolved 53 // resolver is called to create a new instane of a QueryResolver to resolve the 54 // query. 55 WithQueryResolver(name string, resolver func(schema.Query) QueryResolver) ResolverFactory 56 57 // WithMutationResolver adds a new query resolver. Each time mutation name is resolved 58 // resolver is called to create a new instane of a MutationResolver to resolve the 59 // mutation. 60 WithMutationResolver( 61 name string, resolver func(schema.Mutation) MutationResolver) ResolverFactory 62 63 // WithConventionResolvers adds a set of our convention based resolvers to the 64 // factory. The registration happens only once. 65 WithConventionResolvers(s schema.Schema, fns *ResolverFns) ResolverFactory 66 67 // WithSchemaIntrospection adds schema introspection capabilities to the factory. 68 // So __schema and __type queries can be resolved. 69 WithSchemaIntrospection() ResolverFactory 70 } 71 72 // A ResultCompleter can take a []byte slice representing an intermediate result 73 // in resolving field and applies a completion step - for example, apply GraphQL 74 // error propagation or massaging error paths. 75 type ResultCompleter interface { 76 Complete(ctx context.Context, field schema.Field, result []byte, err error) ([]byte, error) 77 } 78 79 // RequestResolver can process GraphQL requests and write GraphQL JSON responses. 80 type RequestResolver struct { 81 schema schema.Schema 82 resolvers ResolverFactory 83 } 84 85 // A resolverFactory is the main implementation of ResolverFactory. It stores a 86 // map of all the resolvers that have been registered and returns a resolver that 87 // just returns errors if it's asked for a resolver for a field that it doesn't 88 // know about. 89 type resolverFactory struct { 90 queryResolvers map[string]func(schema.Query) QueryResolver 91 mutationResolvers map[string]func(schema.Mutation) MutationResolver 92 93 // returned if the factory gets asked for resolver for a field that it doesn't 94 // know about. 95 queryError QueryResolverFunc 96 mutationError MutationResolverFunc 97 } 98 99 // ResolverFns is a convenience struct for passing blocks of rewriters and executors. 100 type ResolverFns struct { 101 Qrw QueryRewriter 102 Arw func() MutationRewriter 103 Urw func() MutationRewriter 104 Drw MutationRewriter 105 Qe QueryExecutor 106 Me MutationExecutor 107 } 108 109 // dgraphExecutor is an implementation of both QueryExecutor and MutationExecutor 110 // that proxies query/mutation resolution through Query method in dgraph server. 111 type dgraphExecutor struct { 112 } 113 114 // A Resolved is the result of resolving a single query or mutation. 115 // A schema.Request may contain any number of queries or mutations (never both). 116 // RequestResolver.Resolve() resolves all of them by finding the resolved answers 117 // of the component queries/mutations and joining into a single schema.Response. 118 type Resolved struct { 119 Data []byte 120 Err error 121 } 122 123 // CompletionFunc is an adapter that allows us to compose completions and build a 124 // ResultCompleter from a function. Based on the http.HandlerFunc pattern. 125 type CompletionFunc func( 126 ctx context.Context, field schema.Field, result []byte, err error) ([]byte, error) 127 128 // Complete calls cf(ctx, field, result, err) 129 func (cf CompletionFunc) Complete( 130 ctx context.Context, 131 field schema.Field, 132 result []byte, 133 err error) ([]byte, error) { 134 135 return cf(ctx, field, result, err) 136 } 137 138 // DgraphAsQueryExecutor builds a QueryExecutor for proxying requests through dgraph. 139 func DgraphAsQueryExecutor() QueryExecutor { 140 return &dgraphExecutor{} 141 } 142 143 // DgraphAsMutationExecutor builds a MutationExecutor for dog. 144 func DgraphAsMutationExecutor() MutationExecutor { 145 return &dgraphExecutor{} 146 } 147 148 func (de *dgraphExecutor) Query(ctx context.Context, query *gql.GraphQuery) ([]byte, error) { 149 return dgraph.Query(ctx, query) 150 } 151 152 func (de *dgraphExecutor) Mutate( 153 ctx context.Context, 154 query *gql.GraphQuery, 155 mutations []*dgoapi.Mutation) (map[string]string, map[string]interface{}, error) { 156 return dgraph.Mutate(ctx, query, mutations) 157 } 158 159 func (rf *resolverFactory) WithQueryResolver( 160 name string, resolver func(schema.Query) QueryResolver) ResolverFactory { 161 rf.queryResolvers[name] = resolver 162 return rf 163 } 164 165 func (rf *resolverFactory) WithMutationResolver( 166 name string, resolver func(schema.Mutation) MutationResolver) ResolverFactory { 167 rf.mutationResolvers[name] = resolver 168 return rf 169 } 170 171 func (rf *resolverFactory) WithSchemaIntrospection() ResolverFactory { 172 introspect := func(q schema.Query) QueryResolver { 173 return &queryResolver{ 174 queryRewriter: NoOpQueryRewrite(), 175 queryExecutor: introspectionExecution(q), 176 resultCompleter: removeObjectCompletion(noopCompletion), 177 } 178 } 179 180 rf.WithQueryResolver("__schema", introspect) 181 rf.WithQueryResolver("__type", introspect) 182 183 return rf 184 } 185 186 func (rf *resolverFactory) WithConventionResolvers( 187 s schema.Schema, fns *ResolverFns) ResolverFactory { 188 189 queries := append(s.Queries(schema.GetQuery), s.Queries(schema.FilterQuery)...) 190 for _, q := range queries { 191 rf.WithQueryResolver(q, func(q schema.Query) QueryResolver { 192 return NewQueryResolver(fns.Qrw, fns.Qe, StdQueryCompletion()) 193 }) 194 } 195 196 for _, m := range s.Mutations(schema.AddMutation) { 197 rf.WithMutationResolver(m, func(m schema.Mutation) MutationResolver { 198 return NewMutationResolver( 199 fns.Arw(), fns.Qe, fns.Me, StdMutationCompletion(m.ResponseName())) 200 }) 201 } 202 203 for _, m := range s.Mutations(schema.UpdateMutation) { 204 rf.WithMutationResolver(m, func(m schema.Mutation) MutationResolver { 205 return NewMutationResolver( 206 fns.Urw(), fns.Qe, fns.Me, StdMutationCompletion(m.ResponseName())) 207 }) 208 } 209 210 for _, m := range s.Mutations(schema.DeleteMutation) { 211 rf.WithMutationResolver(m, func(m schema.Mutation) MutationResolver { 212 return NewMutationResolver( 213 fns.Drw, NoOpQueryExecution(), fns.Me, StdDeleteCompletion(m.ResponseName())) 214 }) 215 } 216 217 return rf 218 } 219 220 // NewResolverFactory returns a ResolverFactory that resolves requests via 221 // query/mutation rewriting and execution through Dgraph. If the factory gets asked 222 // to resolve a query/mutation it doesn't know how to rewrite, it uses 223 // the queryError/mutationError to build an error result. 224 func NewResolverFactory( 225 queryError QueryResolverFunc, mutationError MutationResolverFunc) ResolverFactory { 226 227 return &resolverFactory{ 228 queryResolvers: make(map[string]func(schema.Query) QueryResolver), 229 mutationResolvers: make(map[string]func(schema.Mutation) MutationResolver), 230 231 queryError: queryError, 232 mutationError: mutationError, 233 } 234 } 235 236 // StdQueryCompletion is the completion steps that get run for queries 237 func StdQueryCompletion() CompletionFunc { 238 return removeObjectCompletion(completeDgraphResult) 239 } 240 241 // StdMutationCompletion is the completion steps that get run for add and update mutations 242 func StdMutationCompletion(name string) CompletionFunc { 243 return addPathCompletion(name, addRootFieldCompletion(name, completeDgraphResult)) 244 } 245 246 // StdDeleteCompletion is the completion steps that get run for add and update mutations 247 func StdDeleteCompletion(name string) CompletionFunc { 248 return addPathCompletion(name, addRootFieldCompletion(name, deleteCompletion())) 249 } 250 251 func (rf *resolverFactory) queryResolverFor(query schema.Query) QueryResolver { 252 if resolver, ok := rf.queryResolvers[query.Name()]; ok { 253 return resolver(query) 254 } 255 256 return rf.queryError 257 } 258 259 func (rf *resolverFactory) mutationResolverFor(mutation schema.Mutation) MutationResolver { 260 if resolver, ok := rf.mutationResolvers[mutation.Name()]; ok { 261 return resolver(mutation) 262 } 263 264 return rf.mutationError 265 } 266 267 // New creates a new RequestResolver. 268 func New(s schema.Schema, resolverFactory ResolverFactory) *RequestResolver { 269 return &RequestResolver{ 270 schema: s, 271 resolvers: resolverFactory, 272 } 273 } 274 275 // Resolve processes r.GqlReq and returns a GraphQL response. 276 // r.GqlReq should be set with a request before Resolve is called 277 // and a schema and backend Dgraph should have been added. 278 // Resolve records any errors in the response's error field. 279 func (r *RequestResolver) Resolve(ctx context.Context, gqlReq *schema.Request) *schema.Response { 280 span := otrace.FromContext(ctx) 281 stop := x.SpanTimer(span, methodResolve) 282 defer stop() 283 284 reqID := api.RequestID(ctx) 285 286 if r == nil { 287 glog.Errorf("[%s] Call to Resolve with nil RequestResolver", reqID) 288 return schema.ErrorResponse(errors.New("Internal error"), reqID) 289 } 290 291 if r.schema == nil { 292 glog.Errorf("[%s] Call to Resolve with no schema", reqID) 293 return schema.ErrorResponse(errors.New("Internal error"), reqID) 294 } 295 296 op, err := r.schema.Operation(gqlReq) 297 if err != nil { 298 return schema.ErrorResponse(err, reqID) 299 } 300 301 resp := &schema.Response{ 302 Extensions: &schema.Extensions{ 303 RequestID: reqID, 304 }, 305 } 306 307 if glog.V(3) { 308 b, err := json.Marshal(gqlReq.Variables) 309 if err != nil { 310 glog.Infof("Failed to marshal variables for logging : %s", err) 311 } 312 glog.Infof("[%s] Resolving GQL request: \n%s\nWith Variables: \n%s\n", 313 reqID, gqlReq.Query, string(b)) 314 } 315 316 // A single request can contain either queries or mutations - not both. 317 // GraphQL validation on the request would have caught that error case 318 // before we get here. At this point, we know it's valid, it's passed 319 // GraphQL validation and any additional validation we've added. So here, 320 // we can just execute it. 321 switch { 322 case op.IsQuery(): 323 // Queries run in parallel and are independent of each other: e.g. 324 // an error in one query, doesn't affect the others. 325 326 var wg sync.WaitGroup 327 allResolved := make([]*Resolved, len(op.Queries())) 328 329 for i, q := range op.Queries() { 330 wg.Add(1) 331 332 go func(q schema.Query, storeAt int) { 333 defer wg.Done() 334 defer api.PanicHandler(api.RequestID(ctx), 335 func(err error) { 336 allResolved[storeAt] = &Resolved{Err: err} 337 }) 338 339 allResolved[storeAt] = r.resolvers.queryResolverFor(q).Resolve(ctx, q) 340 }(q, i) 341 } 342 wg.Wait() 343 344 // The GraphQL data response needs to be written in the same order as the 345 // queries in the request. 346 for _, res := range allResolved { 347 // Errors and data in the same response is valid. Both WithError and 348 // AddData handle nil cases. 349 resp.WithError(res.Err) 350 resp.AddData(res.Data) 351 } 352 case op.IsMutation(): 353 // A mutation operation can contain any number of mutation fields. Those should be executed 354 // serially. 355 // (spec https://graphql.github.io/graphql-spec/June2018/#sec-Normal-and-Serial-Execution) 356 // 357 // The spec is ambiguous about what to do in the case of errors during that serial execution 358 // - apparently deliberately so; see this comment from Lee Byron: 359 // https://github.com/graphql/graphql-spec/issues/277#issuecomment-385588590 360 // and clarification 361 // https://github.com/graphql/graphql-spec/pull/438 362 // 363 // A reasonable interpretation of that is to stop a list of mutations after the first error - 364 // which seems like the natural semantics and is what we enforce here. 365 allSuccessful := true 366 367 for _, m := range op.Mutations() { 368 if !allSuccessful { 369 resp.WithError(x.GqlErrorf( 370 "Mutation %s was not executed because of a previous error.", 371 m.ResponseName()). 372 WithLocations(m.Location())) 373 374 continue 375 } 376 377 var res *Resolved 378 res, allSuccessful = r.resolvers.mutationResolverFor(m).Resolve(ctx, m) 379 resp.WithError(res.Err) 380 resp.AddData(res.Data) 381 } 382 case op.IsSubscription(): 383 resp.WithError(errors.Errorf("Subscriptions not yet supported.")) 384 } 385 386 return resp 387 } 388 389 // noopCompletion just passes back it's result and err arguments 390 func noopCompletion( 391 ctx context.Context, field schema.Field, result []byte, err error) ([]byte, error) { 392 return result, err 393 } 394 395 // removeObjectCompletion chops leading '{' and trailing '}' from a JSON object 396 // 397 // The final GraphQL result gets built like 398 // { data: 399 // { 400 // q1: {...}, 401 // q2: [ {...}, {...} ], 402 // ... 403 // } 404 // } 405 // 406 // When we are building a single one of the q's, the result is built initially as 407 // { q1: {...} } 408 // so the completed result should be 409 // q1: {...} 410 func removeObjectCompletion(cf CompletionFunc) CompletionFunc { 411 return CompletionFunc( 412 func(ctx context.Context, field schema.Field, result []byte, err error) ([]byte, error) { 413 res, err := cf(ctx, field, result, err) 414 if len(res) >= 2 { 415 res = res[1 : len(res)-1] 416 } 417 return res, err 418 }) 419 } 420 421 // addRootFieldCompletion adds an extra object name to the start of a result. 422 // 423 // A mutation always looks like 424 // `addFoo(...) { foo { ... } }` 425 // What's resolved initially is 426 // `foo { ... }` 427 // So `addFoo: ...` is added. 428 func addRootFieldCompletion(name string, cf CompletionFunc) CompletionFunc { 429 return CompletionFunc(func( 430 ctx context.Context, field schema.Field, result []byte, err error) ([]byte, error) { 431 432 res, err := cf(ctx, field, result, err) 433 434 var b bytes.Buffer 435 x.Check2(b.WriteString("\"")) 436 x.Check2(b.WriteString(name)) 437 x.Check2(b.WriteString(`": `)) 438 if len(res) > 0 { 439 x.Check2(b.Write(res)) 440 } else { 441 x.Check2(b.WriteString("null")) 442 } 443 444 return b.Bytes(), err 445 }) 446 } 447 448 // addPathCompletion adds an extra object name to the start of every error path 449 // arrising from applying cf. 450 // 451 // A mutation always looks like 452 // `addFoo(...) { foo { ... } }` 453 // But cf's error paths begin at `foo`, so `addFoo` needs to be added to all. 454 func addPathCompletion(name string, cf CompletionFunc) CompletionFunc { 455 return CompletionFunc(func( 456 ctx context.Context, field schema.Field, result []byte, err error) ([]byte, error) { 457 458 res, err := cf(ctx, field, result, err) 459 460 resErrs := schema.AsGQLErrors(err) 461 for _, err := range resErrs { 462 if len(err.Path) > 0 { 463 err.Path = append([]interface{}{name}, err.Path...) 464 } 465 } 466 467 return res, resErrs 468 }) 469 } 470 471 // Once a result has been returned from Dgraph, that result needs to be worked 472 // through for two main reasons: 473 // 474 // 1) (null insertion) 475 // Where an edge was requested in a query, but isn't in the store, Dgraph just 476 // won't return an edge for that in the results. But GraphQL wants those as 477 // "null" in the result. And then we need to inspect those nulls via pt (2) 478 // 479 // 2) (error propagation) 480 // The schema is a contract with consumers. So if there's an `f: T!` in the 481 // schema, that says: "this API never returns a null f". If f turned out null 482 // in the results, then returning null would break the contract. GraphQL specifies 483 // a set of rules about how to propagate and record those errors. 484 // 485 // The basic intuition is that if we asked for something that's nullable and we 486 // got back a null/error, then that's fine, just set it to null. But if we asked 487 // for something non-nullable and got a null/error, then the object we are building 488 // is in an error state, and we should propagate that up to it's parent, and so 489 // on, until we reach a nullable field, or the top level. 490 // 491 // The completeXYZ() functions below essentially covers the value completion alg from 492 // https://graphql.github.io/graphql-spec/June2018/#sec-Value-Completion. 493 // see also: error propagation 494 // https://graphql.github.io/graphql-spec/June2018/#sec-Errors-and-Non-Nullability 495 // and the spec requirements for response 496 // https://graphql.github.io/graphql-spec/June2018/#sec-Response. 497 // 498 // There's three basic types to consider here: GraphQL object types (equals json 499 // objects in the result), list types (equals lists of objects or scalars), and 500 // values (either scalar values, lists or objects). 501 // 502 // So the algorithm is a three way mutual recursion between those types. 503 // 504 // That works like this... if part of the json result from Dgraph 505 // looked like: 506 // 507 // { 508 // "name": "A name" 509 // "friends": [ 510 // { "name": "Friend 1"}, 511 // { "name": "Friend 2", "friends": [...] } 512 // ] 513 // } 514 // 515 // Then, schematically, the recursion tree would look like: 516 // 517 // completeObject ( { 518 // "name": completeValue("A name") 519 // "friends": completeValue( completeList([ 520 // completeValue (completeObject ({ "name": completeValue ("Friend 1")} )), 521 // completeValue (completeObject ({ 522 // "name": completeValue("Friend 2"), 523 // "friends": completeValue ( completeList([ completeObject(..), ..]) } ) 524 // 525 526 // completeDgraphResult starts the recursion with field as the top level GraphQL 527 // query and dgResult as the matching full Dgraph result. Always returns a valid 528 // JSON []byte of the form 529 // { "query-name": null } 530 // if there's no result, or 531 // { "query-name": ... } 532 // if there is a result. 533 // 534 // Returned errors are generally lists of errors resulting from the value completion 535 // algorithm that may emit multiple errors 536 func completeDgraphResult(ctx context.Context, field schema.Field, dgResult []byte, e error) ( 537 []byte, error) { 538 span := trace.FromContext(ctx) 539 stop := x.SpanTimer(span, "completeDgraphResult") 540 defer stop() 541 542 // We need an initial case in the alg because Dgraph always returns a list 543 // result no matter what. 544 // 545 // If the query was for a non-list type, that needs to be corrected: 546 // 547 // { "q":[{ ... }] } ---> { "q":{ ... } } 548 // 549 // Also, if the query found nothing at all, that needs correcting too: 550 // 551 // { } ---> { "q": null } 552 553 errs := schema.AsGQLErrors(e) 554 if len(dgResult) == 0 { 555 return nil, errs 556 } 557 558 nullResponse := func() []byte { 559 var buf bytes.Buffer 560 x.Check2(buf.WriteString(`{ "`)) 561 x.Check2(buf.WriteString(field.ResponseName())) 562 x.Check2(buf.WriteString(`": null }`)) 563 return buf.Bytes() 564 } 565 566 dgraphError := func() ([]byte, error) { 567 glog.Errorf("[%s] Could not process Dgraph result : \n%s", 568 api.RequestID(ctx), string(dgResult)) 569 return nullResponse(), 570 x.GqlErrorf("Couldn't process the result from Dgraph. " + 571 "This probably indicates a bug in the Dgraph GraphQL layer. " + 572 "Please let us know : https://github.com/dgraph-io/dgraph/issues."). 573 WithLocations(field.Location()) 574 } 575 576 // Dgraph should only return {} or a JSON object. Also, 577 // GQL type checking should ensure query results are only object types 578 // https://graphql.github.io/graphql-spec/June2018/#sec-Query 579 // So we are only building object results. 580 var valToComplete map[string]interface{} 581 err := json.Unmarshal(dgResult, &valToComplete) 582 if err != nil { 583 glog.Errorf("[%s] %+v \n Dgraph result :\n%s\n", 584 api.RequestID(ctx), 585 errors.Wrap(err, "failed to unmarshal Dgraph query result"), 586 string(dgResult)) 587 return nullResponse(), 588 schema.GQLWrapLocationf(err, field.Location(), "couldn't unmarshal Dgraph result") 589 } 590 591 switch val := valToComplete[field.ResponseName()].(type) { 592 case []interface{}: 593 if field.Type().ListType() == nil { 594 // Turn Dgraph list result to single object 595 // "q":[{ ... }] ---> "q":{ ... } 596 597 var internalVal interface{} 598 599 if len(val) > 0 { 600 var ok bool 601 if internalVal, ok = val[0].(map[string]interface{}); !ok { 602 // This really shouldn't happen. Dgraph only returns arrays 603 // of json objects. 604 return dgraphError() 605 } 606 } 607 608 if len(val) > 1 { 609 // If we get here, then we got a list result for a query that expected 610 // a single item. That probably indicates a schema error, or maybe 611 // a bug in GraphQL processing or some data corruption. 612 // 613 // We'll continue and just try the first item to return some data. 614 615 glog.Errorf("[%s] Got a list of length %v from Dgraph when expecting a "+ 616 "one-item list.\n"+ 617 "GraphQL query was : %s\n", 618 api.RequestID(ctx), len(val), api.QueryString(ctx)) 619 620 errs = append(errs, 621 x.GqlErrorf( 622 "Dgraph returned a list, but %s (type %s) was expecting just one item. "+ 623 "The first item in the list was used to produce the result. "+ 624 "Logged as a potential bug; see the API log for more details.", 625 field.Name(), field.Type().String()).WithLocations(field.Location())) 626 } 627 628 valToComplete[field.ResponseName()] = internalVal 629 } 630 default: 631 if val != nil { 632 return dgraphError() 633 } 634 635 // valToComplete[field.ResponseName()] is nil, so resolving for the 636 // { } ---> "q": null 637 // case 638 } 639 640 // Errors should report the "path" into the result where the error was found. 641 // 642 // The definition of a path in a GraphQL error is here: 643 // https://graphql.github.io/graphql-spec/June2018/#sec-Errors 644 // For a query like (assuming field f is of a list type and g is a scalar type): 645 // - q { f { g } } 646 // a path to the 2nd item in the f list would look like: 647 // - [ "q", "f", 2, "g" ] 648 path := make([]interface{}, 0, maxPathLength(field)) 649 650 completed, gqlErrs := completeObject( 651 path, field.Type(), []schema.Field{field}, valToComplete) 652 653 if len(completed) < 2 { 654 // This could only occur completeObject crushed the whole query, but 655 // that should never happen because the result type shouldn't be '!'. 656 // We should wrap enough testing around the schema generation that this 657 // just can't happen. 658 // 659 // This isn't really an observable GraphQL error, so no need to add anything 660 // to the payload of errors for the result. 661 glog.Errorf("[%s] Top level completeObject didn't return a result. "+ 662 "That's only possible if the query result is non-nullable. "+ 663 "There's something wrong in the GraphQL schema. \n"+ 664 "GraphQL query was : %s\n", 665 api.RequestID(ctx), api.QueryString(ctx)) 666 return nullResponse(), append(errs, gqlErrs...) 667 } 668 669 return completed, append(errs, gqlErrs...) 670 } 671 672 // completeObject builds a json GraphQL result object for the current query level. 673 // It returns a bracketed json object like { f1:..., f2:..., ... }. 674 // 675 // fields are all the fields from this bracketed level in the GraphQL query, e.g: 676 // { 677 // name 678 // dob 679 // friends {...} 680 // } 681 // If it's the top level of a query then it'll be the top level query name. 682 // 683 // typ is the expected type matching those fields, e.g. above that'd be something 684 // like the `Person` type that has fields name, dob and friends. 685 // 686 // res is the results map from Dgraph for this level of the query. This map needn't 687 // contain values for all the requested fields, e.g. if there's no corresponding 688 // values in the store or if the query contained a filter that excluded a value. 689 // So res might be the map : name->"A Name", friends -> []interface{} 690 // 691 // completeObject fills out this result putting in null for any missing values 692 // (dob above) and applying GraphQL error propagation for any null fields that the 693 // schema says can't be null. 694 // 695 // Example: 696 // 697 // if the map is name->"A Name", friends -> []interface{} 698 // 699 // and "dob" is nullable then the result should be json object 700 // {"name": "A Name", "dob": null, "friends": ABC} 701 // where ABC is the result of applying completeValue to res["friends"] 702 // 703 // if "dob" were non-nullable (maybe it's type is DateTime!), then the result is 704 // nil and the error propagates to the enclosing level. 705 func completeObject( 706 path []interface{}, 707 typ schema.Type, 708 fields []schema.Field, 709 res map[string]interface{}) ([]byte, x.GqlErrorList) { 710 711 var errs x.GqlErrorList 712 var buf bytes.Buffer 713 comma := "" 714 715 x.Check2(buf.WriteRune('{')) 716 717 dgraphTypes, ok := res["dgraph.type"].([]interface{}) 718 for _, f := range fields { 719 if f.Skip() || !f.Include() { 720 continue 721 } 722 723 includeField := true 724 // If typ is an interface, and dgraphTypes contains another type, then we ignore 725 // fields which don't start with that type. This would happen when multiple 726 // fragments (belonging to different types) are requested within a query for an interface. 727 728 // If the dgraphPredicate doesn't start with the typ.Name(), then this field belongs to 729 // a concrete type, lets check that it has inputType as the prefix, otherwise skip it. 730 if len(dgraphTypes) > 0 { 731 includeField = f.IncludeInterfaceField(dgraphTypes) 732 } 733 if !includeField { 734 continue 735 } 736 737 x.Check2(buf.WriteString(comma)) 738 x.Check2(buf.WriteRune('"')) 739 x.Check2(buf.WriteString(f.ResponseName())) 740 x.Check2(buf.WriteString(`": `)) 741 742 val := res[f.ResponseName()] 743 if f.Name() == schema.Typename { 744 // From GraphQL spec: 745 // https://graphql.github.io/graphql-spec/June2018/#sec-Type-Name-Introspection 746 // "GraphQL supports type name introspection at any point within a query by the 747 // meta‐field __typename: String! when querying against any Object, Interface, 748 // or Union. It returns the name of the object type currently being queried." 749 750 // If we have dgraph.type information, we will use that to figure out the type 751 // otherwise we will get it from the schema. 752 if ok { 753 val = f.TypeName(dgraphTypes) 754 } else { 755 val = f.GetObjectName() 756 } 757 } 758 759 completed, err := completeValue(append(path, f.ResponseName()), f, val) 760 errs = append(errs, err...) 761 if completed == nil { 762 if !f.Type().Nullable() { 763 return nil, errs 764 } 765 completed = []byte(`null`) 766 } 767 x.Check2(buf.Write(completed)) 768 comma = ", " 769 } 770 x.Check2(buf.WriteRune('}')) 771 772 return buf.Bytes(), errs 773 } 774 775 // completeValue applies the value completion algorithm to a single value, which 776 // could turn out to be a list or object or scalar value. 777 func completeValue( 778 path []interface{}, 779 field schema.Field, 780 val interface{}) ([]byte, x.GqlErrorList) { 781 782 switch val := val.(type) { 783 case map[string]interface{}: 784 return completeObject(path, field.Type(), field.SelectionSet(), val) 785 case []interface{}: 786 return completeList(path, field, val) 787 default: 788 if val == nil { 789 if field.Type().ListType() != nil { 790 // We could choose to set this to null. This is our decision, not 791 // anything required by the GraphQL spec. 792 // 793 // However, if we query, for example, for a persons's friends with 794 // some restrictions, and there aren't any, is that really a case to 795 // set this at null and error if the list is required? What 796 // about if an person has just been added and doesn't have any friends? 797 // Doesn't seem right to add null and cause error propagation. 798 // 799 // Seems best if we pick [], rather than null, as the list value if 800 // there's nothing in the Dgraph result. 801 return []byte("[]"), nil 802 } 803 804 if field.Type().Nullable() { 805 return []byte("null"), nil 806 } 807 808 gqlErr := x.GqlErrorf( 809 "Non-nullable field '%s' (type %s) was not present in result from Dgraph. "+ 810 "GraphQL error propagation triggered.", field.Name(), field.Type()). 811 WithLocations(field.Location()) 812 gqlErr.Path = copyPath(path) 813 814 return nil, x.GqlErrorList{gqlErr} 815 } 816 817 // val is a scalar 818 819 // Can this ever error? We can't have an unsupported type or value because 820 // we just unmarshaled this val. 821 json, err := json.Marshal(val) 822 if err != nil { 823 gqlErr := x.GqlErrorf( 824 "Error marshalling value for field '%s' (type %s). "+ 825 "Resolved as null (which may trigger GraphQL error propagation) ", 826 field.Name(), field.Type()). 827 WithLocations(field.Location()) 828 gqlErr.Path = copyPath(path) 829 830 if field.Type().Nullable() { 831 return []byte("null"), x.GqlErrorList{gqlErr} 832 } 833 834 return nil, x.GqlErrorList{gqlErr} 835 } 836 837 return json, nil 838 } 839 } 840 841 // completeList applies the completion algorithm to a list field and result. 842 // 843 // field is one field from the query - which should have a list type in the 844 // GraphQL schema. 845 // 846 // values is the list of values found by the query for this field. 847 // 848 // completeValue() is applied to every list element, but 849 // the type of field can only be a scalar list like [String], or an object 850 // list like [Person], so schematically the final result is either 851 // [ completValue("..."), completValue("..."), ... ] 852 // or 853 // [ completeObject({...}), completeObject({...}), ... ] 854 // depending on the type of list. 855 // 856 // If the list has non-nullable elements (a type like [T!]) and any of those 857 // elements resolve to null, then the whole list is crushed to null. 858 func completeList( 859 path []interface{}, 860 field schema.Field, 861 values []interface{}) ([]byte, x.GqlErrorList) { 862 863 var buf bytes.Buffer 864 var errs x.GqlErrorList 865 comma := "" 866 867 if field.Type().ListType() == nil { 868 // This means a bug on our part - in rewriting, schema generation, 869 // or Dgraph returned something unexpected. 870 // 871 // Let's crush it to null so we still get something from the rest of the 872 // query and log the error. 873 return mismatched(path, field, values) 874 } 875 876 x.Check2(buf.WriteRune('[')) 877 for i, b := range values { 878 r, err := completeValue(append(path, i), field, b) 879 errs = append(errs, err...) 880 x.Check2(buf.WriteString(comma)) 881 if r == nil { 882 if !field.Type().ListType().Nullable() { 883 // Unlike the choice in completeValue() above, where we turn missing 884 // lists into [], the spec explicitly calls out: 885 // "If a List type wraps a Non-Null type, and one of the 886 // elements of that list resolves to null, then the entire list 887 // must resolve to null." 888 // 889 // The list gets reduced to nil, but an error recording that must 890 // already be in errs. See 891 // https://graphql.github.io/graphql-spec/June2018/#sec-Errors-and-Non-Nullability 892 // "If the field returns null because of an error which has already 893 // been added to the "errors" list in the response, the "errors" 894 // list must not be further affected." 895 // The behavior is also in the examples in here: 896 // https://graphql.github.io/graphql-spec/June2018/#sec-Errors 897 return nil, errs 898 } 899 x.Check2(buf.WriteString("null")) 900 } else { 901 x.Check2(buf.Write(r)) 902 } 903 comma = ", " 904 } 905 x.Check2(buf.WriteRune(']')) 906 907 return buf.Bytes(), errs 908 } 909 910 func mismatched( 911 path []interface{}, 912 field schema.Field, 913 values []interface{}) ([]byte, x.GqlErrorList) { 914 915 glog.Errorf("completeList() called in resolving %s (Line: %v, Column: %v), "+ 916 "but its type is %s.\n"+ 917 "That could indicate the Dgraph schema doesn't match the GraphQL schema.", 918 field.Name(), field.Location().Line, field.Location().Column, field.Type().Name()) 919 920 gqlErr := &x.GqlError{ 921 Message: "Dgraph returned a list, but GraphQL was expecting just one item. " + 922 "This indicates an internal error - " + 923 "probably a mismatch between GraphQL and Dgraph schemas. " + 924 "The value was resolved as null (which may trigger GraphQL error propagation) " + 925 "and as much other data as possible returned.", 926 Locations: []x.Location{field.Location()}, 927 Path: copyPath(path), 928 } 929 930 val, errs := completeValue(path, field, nil) 931 return val, append(errs, gqlErr) 932 } 933 934 func copyPath(path []interface{}) []interface{} { 935 result := make([]interface{}, len(path)) 936 copy(result, path) 937 return result 938 } 939 940 // maxPathLength finds the max length (including list indexes) of any path in the 'query' f. 941 // Used to pre-allocate a path buffer of the correct size before running completeObject on 942 // the top level query - means that we aren't reallocating slices multiple times 943 // during the complete* functions. 944 func maxPathLength(f schema.Field) int { 945 childMax := 0 946 for _, chld := range f.SelectionSet() { 947 d := maxPathLength(chld) 948 if d > childMax { 949 childMax = d 950 } 951 } 952 if f.Type().ListType() != nil { 953 // It's f: [...], so add a space for field name and 954 // a space for the index into the list 955 return 2 + childMax 956 } 957 958 return 1 + childMax 959 }