github.com/cayleygraph/cayley@v0.7.7/internal/http/query.go (about)

     1  // Copyright 2014 The Cayley Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package http
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"errors"
    21  	"io"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"net/url"
    25  	"strconv"
    26  
    27  	"github.com/julienschmidt/httprouter"
    28  
    29  	"github.com/cayleygraph/cayley/query"
    30  )
    31  
    32  type SuccessQueryWrapper struct {
    33  	Result interface{} `json:"result"`
    34  }
    35  
    36  type ErrorQueryWrapper struct {
    37  	Error string `json:"error"`
    38  }
    39  
    40  func WriteError(w io.Writer, err error) error {
    41  	enc := json.NewEncoder(w)
    42  	//enc.SetIndent("", " ")
    43  	return enc.Encode(ErrorQueryWrapper{err.Error()})
    44  }
    45  
    46  func WriteResult(w io.Writer, result interface{}) error {
    47  	enc := json.NewEncoder(w)
    48  	//enc.SetIndent("", " ")
    49  	return enc.Encode(SuccessQueryWrapper{result})
    50  }
    51  
    52  func GetQueryShape(q string, ses query.HTTP) ([]byte, error) {
    53  	s, err := ses.ShapeOf(q)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	return json.Marshal(s)
    58  }
    59  
    60  func (api *API) contextForRequest(r *http.Request) (context.Context, func()) {
    61  	ctx := context.TODO() // TODO(dennwc): get from request
    62  	cancel := func() {}
    63  	if api.config.Timeout > 0 {
    64  		ctx, cancel = context.WithTimeout(ctx, api.config.Timeout)
    65  	}
    66  	return ctx, cancel
    67  }
    68  
    69  func defaultErrorFunc(w query.ResponseWriter, err error) {
    70  	data, _ := json.Marshal(err.Error())
    71  	w.WriteHeader(http.StatusBadRequest)
    72  	w.Write([]byte(`{"error" : `))
    73  	w.Write(data)
    74  	w.Write([]byte(`}`))
    75  }
    76  
    77  // TODO(barakmich): Turn this into proper middleware.
    78  func (api *API) ServeV1Query(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
    79  	ctx, cancel := api.contextForRequest(r)
    80  	defer cancel()
    81  	l := query.GetLanguage(params.ByName("query_lang"))
    82  	if l == nil {
    83  		jsonResponse(w, http.StatusBadRequest, "Unknown query language.")
    84  		return
    85  	}
    86  	errFunc := defaultErrorFunc
    87  	if l.HTTPError != nil {
    88  		errFunc = l.HTTPError
    89  	}
    90  	select {
    91  	case <-ctx.Done():
    92  		errFunc(w, ctx.Err())
    93  		return
    94  	default:
    95  	}
    96  	h, err := api.GetHandleForRequest(r)
    97  	if err != nil {
    98  		errFunc(w, err)
    99  		return
   100  	}
   101  	if l.HTTPQuery != nil {
   102  		defer r.Body.Close()
   103  		l.HTTPQuery(ctx, h.QuadStore, w, r.Body)
   104  		return
   105  	}
   106  	if l.HTTP == nil {
   107  		errFunc(w, errors.New("HTTP interface is not supported for this query language."))
   108  		return
   109  	}
   110  
   111  	par, _ := url.ParseQuery(r.URL.RawQuery)
   112  	limit, _ := strconv.Atoi(par.Get("limit"))
   113  	if limit == 0 {
   114  		limit = 100
   115  	}
   116  
   117  	ses := l.HTTP(h.QuadStore)
   118  	bodyBytes, err := ioutil.ReadAll(r.Body)
   119  	if err != nil {
   120  		errFunc(w, err)
   121  		return
   122  	}
   123  	it, err := ses.Execute(ctx, string(bodyBytes), query.Options{
   124  		Collation: query.JSON,
   125  		Limit:     limit,
   126  	})
   127  	if err != nil {
   128  		errFunc(w, err)
   129  		return
   130  	}
   131  	defer it.Close()
   132  
   133  	var out []interface{}
   134  	for it.Next(ctx) {
   135  		out = append(out, it.Result())
   136  	}
   137  	if err = it.Err(); err != nil {
   138  		errFunc(w, err)
   139  		return
   140  	}
   141  	_ = WriteResult(w, out)
   142  }
   143  
   144  func (api *API) ServeV1Shape(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
   145  	ctx, cancel := api.contextForRequest(r)
   146  	defer cancel()
   147  	select {
   148  	case <-ctx.Done():
   149  		jsonResponse(w, http.StatusBadRequest, "Cancelled")
   150  		return
   151  	default:
   152  	}
   153  	h, err := api.GetHandleForRequest(r)
   154  	if err != nil {
   155  		jsonResponse(w, http.StatusBadRequest, err)
   156  		return
   157  	}
   158  	l := query.GetLanguage(params.ByName("query_lang"))
   159  	if l == nil {
   160  		jsonResponse(w, http.StatusBadRequest, "Unknown query language.")
   161  		return
   162  	} else if l.HTTP == nil {
   163  		jsonResponse(w, http.StatusBadRequest, "HTTP interface is not supported for this query language.")
   164  		return
   165  	}
   166  	ses := l.HTTP(h.QuadStore)
   167  	bodyBytes, err := ioutil.ReadAll(r.Body)
   168  	if err != nil {
   169  		jsonResponse(w, http.StatusBadRequest, err)
   170  		return
   171  	}
   172  	code := string(bodyBytes)
   173  
   174  	output, err := GetQueryShape(code, ses)
   175  	if err == query.ErrParseMore {
   176  		jsonResponse(w, http.StatusBadRequest, "Incomplete data?")
   177  		return
   178  	} else if err != nil {
   179  		w.WriteHeader(http.StatusBadRequest)
   180  		WriteError(w, err)
   181  		return
   182  	}
   183  	w.Write(output)
   184  }