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  }