github.com/dgraph-io/dgraph@v1.2.8/graphql/schema/response.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 "bytes" 21 "encoding/json" 22 "fmt" 23 "io" 24 25 "github.com/dgraph-io/dgraph/x" 26 "github.com/golang/glog" 27 "github.com/pkg/errors" 28 ) 29 30 // GraphQL spec on response is here: 31 // https://graphql.github.io/graphql-spec/June2018/#sec-Response 32 33 // GraphQL spec on errors is here: 34 // https://graphql.github.io/graphql-spec/June2018/#sec-Errors 35 36 // Response represents a GraphQL response 37 type Response struct { 38 Errors x.GqlErrorList 39 Data bytes.Buffer 40 Extensions *Extensions 41 } 42 43 // Extensions : GraphQL specifies allowing "extensions" in results, but the 44 // format is up to the implementation. 45 type Extensions struct { 46 RequestID string `json:"requestID,omitempty"` 47 } 48 49 // ErrorResponse formats an error as a list of GraphQL errors and builds 50 // a response with that error list and no data. Because it doesn't add data, it 51 // should be used before starting execution - GraphQL spec requires no data if an 52 // error is detected before execution begins. 53 func ErrorResponse(err error, requestID string) *Response { 54 return &Response{ 55 Errors: AsGQLErrors(err), 56 Extensions: &Extensions{RequestID: requestID}, 57 } 58 } 59 60 // WithError generates GraphQL errors from err and records those in r. 61 func (r *Response) WithError(err error) { 62 r.Errors = append(r.Errors, AsGQLErrors(err)...) 63 } 64 65 // AddData adds p to r's data buffer. If p is empty, the call has no effect. 66 // If r.Data is empty before the call, then r.Data becomes {p} 67 // If r.Data contains data it always looks like {f,g,...}, and 68 // adding to that results in {f,g,...,p} 69 func (r *Response) AddData(p []byte) { 70 if r == nil || len(p) == 0 { 71 return 72 } 73 74 if r.Data.Len() > 0 { 75 // The end of the buffer is always the closing `}` 76 r.Data.Truncate(r.Data.Len() - 1) 77 x.Check2(r.Data.WriteRune(',')) 78 } 79 80 if r.Data.Len() == 0 { 81 x.Check2(r.Data.WriteRune('{')) 82 } 83 84 x.Check2(r.Data.Write(p)) 85 x.Check2(r.Data.WriteRune('}')) 86 } 87 88 // WriteTo writes the GraphQL response as unindented JSON to w 89 // and returns the number of bytes written and error, if any. 90 func (r *Response) WriteTo(w io.Writer) (int64, error) { 91 if r == nil { 92 i, err := w.Write([]byte( 93 `{ "errors": [{"message": "Internal error - no response to write."}], ` + 94 ` "data": null, "extensions": { "requestID": "unknown request ID" } }`)) 95 return int64(i), err 96 } 97 98 js, err := json.Marshal(struct { 99 Errors []*x.GqlError `json:"errors,omitempty"` 100 Data json.RawMessage `json:"data,omitempty"` 101 Extensions *Extensions `json:"extensions,omitempty"` 102 }{ 103 Errors: r.Errors, 104 Data: r.Data.Bytes(), 105 Extensions: r.Extensions, 106 }) 107 108 if err != nil { 109 var reqID string 110 if r.Extensions != nil { 111 reqID = r.Extensions.RequestID 112 } else { 113 reqID = "unknown request ID" 114 } 115 msg := "Internal error - failed to marshal a valid JSON response" 116 glog.Errorf("[%s] %+v", reqID, errors.Wrap(err, msg)) 117 js = []byte(fmt.Sprintf( 118 `{ "errors": [{"message": "%s"}], "data": null, "extensions": { "requestID": "%s" } }`, 119 msg, reqID)) 120 } 121 122 i, err := w.Write(js) 123 return int64(i), err 124 }