mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-15 02:01:43 +03:00
graphql: add a general test for the handler/resolvers
This commit is contained in:
parent
879e147e2b
commit
f969370901
17
Gopkg.lock
generated
17
Gopkg.lock
generated
@ -156,6 +156,14 @@
|
||||
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:645110e089152bd0f4a011a2648fbb0e4df5977be73ca605781157ac297f50c4"
|
||||
name = "github.com/mitchellh/mapstructure"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "fa473d140ef3c6adf42d6b391fe76707f1f243c8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c9b6e36dbd23f8403a04493376916ca5dad8c01b2da5ae0a05e6a468eb0b6f24"
|
||||
@ -262,6 +270,14 @@
|
||||
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:895fa0f564003de2498fbaf00be596fa814655ab445ce7cc215f7724bb6831ae"
|
||||
name = "github.com/vektah/gqlgen"
|
||||
packages = ["client"]
|
||||
pruneopts = "UT"
|
||||
revision = "636435b68700211441303f1a5ed92f3768ba5774"
|
||||
version = "v0.5.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8150271279cc160a41e9aabfee8118c20a0e88894a25b2577f93e7c868e5259c"
|
||||
@ -380,6 +396,7 @@
|
||||
"github.com/skratchdot/open-golang/open",
|
||||
"github.com/spf13/cobra",
|
||||
"github.com/spf13/cobra/doc",
|
||||
"github.com/vektah/gqlgen/client",
|
||||
"github.com/vektah/gqlparser",
|
||||
"github.com/vektah/gqlparser/ast",
|
||||
"golang.org/x/crypto/ssh/terminal",
|
||||
|
148
tests/graphql_test.go
Normal file
148
tests/graphql_test.go
Normal file
@ -0,0 +1,148 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/MichaelMure/git-bug/graphql"
|
||||
"github.com/MichaelMure/git-bug/graphql/models"
|
||||
"github.com/vektah/gqlgen/client"
|
||||
)
|
||||
|
||||
func TestQueries(t *testing.T) {
|
||||
repo := createFilledRepo(10)
|
||||
|
||||
handler, err := graphql.NewHandler(repo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
srv := httptest.NewServer(handler)
|
||||
c := client.New(srv.URL)
|
||||
|
||||
query := `
|
||||
query {
|
||||
defaultRepository {
|
||||
allBugs(first: 2) {
|
||||
pageInfo {
|
||||
endCursor
|
||||
hasNextPage
|
||||
startCursor
|
||||
hasPreviousPage
|
||||
}
|
||||
nodes{
|
||||
author {
|
||||
name
|
||||
email
|
||||
avatarUrl
|
||||
}
|
||||
|
||||
createdAt
|
||||
humanId
|
||||
id
|
||||
lastEdit
|
||||
status
|
||||
title
|
||||
|
||||
comments(first: 2) {
|
||||
pageInfo {
|
||||
endCursor
|
||||
hasNextPage
|
||||
startCursor
|
||||
hasPreviousPage
|
||||
}
|
||||
nodes {
|
||||
files
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
operations(first: 20) {
|
||||
pageInfo {
|
||||
endCursor
|
||||
hasNextPage
|
||||
startCursor
|
||||
hasPreviousPage
|
||||
}
|
||||
nodes {
|
||||
author {
|
||||
name
|
||||
email
|
||||
avatarUrl
|
||||
}
|
||||
date
|
||||
... on CreateOperation {
|
||||
title
|
||||
message
|
||||
files
|
||||
}
|
||||
... on SetTitleOperation {
|
||||
title
|
||||
was
|
||||
}
|
||||
... on AddCommentOperation {
|
||||
files
|
||||
message
|
||||
}
|
||||
... on SetStatusOperation {
|
||||
status
|
||||
}
|
||||
... on LabelChangeOperation {
|
||||
added
|
||||
removed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
type Person struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
AvatarUrl string `json:"avatarUrl"`
|
||||
}
|
||||
|
||||
var resp struct {
|
||||
DefaultRepository struct {
|
||||
AllBugs struct {
|
||||
PageInfo models.PageInfo
|
||||
Nodes []struct {
|
||||
Author Person
|
||||
CreatedAt string `json:"createdAt"`
|
||||
HumandId string `json:"humanId"`
|
||||
Id string
|
||||
LastEdit string `json:"lastEdit"`
|
||||
Status string
|
||||
Title string
|
||||
|
||||
Comments struct {
|
||||
PageInfo models.PageInfo
|
||||
Nodes []struct {
|
||||
Files []string
|
||||
Message string
|
||||
}
|
||||
}
|
||||
|
||||
Operations struct {
|
||||
PageInfo models.PageInfo
|
||||
Nodes []struct {
|
||||
Author Person
|
||||
Date string
|
||||
Title string
|
||||
Files []string
|
||||
Message string
|
||||
Was string
|
||||
Status string
|
||||
Added []string
|
||||
Removed []string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.MustPost(query, &resp)
|
||||
}
|
8
vendor/github.com/mitchellh/mapstructure/.travis.yml
generated
vendored
Normal file
8
vendor/github.com/mitchellh/mapstructure/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.9.x
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test
|
21
vendor/github.com/mitchellh/mapstructure/LICENSE
generated
vendored
Normal file
21
vendor/github.com/mitchellh/mapstructure/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Mitchell Hashimoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
46
vendor/github.com/mitchellh/mapstructure/README.md
generated
vendored
Normal file
46
vendor/github.com/mitchellh/mapstructure/README.md
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# mapstructure [![Godoc](https://godoc.org/github.com/mitchellh/mapstructure?status.svg)](https://godoc.org/github.com/mitchellh/mapstructure)
|
||||
|
||||
mapstructure is a Go library for decoding generic map values to structures
|
||||
and vice versa, while providing helpful error handling.
|
||||
|
||||
This library is most useful when decoding values from some data stream (JSON,
|
||||
Gob, etc.) where you don't _quite_ know the structure of the underlying data
|
||||
until you read a part of it. You can therefore read a `map[string]interface{}`
|
||||
and use this library to decode it into the proper underlying native Go
|
||||
structure.
|
||||
|
||||
## Installation
|
||||
|
||||
Standard `go get`:
|
||||
|
||||
```
|
||||
$ go get github.com/mitchellh/mapstructure
|
||||
```
|
||||
|
||||
## Usage & Example
|
||||
|
||||
For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/mapstructure).
|
||||
|
||||
The `Decode` function has examples associated with it there.
|
||||
|
||||
## But Why?!
|
||||
|
||||
Go offers fantastic standard libraries for decoding formats such as JSON.
|
||||
The standard method is to have a struct pre-created, and populate that struct
|
||||
from the bytes of the encoded format. This is great, but the problem is if
|
||||
you have configuration or an encoding that changes slightly depending on
|
||||
specific fields. For example, consider this JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "person",
|
||||
"name": "Mitchell"
|
||||
}
|
||||
```
|
||||
|
||||
Perhaps we can't populate a specific structure without first reading
|
||||
the "type" field from the JSON. We could always do two passes over the
|
||||
decoding of the JSON (reading the "type" first, and the rest later).
|
||||
However, it is much simpler to just decode this into a `map[string]interface{}`
|
||||
structure, read the "type" key, then use something like this library
|
||||
to decode it into the proper structure.
|
171
vendor/github.com/mitchellh/mapstructure/decode_hooks.go
generated
vendored
Normal file
171
vendor/github.com/mitchellh/mapstructure/decode_hooks.go
generated
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
package mapstructure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
|
||||
// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
|
||||
func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
|
||||
// Create variables here so we can reference them with the reflect pkg
|
||||
var f1 DecodeHookFuncType
|
||||
var f2 DecodeHookFuncKind
|
||||
|
||||
// Fill in the variables into this interface and the rest is done
|
||||
// automatically using the reflect package.
|
||||
potential := []interface{}{f1, f2}
|
||||
|
||||
v := reflect.ValueOf(h)
|
||||
vt := v.Type()
|
||||
for _, raw := range potential {
|
||||
pt := reflect.ValueOf(raw).Type()
|
||||
if vt.ConvertibleTo(pt) {
|
||||
return v.Convert(pt).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeHookExec executes the given decode hook. This should be used
|
||||
// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
|
||||
// that took reflect.Kind instead of reflect.Type.
|
||||
func DecodeHookExec(
|
||||
raw DecodeHookFunc,
|
||||
from reflect.Type, to reflect.Type,
|
||||
data interface{}) (interface{}, error) {
|
||||
switch f := typedDecodeHook(raw).(type) {
|
||||
case DecodeHookFuncType:
|
||||
return f(from, to, data)
|
||||
case DecodeHookFuncKind:
|
||||
return f(from.Kind(), to.Kind(), data)
|
||||
default:
|
||||
return nil, errors.New("invalid decode hook signature")
|
||||
}
|
||||
}
|
||||
|
||||
// ComposeDecodeHookFunc creates a single DecodeHookFunc that
|
||||
// automatically composes multiple DecodeHookFuncs.
|
||||
//
|
||||
// The composed funcs are called in order, with the result of the
|
||||
// previous transformation.
|
||||
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Type,
|
||||
t reflect.Type,
|
||||
data interface{}) (interface{}, error) {
|
||||
var err error
|
||||
for _, f1 := range fs {
|
||||
data, err = DecodeHookExec(f1, f, t, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Modify the from kind to be correct with the new data
|
||||
f = nil
|
||||
if val := reflect.ValueOf(data); val.IsValid() {
|
||||
f = val.Type()
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
// StringToSliceHookFunc returns a DecodeHookFunc that converts
|
||||
// string to []string by splitting on the given sep.
|
||||
func StringToSliceHookFunc(sep string) DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
if f != reflect.String || t != reflect.Slice {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
raw := data.(string)
|
||||
if raw == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
return strings.Split(raw, sep), nil
|
||||
}
|
||||
}
|
||||
|
||||
// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to time.Duration.
|
||||
func StringToTimeDurationHookFunc() DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Type,
|
||||
t reflect.Type,
|
||||
data interface{}) (interface{}, error) {
|
||||
if f.Kind() != reflect.String {
|
||||
return data, nil
|
||||
}
|
||||
if t != reflect.TypeOf(time.Duration(5)) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Convert it by parsing
|
||||
return time.ParseDuration(data.(string))
|
||||
}
|
||||
}
|
||||
|
||||
// StringToTimeHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to time.Time.
|
||||
func StringToTimeHookFunc(layout string) DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Type,
|
||||
t reflect.Type,
|
||||
data interface{}) (interface{}, error) {
|
||||
if f.Kind() != reflect.String {
|
||||
return data, nil
|
||||
}
|
||||
if t != reflect.TypeOf(time.Time{}) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Convert it by parsing
|
||||
return time.Parse(layout, data.(string))
|
||||
}
|
||||
}
|
||||
|
||||
// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
|
||||
// the decoder.
|
||||
//
|
||||
// Note that this is significantly different from the WeaklyTypedInput option
|
||||
// of the DecoderConfig.
|
||||
func WeaklyTypedHook(
|
||||
f reflect.Kind,
|
||||
t reflect.Kind,
|
||||
data interface{}) (interface{}, error) {
|
||||
dataVal := reflect.ValueOf(data)
|
||||
switch t {
|
||||
case reflect.String:
|
||||
switch f {
|
||||
case reflect.Bool:
|
||||
if dataVal.Bool() {
|
||||
return "1", nil
|
||||
}
|
||||
return "0", nil
|
||||
case reflect.Float32:
|
||||
return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
|
||||
case reflect.Int:
|
||||
return strconv.FormatInt(dataVal.Int(), 10), nil
|
||||
case reflect.Slice:
|
||||
dataType := dataVal.Type()
|
||||
elemKind := dataType.Elem().Kind()
|
||||
if elemKind == reflect.Uint8 {
|
||||
return string(dataVal.Interface().([]uint8)), nil
|
||||
}
|
||||
case reflect.Uint:
|
||||
return strconv.FormatUint(dataVal.Uint(), 10), nil
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
50
vendor/github.com/mitchellh/mapstructure/error.go
generated
vendored
Normal file
50
vendor/github.com/mitchellh/mapstructure/error.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
package mapstructure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Error implements the error interface and can represents multiple
|
||||
// errors that occur in the course of a single decode.
|
||||
type Error struct {
|
||||
Errors []string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
points := make([]string, len(e.Errors))
|
||||
for i, err := range e.Errors {
|
||||
points[i] = fmt.Sprintf("* %s", err)
|
||||
}
|
||||
|
||||
sort.Strings(points)
|
||||
return fmt.Sprintf(
|
||||
"%d error(s) decoding:\n\n%s",
|
||||
len(e.Errors), strings.Join(points, "\n"))
|
||||
}
|
||||
|
||||
// WrappedErrors implements the errwrap.Wrapper interface to make this
|
||||
// return value more useful with the errwrap and go-multierror libraries.
|
||||
func (e *Error) WrappedErrors() []error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]error, len(e.Errors))
|
||||
for i, e := range e.Errors {
|
||||
result[i] = errors.New(e)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func appendErrors(errors []string, err error) []string {
|
||||
switch e := err.(type) {
|
||||
case *Error:
|
||||
return append(errors, e.Errors...)
|
||||
default:
|
||||
return append(errors, e.Error())
|
||||
}
|
||||
}
|
1
vendor/github.com/mitchellh/mapstructure/go.mod
generated
vendored
Normal file
1
vendor/github.com/mitchellh/mapstructure/go.mod
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
module github.com/mitchellh/mapstructure
|
1064
vendor/github.com/mitchellh/mapstructure/mapstructure.go
generated
vendored
Normal file
1064
vendor/github.com/mitchellh/mapstructure/mapstructure.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
19
vendor/github.com/vektah/gqlgen/LICENSE
generated
vendored
Normal file
19
vendor/github.com/vektah/gqlgen/LICENSE
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2018 Adam Scarr
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
141
vendor/github.com/vektah/gqlgen/client/client.go
generated
vendored
Normal file
141
vendor/github.com/vektah/gqlgen/client/client.go
generated
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
// client is used internally for testing. See readme for alternatives
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// Client for graphql requests
|
||||
type Client struct {
|
||||
url string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// New creates a graphql client
|
||||
func New(url string, client ...*http.Client) *Client {
|
||||
p := &Client{
|
||||
url: url,
|
||||
}
|
||||
|
||||
if len(client) > 0 {
|
||||
p.client = client[0]
|
||||
} else {
|
||||
p.client = http.DefaultClient
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Query string `json:"query"`
|
||||
Variables map[string]interface{} `json:"variables,omitempty"`
|
||||
OperationName string `json:"operationName,omitempty"`
|
||||
}
|
||||
|
||||
type Option func(r *Request)
|
||||
|
||||
func Var(name string, value interface{}) Option {
|
||||
return func(r *Request) {
|
||||
if r.Variables == nil {
|
||||
r.Variables = map[string]interface{}{}
|
||||
}
|
||||
|
||||
r.Variables[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
func Operation(name string) Option {
|
||||
return func(r *Request) {
|
||||
r.OperationName = name
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Client) MustPost(query string, response interface{}, options ...Option) {
|
||||
if err := p.Post(query, response, options...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Client) mkRequest(query string, options ...Option) Request {
|
||||
r := Request{
|
||||
Query: query,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(&r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (p *Client) Post(query string, response interface{}, options ...Option) (resperr error) {
|
||||
r := p.mkRequest(query, options...)
|
||||
requestBody, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode: %s", err.Error())
|
||||
}
|
||||
|
||||
rawResponse, err := p.client.Post(p.url, "application/json", bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
return fmt.Errorf("post: %s", err.Error())
|
||||
}
|
||||
defer func() {
|
||||
_ = rawResponse.Body.Close()
|
||||
}()
|
||||
|
||||
if rawResponse.StatusCode >= http.StatusBadRequest {
|
||||
responseBody, _ := ioutil.ReadAll(rawResponse.Body)
|
||||
return fmt.Errorf("http %d: %s", rawResponse.StatusCode, responseBody)
|
||||
}
|
||||
|
||||
responseBody, err := ioutil.ReadAll(rawResponse.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read: %s", err.Error())
|
||||
}
|
||||
|
||||
// decode it into map string first, let mapstructure do the final decode
|
||||
// because it can be much stricter about unknown fields.
|
||||
respDataRaw := struct {
|
||||
Data interface{}
|
||||
Errors json.RawMessage
|
||||
}{}
|
||||
err = json.Unmarshal(responseBody, &respDataRaw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode: %s", err.Error())
|
||||
}
|
||||
|
||||
// we want to unpack even if there is an error, so we can see partial responses
|
||||
unpackErr := unpack(respDataRaw.Data, response)
|
||||
|
||||
if respDataRaw.Errors != nil {
|
||||
return RawJsonError{respDataRaw.Errors}
|
||||
}
|
||||
return unpackErr
|
||||
}
|
||||
|
||||
type RawJsonError struct {
|
||||
json.RawMessage
|
||||
}
|
||||
|
||||
func (r RawJsonError) Error() string {
|
||||
return string(r.RawMessage)
|
||||
}
|
||||
|
||||
func unpack(data interface{}, into interface{}) error {
|
||||
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
Result: into,
|
||||
TagName: "json",
|
||||
ErrorUnused: true,
|
||||
ZeroFields: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("mapstructure: %s", err.Error())
|
||||
}
|
||||
|
||||
return d.Decode(data)
|
||||
}
|
5
vendor/github.com/vektah/gqlgen/client/readme.md
generated
vendored
Normal file
5
vendor/github.com/vektah/gqlgen/client/readme.md
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
This client is used internally for testing. I wanted a simple graphql client sent user specified queries.
|
||||
|
||||
You might want to look at:
|
||||
- https://github.com/shurcooL/graphql: Uses reflection to build queries from structs.
|
||||
- https://github.com/machinebox/graphql: Probably would have been a perfect fit, but it uses form encoding instead of json...
|
103
vendor/github.com/vektah/gqlgen/client/websocket.go
generated
vendored
Normal file
103
vendor/github.com/vektah/gqlgen/client/websocket.go
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/vektah/gqlparser/gqlerror"
|
||||
)
|
||||
|
||||
const (
|
||||
connectionInitMsg = "connection_init" // Client -> Server
|
||||
startMsg = "start" // Client -> Server
|
||||
connectionAckMsg = "connection_ack" // Server -> Client
|
||||
dataMsg = "data" // Server -> Client
|
||||
errorMsg = "error" // Server -> Client
|
||||
)
|
||||
|
||||
type operationMessage struct {
|
||||
Payload json.RawMessage `json:"payload,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type Subscription struct {
|
||||
Close func() error
|
||||
Next func(response interface{}) error
|
||||
}
|
||||
|
||||
func errorSubscription(err error) *Subscription {
|
||||
return &Subscription{
|
||||
Close: func() error { return nil },
|
||||
Next: func(response interface{}) error {
|
||||
return err
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Client) Websocket(query string, options ...Option) *Subscription {
|
||||
r := p.mkRequest(query, options...)
|
||||
requestBody, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return errorSubscription(fmt.Errorf("encode: %s", err.Error()))
|
||||
}
|
||||
|
||||
url := strings.Replace(p.url, "http://", "ws://", -1)
|
||||
url = strings.Replace(url, "https://", "wss://", -1)
|
||||
|
||||
c, _, err := websocket.DefaultDialer.Dial(url, nil)
|
||||
if err != nil {
|
||||
return errorSubscription(fmt.Errorf("dial: %s", err.Error()))
|
||||
}
|
||||
|
||||
if err = c.WriteJSON(operationMessage{Type: connectionInitMsg}); err != nil {
|
||||
return errorSubscription(fmt.Errorf("init: %s", err.Error()))
|
||||
}
|
||||
|
||||
var ack operationMessage
|
||||
if err = c.ReadJSON(&ack); err != nil {
|
||||
return errorSubscription(fmt.Errorf("ack: %s", err.Error()))
|
||||
}
|
||||
if ack.Type != connectionAckMsg {
|
||||
return errorSubscription(fmt.Errorf("expected ack message, got %#v", ack))
|
||||
}
|
||||
|
||||
if err = c.WriteJSON(operationMessage{Type: startMsg, ID: "1", Payload: requestBody}); err != nil {
|
||||
return errorSubscription(fmt.Errorf("start: %s", err.Error()))
|
||||
}
|
||||
|
||||
return &Subscription{
|
||||
Close: c.Close,
|
||||
Next: func(response interface{}) error {
|
||||
var op operationMessage
|
||||
c.ReadJSON(&op)
|
||||
if op.Type != dataMsg {
|
||||
if op.Type == errorMsg {
|
||||
return fmt.Errorf(string(op.Payload))
|
||||
} else {
|
||||
return fmt.Errorf("expected data message, got %#v", op)
|
||||
}
|
||||
}
|
||||
|
||||
respDataRaw := map[string]interface{}{}
|
||||
err = json.Unmarshal(op.Payload, &respDataRaw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode: %s", err.Error())
|
||||
}
|
||||
|
||||
if respDataRaw["errors"] != nil {
|
||||
var errs []*gqlerror.Error
|
||||
if err = unpack(respDataRaw["errors"], &errs); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("errors: %s", errs)
|
||||
}
|
||||
}
|
||||
|
||||
return unpack(respDataRaw["data"], response)
|
||||
},
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user