Add graphql server (#27)
* Add graphql server * Update Makefile * Update log_filters constraint * Add GetLogFilter to repo * Update travis (use Makefile, go fmt, go vet) * Add logFilter schema and resolvers * Add GetWatchedEvent to watched_events_repo * Add watchedEventLog schema and resolvers
This commit is contained in:
parent
d5852654bb
commit
605b0a96ae
15
.travis.yml
15
.travis.yml
@ -10,24 +10,21 @@ addons:
|
||||
go_import_path: github.com/vulcanize/vulcanizedb
|
||||
|
||||
before_install:
|
||||
# ginkgo
|
||||
- go get -u github.com/onsi/ginkgo/ginkgo
|
||||
# migrate
|
||||
- go get -u -d github.com/mattes/migrate/cli github.com/lib/pq
|
||||
- sudo $(which go) build -tags 'postgres' -o /usr/local/bin/migrate github.com/mattes/migrate/cli
|
||||
- sudo -u postgres createdb vulcanize_private
|
||||
# ginkgo golint dep migrate
|
||||
- make installtools
|
||||
# geth
|
||||
- wget https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.7.2-1db4ecdc.tar.gz
|
||||
- tar -xzf geth-linux-amd64-1.7.2-1db4ecdc.tar.gz
|
||||
- sudo cp geth-linux-amd64-1.7.2-1db4ecdc/geth /usr/local/bin
|
||||
|
||||
before_script:
|
||||
- ./scripts/setup
|
||||
- nohup ./scripts/start_private_blockchain </dev/null &
|
||||
- sudo -u postgres createdb vulcanize_private
|
||||
- make migrate HOST_NAME=localhost NAME=vulcanize_private PORT=5432
|
||||
- make createprivate
|
||||
- nohup make startprivate </dev/null &
|
||||
|
||||
script:
|
||||
- ginkgo -r
|
||||
- make test
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
33
Gopkg.lock
generated
33
Gopkg.lock
generated
@ -152,6 +152,27 @@
|
||||
packages = ["."]
|
||||
revision = "b4575eea38cca1123ec2dc90c26529b5c5acfcff"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/neelance/graphql-go"
|
||||
packages = [
|
||||
".",
|
||||
"errors",
|
||||
"internal/common",
|
||||
"internal/exec",
|
||||
"internal/exec/packer",
|
||||
"internal/exec/resolvable",
|
||||
"internal/exec/selected",
|
||||
"internal/query",
|
||||
"internal/schema",
|
||||
"internal/validation",
|
||||
"introspection",
|
||||
"log",
|
||||
"relay",
|
||||
"trace"
|
||||
]
|
||||
revision = "b46637030579abd312c5eea21d36845b6e9e7ca4"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/onsi/ginkgo"
|
||||
packages = [
|
||||
@ -197,6 +218,16 @@
|
||||
revision = "c893efa28eb45626cdaa76c9f653b62488858837"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/opentracing/opentracing-go"
|
||||
packages = [
|
||||
".",
|
||||
"ext",
|
||||
"log"
|
||||
]
|
||||
revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pelletier/go-toml"
|
||||
packages = ["."]
|
||||
@ -348,6 +379,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "9f01f0f0bbce801c579dccd45229f8a78a21e229e5c93f8a54cb22748b0b25b2"
|
||||
inputs-digest = "4d1806bf6a5678261abb604455d0076906ca5aba42e8625ee09cae019fb21d40"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
@ -40,3 +40,7 @@
|
||||
[[constraint]]
|
||||
name = "github.com/ethereum/go-ethereum"
|
||||
version = "1.7.3"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/neelance/graphql-go"
|
||||
|
97
Makefile
97
Makefile
@ -1,49 +1,92 @@
|
||||
HOST_NAME =
|
||||
PORT =
|
||||
BIN = $(GOPATH)/bin
|
||||
BASE = $(GOPATH)/src/$(PACKAGE)
|
||||
PKGS = go list ./... | grep -v "^vendor/"
|
||||
|
||||
#Tools
|
||||
DEP = $(BIN)/dep
|
||||
$(BIN)/dep:
|
||||
go get -u github.com/golang/dep/cmd/dep
|
||||
|
||||
GINKGO = $(BIN)/ginkgo
|
||||
$(BIN)/ginkgo:
|
||||
go get -u github.com/onsi/ginkgo/ginkgo
|
||||
|
||||
MIGRATE = $(BIN)/migrate
|
||||
$(BIN)/migrate:
|
||||
go get -u -d github.com/mattes/migrate/cli github.com/lib/pq
|
||||
go build -tags 'postgres' -o $(BIN)/migrate github.com/mattes/migrate/cli
|
||||
|
||||
LINT = $(BIN)/golint
|
||||
$(BIN)/golint:
|
||||
go get github.com/golang/lint/golint
|
||||
|
||||
METALINT = $(BIN)/gometalinter.v2
|
||||
$(BIN)/gometalinter.v2:
|
||||
go get -u gopkg.in/alecthomas/gometalinter.v2
|
||||
$(METALINT) --install
|
||||
|
||||
.PHONY: installtools
|
||||
installtools: | $(LINT) $(MIGRATE) $(GINKGO) $(DEP)
|
||||
echo "Installing tools"
|
||||
|
||||
.PHONY: metalint
|
||||
metalint: | $(METALINT)
|
||||
$(METALINT) ./... --vendor \
|
||||
--fast \
|
||||
--exclude="exported (function)|(var)|(method)|(type).*should have comment or be unexported" \
|
||||
--format="{{.Path.Abs}}:{{.Line}}:{{if .Col}}{{.Col}}{{end}}:{{.Severity}}: {{.Message}} ({{.Linter}})"
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
$(LINT) $$($(PKGS)) | grep -v -E "exported (function)|(var)|(method)|(type).*should have comment or be unexported"
|
||||
|
||||
.PHONY: test
|
||||
test: | $(GINKGO) $(LINT)
|
||||
go vet ./...
|
||||
go fmt ./...
|
||||
$(GINKGO) -r
|
||||
|
||||
.PHONY: dep
|
||||
dep: | $(DEP)
|
||||
$(DEP) ensure
|
||||
|
||||
build: dep
|
||||
go fmt ./...
|
||||
go build
|
||||
|
||||
#Database
|
||||
HOST_NAME = localhost
|
||||
PORT = 5432
|
||||
NAME =
|
||||
CONNECT_STRING=postgresql://$(HOST_NAME):$(PORT)/$(NAME)?sslmode=disable
|
||||
|
||||
$(MATTESMIGRATE):
|
||||
go get -u -d github.com/mattes/migrate/cli github.com/lib/pq
|
||||
go build -tags 'postgres' -o /usr/local/bin/migrate github.com/mattes/migrate/cli
|
||||
|
||||
$(DEP):
|
||||
go get -u github.com/golang/dep/cmd/dep
|
||||
|
||||
$(GINKGO):
|
||||
go get -u github.com/onsi/ginkgo/ginkgo
|
||||
|
||||
.PHONY: checkdbvars
|
||||
checkdbvars:
|
||||
test -n "$(HOST_NAME)" # $$HOST_NAME
|
||||
test -n "$(PORT)" # $$PORT
|
||||
test -n "$(NAME)" # $$NAME
|
||||
|
||||
rollback: checkdbvars
|
||||
migrate -database $(CONNECT_STRING) -path ./db/migrations down 1
|
||||
.PHONY: rollback
|
||||
rollback: $(MIGRATE) checkdbvars
|
||||
$(MIGRATE) -database $(CONNECT_STRING) -path ./db/migrations down 1
|
||||
pg_dump -O -s $(CONNECT_STRING) > db/schema.sql
|
||||
|
||||
migrate: $(MATTESMIGRATE) checkdbvars
|
||||
migrate -database $(CONNECT_STRING) -path ./db/migrations up
|
||||
.PHONY: migrate
|
||||
migrate: $(MIGRATE) checkdbvars
|
||||
$(MIGRATE) -database $(CONNECT_STRING) -path ./db/migrations up
|
||||
pg_dump -O -s $(CONNECT_STRING) > db/schema.sql
|
||||
|
||||
.PHONY: import
|
||||
import:
|
||||
test -n "$(NAME)" # $$NAME
|
||||
psql $(NAME) < db/schema.sql
|
||||
|
||||
dep: $(DEP)
|
||||
dep ensure
|
||||
|
||||
build: dep
|
||||
go build
|
||||
|
||||
test: $(GINKGO)
|
||||
ginkgo -r
|
||||
|
||||
#Ethereum
|
||||
createprivate:
|
||||
#!/bin/bash
|
||||
echo "Deleting test blockchain"
|
||||
echo "Deleting test node"
|
||||
rm -rf test_data_dir
|
||||
echo "Creating test blockchain with a new account"
|
||||
echo "Creating test node with a new account"
|
||||
mkdir test_data_dir
|
||||
geth --dev --datadir test_data_dir --password .private_blockchain_password account new
|
||||
|
||||
|
@ -5,10 +5,10 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||
"github.com/vulcanize/vulcanizedb/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// addFilterCmd represents the addFilter command
|
||||
@ -67,7 +67,7 @@ func addFilter() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, filter := range logFilters {
|
||||
err = repository.AddFilter(filter)
|
||||
err = repository.CreateFilter(filter)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
96
cmd/graphql.go
Normal file
96
cmd/graphql.go
Normal file
@ -0,0 +1,96 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/neelance/graphql-go"
|
||||
"github.com/neelance/graphql-go/relay"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/graphql_server"
|
||||
"github.com/vulcanize/vulcanizedb/utils"
|
||||
)
|
||||
|
||||
var graphqlCmd = &cobra.Command{
|
||||
Use: "graphql",
|
||||
Short: "Starts Vulcanize graphql server",
|
||||
Long: `Starts vulcanize graphql server
|
||||
and usage of using your command. For example:
|
||||
|
||||
vulcanizedb graphql --port 9090 --host localhost
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
schema := parseSchema()
|
||||
serve(schema)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
var (
|
||||
port int
|
||||
host string
|
||||
)
|
||||
rootCmd.AddCommand(graphqlCmd)
|
||||
|
||||
syncCmd.Flags().IntVar(&port, "port", 9090, "graphql: port")
|
||||
syncCmd.Flags().StringVar(&host, "host", "localhost", "graphql: host")
|
||||
|
||||
}
|
||||
|
||||
func parseSchema() *graphql.Schema {
|
||||
|
||||
blockchain := geth.NewBlockchain(ipc)
|
||||
repository := utils.LoadPostgres(databaseConfig, blockchain.Node())
|
||||
schema := graphql.MustParseSchema(graphql_server.Schema, graphql_server.NewResolver(repository))
|
||||
return schema
|
||||
|
||||
}
|
||||
|
||||
func serve(schema *graphql.Schema) {
|
||||
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(page)
|
||||
}))
|
||||
http.Handle("/query", &relay.Handler{Schema: schema})
|
||||
|
||||
log.Fatal(http.ListenAndServe(":9090", nil))
|
||||
}
|
||||
|
||||
var page = []byte(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.10.2/graphiql.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.1.0/fetch.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react-dom.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.10.2/graphiql.js"></script>
|
||||
</head>
|
||||
<body style="width: 100%; height: 100%; margin: 0; overflow: hidden;">
|
||||
<div id="graphiql" style="height: 100vh;">Loading...</div>
|
||||
<script>
|
||||
function graphQLFetcher(graphQLParams) {
|
||||
return fetch("/query", {
|
||||
method: "post",
|
||||
body: JSON.stringify(graphQLParams),
|
||||
credentials: "include",
|
||||
}).then(function (response) {
|
||||
return response.text();
|
||||
}).then(function (responseBody) {
|
||||
try {
|
||||
return JSON.parse(responseBody);
|
||||
} catch (error) {
|
||||
return responseBody;
|
||||
}
|
||||
});
|
||||
}
|
||||
ReactDOM.render(
|
||||
React.createElement(GraphiQL, {fetcher: graphQLFetcher}),
|
||||
document.getElementById("graphiql")
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
10
cmd/root.go
10
cmd/root.go
@ -4,15 +4,17 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/config"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/config"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
var databaseConfig config.Database
|
||||
var ipc string
|
||||
var (
|
||||
cfgFile string
|
||||
databaseConfig config.Database
|
||||
ipc string
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "vulcanizedb",
|
||||
|
@ -0,0 +1,7 @@
|
||||
BEGIN;
|
||||
ALTER TABLE log_filters
|
||||
DROP CONSTRAINT log_filters_to_block_check;
|
||||
|
||||
ALTER TABLE log_filters
|
||||
ADD CONSTRAINT log_filters_from_block_check1 CHECK (to_block >= 0);
|
||||
COMMIT;
|
@ -0,0 +1,7 @@
|
||||
BEGIN;
|
||||
ALTER TABLE log_filters
|
||||
DROP CONSTRAINT log_filters_from_block_check1;
|
||||
|
||||
ALTER TABLE log_filters
|
||||
ADD CONSTRAINT log_filters_to_block_check CHECK (to_block >= 0);
|
||||
COMMIT;
|
@ -123,8 +123,8 @@ CREATE TABLE log_filters (
|
||||
topic2 character varying(66),
|
||||
topic3 character varying(66),
|
||||
CONSTRAINT log_filters_from_block_check CHECK ((from_block >= 0)),
|
||||
CONSTRAINT log_filters_from_block_check1 CHECK ((from_block >= 0)),
|
||||
CONSTRAINT log_filters_name_check CHECK (((name)::text <> ''::text))
|
||||
CONSTRAINT log_filters_name_check CHECK (((name)::text <> ''::text)),
|
||||
CONSTRAINT log_filters_to_block_check CHECK ((to_block >= 0))
|
||||
);
|
||||
|
||||
|
||||
|
@ -7,7 +7,6 @@
|
||||
},
|
||||
{
|
||||
"name": "NewFilter",
|
||||
"toBlock": "0x4B34AA",
|
||||
"fromBlock": "0x4B34AD",
|
||||
"address": "0x06012c8cf97bead5deae237070f9587f8e7a266d",
|
||||
"topics": ["0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80"]
|
||||
|
@ -3,10 +3,10 @@ package integration
|
||||
import (
|
||||
"log"
|
||||
|
||||
cfg "github.com/vulcanize/vulcanizedb/pkg/config"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
cfg "github.com/vulcanize/vulcanizedb/pkg/config"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||
)
|
||||
|
||||
var _ = Describe("Rewards calculations", func() {
|
||||
|
@ -5,12 +5,12 @@ import (
|
||||
|
||||
"log"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
cfg "github.com/vulcanize/vulcanizedb/pkg/config"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth/testing"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Reading contracts", func() {
|
||||
|
@ -3,9 +3,9 @@ package config_test
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
cfg "github.com/vulcanize/vulcanizedb/pkg/config"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
cfg "github.com/vulcanize/vulcanizedb/pkg/config"
|
||||
)
|
||||
|
||||
var _ = Describe("Loading the config", func() {
|
||||
|
@ -3,8 +3,8 @@ package contract_summary
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
)
|
||||
|
||||
func GenerateConsoleOutput(summary ContractSummary) string {
|
||||
|
@ -18,7 +18,7 @@ type ContractSummary struct {
|
||||
}
|
||||
|
||||
func NewSummary(blockchain core.Blockchain, repository repositories.Repository, contractHash string, blockNumber *big.Int) (ContractSummary, error) {
|
||||
contract, err := repository.FindContract(contractHash)
|
||||
contract, err := repository.GetContract(contractHash)
|
||||
if err != nil {
|
||||
return ContractSummary{}, err
|
||||
} else {
|
||||
|
14
pkg/core/watched_event_log.go
Normal file
14
pkg/core/watched_event_log.go
Normal file
@ -0,0 +1,14 @@
|
||||
package core
|
||||
|
||||
type WatchedEvent struct {
|
||||
Name string `json:"name"` // name
|
||||
BlockNumber int64 `json:"block_number" db:"block_number"` // block_number
|
||||
Address string `json:"address"` // address
|
||||
TxHash string `json:"tx_hash" db:"tx_hash"` // tx_hash
|
||||
Index int64 `json:"index"` // index
|
||||
Topic0 string `json:"topic0"` // topic0
|
||||
Topic1 string `json:"topic1"` // topic1
|
||||
Topic2 string `json:"topic2"` // topic2
|
||||
Topic3 string `json:"topic3"` // topic3
|
||||
Data string `json:"data"` // data
|
||||
}
|
@ -5,17 +5,17 @@ import (
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
)
|
||||
|
||||
type LogFilters []LogFilter
|
||||
|
||||
type LogFilter struct {
|
||||
Name string `json:"name"`
|
||||
FromBlock int64 `json:"fromBlock"`
|
||||
ToBlock int64 `json:"toBlock"`
|
||||
FromBlock int64 `json:"fromBlock" db:"from_block"`
|
||||
ToBlock int64 `json:"toBlock" db:"to_block"`
|
||||
Address string `json:"address"`
|
||||
core.Topics `json:"topics"`
|
||||
}
|
||||
|
@ -3,10 +3,10 @@ package filters_test
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
||||
)
|
||||
|
||||
var _ = Describe("Log filters", func() {
|
||||
|
@ -9,12 +9,12 @@ import (
|
||||
|
||||
"log"
|
||||
|
||||
cfg "github.com/vulcanize/vulcanizedb/pkg/config"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/ghttp"
|
||||
cfg "github.com/vulcanize/vulcanizedb/pkg/config"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||
)
|
||||
|
||||
var _ = Describe("ABI files", func() {
|
||||
|
@ -1,9 +1,9 @@
|
||||
package geth
|
||||
|
||||
import (
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
)
|
||||
|
||||
func CalcUnclesReward(block core.Block, uncles []*types.Header) float64 {
|
||||
|
@ -5,12 +5,12 @@ import (
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||
)
|
||||
|
||||
type FakeGethClient struct {
|
||||
|
@ -7,13 +7,13 @@ import (
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth/node"
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth/node"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
|
@ -8,9 +8,9 @@ import (
|
||||
"context"
|
||||
"math/big"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -50,7 +50,7 @@ func (blockchain *Blockchain) GetAttributes(contract core.Contract) (core.Contra
|
||||
for _, abiElement := range parsed.Methods {
|
||||
if (len(abiElement.Outputs) > 0) && (len(abiElement.Inputs) == 0) && abiElement.Const {
|
||||
attributeType := abiElement.Outputs[0].Type.String()
|
||||
contractAttributes = append(contractAttributes, core.ContractAttribute{abiElement.Name, attributeType})
|
||||
contractAttributes = append(contractAttributes, core.ContractAttribute{Name: abiElement.Name, Type: attributeType})
|
||||
}
|
||||
}
|
||||
sort.Sort(contractAttributes)
|
||||
|
@ -3,10 +3,10 @@ package geth
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
)
|
||||
|
||||
func ToCoreLogs(gethLogs []types.Log) []core.Log {
|
||||
|
@ -3,13 +3,13 @@ package geth_test
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||
)
|
||||
|
||||
var _ = Describe("Conversion of GethLog to core.Log", func() {
|
||||
|
@ -5,10 +5,10 @@ import (
|
||||
|
||||
"strconv"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
)
|
||||
|
||||
func Info(client *rpc.Client) core.Node {
|
||||
|
@ -5,10 +5,10 @@ import (
|
||||
|
||||
"bytes"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
)
|
||||
|
||||
func BigTo64(n *big.Int) int64 {
|
||||
|
@ -3,13 +3,13 @@ package geth_test
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||
)
|
||||
|
||||
var _ = Describe("Conversion of GethReceipt to core.Receipt", func() {
|
||||
|
13
pkg/graphql_server/graphql_server_suite_test.go
Normal file
13
pkg/graphql_server/graphql_server_suite_test.go
Normal file
@ -0,0 +1,13 @@
|
||||
package graphql_server_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestGraphqlServer(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "GraphqlServer Suite")
|
||||
}
|
162
pkg/graphql_server/schema.go
Normal file
162
pkg/graphql_server/schema.go
Normal file
@ -0,0 +1,162 @@
|
||||
package graphql_server
|
||||
|
||||
import (
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
||||
)
|
||||
|
||||
var Schema = `
|
||||
schema {
|
||||
query: Query
|
||||
}
|
||||
type Query {
|
||||
logFilter(name: String!): LogFilter
|
||||
watchedEvents(name: String!): WatchedEventList
|
||||
}
|
||||
|
||||
type LogFilter {
|
||||
name: String!
|
||||
fromBlock: Int
|
||||
toBlock: Int
|
||||
address: String!
|
||||
topics: [String]!
|
||||
}
|
||||
|
||||
type WatchedEventList{
|
||||
total: Int!
|
||||
watchedEvents: [WatchedEvent]!
|
||||
}
|
||||
|
||||
type WatchedEvent {
|
||||
name: String!
|
||||
blockNumber: Int!
|
||||
address: String!
|
||||
tx_hash: String!
|
||||
topic0: String!
|
||||
topic1: String!
|
||||
topic2: String!
|
||||
topic3: String!
|
||||
data: String!
|
||||
}
|
||||
`
|
||||
|
||||
type Resolver struct {
|
||||
repository repositories.Repository
|
||||
}
|
||||
|
||||
func NewResolver(repository repositories.Repository) *Resolver {
|
||||
return &Resolver{repository: repository}
|
||||
}
|
||||
|
||||
func (r *Resolver) LogFilter(args struct {
|
||||
Name string
|
||||
}) (*logFilterResolver, error) {
|
||||
logFilter, err := r.repository.GetFilter(args.Name)
|
||||
if err != nil {
|
||||
return &logFilterResolver{}, err
|
||||
}
|
||||
return &logFilterResolver{&logFilter}, nil
|
||||
}
|
||||
|
||||
type logFilterResolver struct {
|
||||
lf *filters.LogFilter
|
||||
}
|
||||
|
||||
func (lfr *logFilterResolver) Name() string {
|
||||
return lfr.lf.Name
|
||||
}
|
||||
|
||||
func (lfr *logFilterResolver) FromBlock() *int32 {
|
||||
fromBlock := int32(lfr.lf.FromBlock)
|
||||
return &fromBlock
|
||||
}
|
||||
|
||||
func (lfr *logFilterResolver) ToBlock() *int32 {
|
||||
toBlock := int32(lfr.lf.ToBlock)
|
||||
return &toBlock
|
||||
}
|
||||
|
||||
func (lfr *logFilterResolver) Address() string {
|
||||
return lfr.lf.Address
|
||||
}
|
||||
|
||||
func (lfr *logFilterResolver) Topics() []*string {
|
||||
var topics = make([]*string, 4)
|
||||
for i := range topics {
|
||||
if lfr.lf.Topics[i] != "" {
|
||||
topics[i] = &lfr.lf.Topics[i]
|
||||
}
|
||||
}
|
||||
return topics
|
||||
}
|
||||
|
||||
func (r *Resolver) WatchedEvents(args struct {
|
||||
Name string
|
||||
}) (*watchedEventsResolver, error) {
|
||||
watchedEvents, err := r.repository.GetWatchedEvents(args.Name)
|
||||
if err != nil {
|
||||
return &watchedEventsResolver{}, err
|
||||
}
|
||||
return &watchedEventsResolver{watchedEvents: watchedEvents}, err
|
||||
}
|
||||
|
||||
type watchedEventsResolver struct {
|
||||
watchedEvents []*core.WatchedEvent
|
||||
}
|
||||
|
||||
func (wesr watchedEventsResolver) WatchedEvents() []*watchedEventResolver {
|
||||
return resolveWatchedEvents(wesr.watchedEvents)
|
||||
}
|
||||
|
||||
func (wesr watchedEventsResolver) Total() int32 {
|
||||
return int32(len(wesr.watchedEvents))
|
||||
}
|
||||
|
||||
func resolveWatchedEvents(watchedEvents []*core.WatchedEvent) []*watchedEventResolver {
|
||||
watchedEventResolvers := make([]*watchedEventResolver, 0)
|
||||
for _, watchedEvent := range watchedEvents {
|
||||
watchedEventResolvers = append(watchedEventResolvers, &watchedEventResolver{watchedEvent})
|
||||
}
|
||||
return watchedEventResolvers
|
||||
}
|
||||
|
||||
type watchedEventResolver struct {
|
||||
we *core.WatchedEvent
|
||||
}
|
||||
|
||||
func (wer watchedEventResolver) Name() string {
|
||||
return wer.we.Name
|
||||
}
|
||||
|
||||
func (wer watchedEventResolver) BlockNumber() int32 {
|
||||
return int32(wer.we.BlockNumber)
|
||||
}
|
||||
|
||||
func (wer watchedEventResolver) Address() string {
|
||||
return wer.we.Address
|
||||
}
|
||||
|
||||
func (wer watchedEventResolver) TxHash() string {
|
||||
return wer.we.TxHash
|
||||
}
|
||||
|
||||
func (wer watchedEventResolver) Topic0() string {
|
||||
return wer.we.Topic0
|
||||
}
|
||||
|
||||
func (wer watchedEventResolver) Topic1() string {
|
||||
return wer.we.Topic1
|
||||
}
|
||||
|
||||
func (wer watchedEventResolver) Topic2() string {
|
||||
return wer.we.Topic2
|
||||
}
|
||||
|
||||
func (wer watchedEventResolver) Topic3() string {
|
||||
return wer.we.Topic3
|
||||
}
|
||||
|
||||
func (wer watchedEventResolver) Data() string {
|
||||
return wer.we.Data
|
||||
}
|
168
pkg/graphql_server/schema_test.go
Normal file
168
pkg/graphql_server/schema_test.go
Normal file
@ -0,0 +1,168 @@
|
||||
package graphql_server_test
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/neelance/graphql-go"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/config"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/graphql_server"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/postgres"
|
||||
)
|
||||
|
||||
func formatJSON(data []byte) []byte {
|
||||
var v interface{}
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
log.Fatalf("invalid JSON: %s", err)
|
||||
}
|
||||
formatted, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return formatted
|
||||
}
|
||||
|
||||
var _ = Describe("GraphQL", func() {
|
||||
var cfg config.Config
|
||||
var repository repositories.Repository
|
||||
|
||||
BeforeEach(func() {
|
||||
|
||||
cfg, _ = config.NewConfig("private")
|
||||
node := core.Node{GenesisBlock: "GENESIS", NetworkId: 1, Id: "x123", ClientName: "geth"}
|
||||
repository = postgres.BuildRepository(node)
|
||||
e := repository.CreateFilter(filters.LogFilter{
|
||||
Name: "TestFilter1",
|
||||
FromBlock: 1,
|
||||
ToBlock: 10,
|
||||
Address: "0x123456789",
|
||||
Topics: core.Topics{0: "topic=1", 2: "topic=2"},
|
||||
})
|
||||
if e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
f, e := repository.GetFilter("TestFilter1")
|
||||
if e != nil {
|
||||
log.Println(f)
|
||||
log.Fatal(e)
|
||||
}
|
||||
|
||||
matchingEvent := core.Log{
|
||||
BlockNumber: 5,
|
||||
TxHash: "0xTX1",
|
||||
Address: "0x123456789",
|
||||
Topics: core.Topics{0: "topic=1", 2: "topic=2"},
|
||||
Index: 0,
|
||||
Data: "0xDATADATADATA",
|
||||
}
|
||||
nonMatchingEvent := core.Log{
|
||||
BlockNumber: 5,
|
||||
TxHash: "0xTX2",
|
||||
Address: "0xOTHERADDRESS",
|
||||
Topics: core.Topics{0: "topic=1", 2: "topic=2"},
|
||||
Index: 0,
|
||||
Data: "0xDATADATADATA",
|
||||
}
|
||||
e = repository.CreateLogs([]core.Log{matchingEvent, nonMatchingEvent})
|
||||
if e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
})
|
||||
|
||||
It("Queries example schema for specific log filter", func() {
|
||||
var variables map[string]interface{}
|
||||
r := graphql_server.NewResolver(repository)
|
||||
var schema = graphql.MustParseSchema(graphql_server.Schema, r)
|
||||
response := schema.Exec(context.Background(),
|
||||
`{
|
||||
logFilter(name: "TestFilter1") {
|
||||
name
|
||||
fromBlock
|
||||
toBlock
|
||||
address
|
||||
topics
|
||||
}
|
||||
}`,
|
||||
"",
|
||||
variables)
|
||||
expected := `{
|
||||
"logFilter": {
|
||||
"name": "TestFilter1",
|
||||
"fromBlock": 1,
|
||||
"toBlock": 10,
|
||||
"address": "0x123456789",
|
||||
"topics": ["topic=1", null, "topic=2", null]
|
||||
}
|
||||
}`
|
||||
var v interface{}
|
||||
if len(response.Errors) != 0 {
|
||||
log.Fatal(response.Errors)
|
||||
}
|
||||
err := json.Unmarshal(response.Data, &v)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
a := formatJSON(response.Data)
|
||||
e := formatJSON([]byte(expected))
|
||||
Expect(a).To(Equal(e))
|
||||
})
|
||||
|
||||
It("Queries example schema for specific watched event log", func() {
|
||||
var variables map[string]interface{}
|
||||
|
||||
r := graphql_server.NewResolver(repository)
|
||||
var schema = graphql.MustParseSchema(graphql_server.Schema, r)
|
||||
response := schema.Exec(context.Background(),
|
||||
`{
|
||||
watchedEvents(name: "TestFilter1") {
|
||||
total
|
||||
watchedEvents{
|
||||
name
|
||||
blockNumber
|
||||
address
|
||||
tx_hash
|
||||
topic0
|
||||
topic1
|
||||
topic2
|
||||
topic3
|
||||
data
|
||||
}
|
||||
}
|
||||
}`,
|
||||
"",
|
||||
variables)
|
||||
expected := `{
|
||||
"watchedEvents":
|
||||
{
|
||||
"total": 1,
|
||||
"watchedEvents": [
|
||||
{"name":"TestFilter1",
|
||||
"blockNumber": 5,
|
||||
"address": "0x123456789",
|
||||
"tx_hash": "0xTX1",
|
||||
"topic0": "topic=1",
|
||||
"topic1": "",
|
||||
"topic2": "topic=2",
|
||||
"topic3": "",
|
||||
"data": "0xDATADATADATA"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
var v interface{}
|
||||
if len(response.Errors) != 0 {
|
||||
log.Fatal(response.Errors)
|
||||
}
|
||||
err := json.Unmarshal(response.Data, &v)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
a := formatJSON(response.Data)
|
||||
e := formatJSON([]byte(expected))
|
||||
Expect(a).To(Equal(e))
|
||||
})
|
||||
})
|
@ -21,7 +21,7 @@ var _ = Describe("Populating blocks", func() {
|
||||
repository.CreateOrUpdateBlock(core.Block{Number: 2})
|
||||
|
||||
blocksAdded := history.PopulateMissingBlocks(blockchain, repository, 1)
|
||||
_, err := repository.FindBlockByNumber(1)
|
||||
_, err := repository.GetBlock(1)
|
||||
|
||||
Expect(blocksAdded).To(Equal(1))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@ -54,15 +54,15 @@ var _ = Describe("Populating blocks", func() {
|
||||
|
||||
Expect(blocksAdded).To(Equal(3))
|
||||
Expect(repository.BlockCount()).To(Equal(11))
|
||||
_, err := repository.FindBlockByNumber(4)
|
||||
_, err := repository.GetBlock(4)
|
||||
Expect(err).To(HaveOccurred())
|
||||
_, err = repository.FindBlockByNumber(5)
|
||||
_, err = repository.GetBlock(5)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = repository.FindBlockByNumber(8)
|
||||
_, err = repository.GetBlock(8)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = repository.FindBlockByNumber(10)
|
||||
_, err = repository.GetBlock(10)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = repository.FindBlockByNumber(13)
|
||||
_, err = repository.GetBlock(13)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
|
@ -36,7 +36,7 @@ var _ = Describe("Blocks validator", func() {
|
||||
})
|
||||
|
||||
It("returns the window size", func() {
|
||||
window := history.ValidationWindow{1, 3}
|
||||
window := history.ValidationWindow{LowerBound: 1, UpperBound: 3}
|
||||
Expect(window.Size()).To(Equal(2))
|
||||
})
|
||||
|
||||
@ -51,15 +51,15 @@ var _ = Describe("Blocks validator", func() {
|
||||
|
||||
validator := history.NewBlockValidator(blockchain, repository, 2)
|
||||
window := validator.ValidateBlocks()
|
||||
Expect(window).To(Equal(history.ValidationWindow{5, 7}))
|
||||
Expect(window).To(Equal(history.ValidationWindow{LowerBound: 5, UpperBound: 7}))
|
||||
Expect(repository.BlockCount()).To(Equal(2))
|
||||
Expect(repository.CreateOrUpdateBlockCallCount).To(Equal(2))
|
||||
})
|
||||
|
||||
It("logs window message", func() {
|
||||
expectedMessage := &bytes.Buffer{}
|
||||
window := history.ValidationWindow{5, 7}
|
||||
history.ParsedWindowTemplate.Execute(expectedMessage, history.ValidationWindow{5, 7})
|
||||
window := history.ValidationWindow{LowerBound: 5, UpperBound: 7}
|
||||
history.ParsedWindowTemplate.Execute(expectedMessage, history.ValidationWindow{LowerBound: 5, UpperBound: 7})
|
||||
|
||||
blockchain := fakes.NewBlockchainWithBlocks([]core.Block{})
|
||||
repository := inmemory.NewInMemory()
|
||||
|
@ -23,7 +23,15 @@ type InMemory struct {
|
||||
CreateOrUpdateBlockCallCount int
|
||||
}
|
||||
|
||||
func (repository *InMemory) AddFilter(filter filters.LogFilter) error {
|
||||
func (repository *InMemory) GetWatchedEvents(name string) ([]*core.WatchedEvent, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (repository *InMemory) GetFilter(name string) (filters.LogFilter, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (repository *InMemory) CreateFilter(filter filters.LogFilter) error {
|
||||
key := filter.Name
|
||||
if _, ok := repository.logFilters[key]; ok || key == "" {
|
||||
return errors.New("filter name not unique")
|
||||
@ -43,7 +51,7 @@ func NewInMemory() *InMemory {
|
||||
}
|
||||
}
|
||||
|
||||
func (repository *InMemory) FindReceipt(txHash string) (core.Receipt, error) {
|
||||
func (repository *InMemory) GetReceipt(txHash string) (core.Receipt, error) {
|
||||
if receipt, ok := repository.receipts[txHash]; ok {
|
||||
return receipt, nil
|
||||
}
|
||||
@ -62,14 +70,14 @@ func (repository *InMemory) SetBlocksStatus(chainHead int64) {
|
||||
|
||||
func (repository *InMemory) CreateLogs(logs []core.Log) error {
|
||||
for _, log := range logs {
|
||||
key := fmt.Sprintf("%s%s", log.BlockNumber, log.Index)
|
||||
key := fmt.Sprintf("%d%d", log.BlockNumber, log.Index)
|
||||
var logs []core.Log
|
||||
repository.logs[key] = append(logs, log)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repository *InMemory) FindLogs(address string, blockNumber int64) []core.Log {
|
||||
func (repository *InMemory) GetLogs(address string, blockNumber int64) []core.Log {
|
||||
var matchingLogs []core.Log
|
||||
for _, logs := range repository.logs {
|
||||
for _, log := range logs {
|
||||
@ -91,7 +99,7 @@ func (repository *InMemory) ContractExists(contractHash string) bool {
|
||||
return present
|
||||
}
|
||||
|
||||
func (repository *InMemory) FindContract(contractHash string) (core.Contract, error) {
|
||||
func (repository *InMemory) GetContract(contractHash string) (core.Contract, error) {
|
||||
contract, ok := repository.contracts[contractHash]
|
||||
if !ok {
|
||||
return core.Contract{}, repositories.ErrContractDoesNotExist(contractHash)
|
||||
@ -130,7 +138,7 @@ func (repository *InMemory) BlockCount() int {
|
||||
return len(repository.blocks)
|
||||
}
|
||||
|
||||
func (repository *InMemory) FindBlockByNumber(blockNumber int64) (core.Block, error) {
|
||||
func (repository *InMemory) GetBlock(blockNumber int64) (core.Block, error) {
|
||||
if block, ok := repository.blocks[blockNumber]; ok {
|
||||
return block, nil
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ func (db DB) MissingBlockNumbers(startingBlockNumber int64, highestBlockNumber i
|
||||
return numbers
|
||||
}
|
||||
|
||||
func (db DB) FindBlockByNumber(blockNumber int64) (core.Block, error) {
|
||||
func (db DB) GetBlock(blockNumber int64) (core.Block, error) {
|
||||
blockRows := db.DB.QueryRowx(
|
||||
`SELECT id,
|
||||
number,
|
||||
|
@ -36,7 +36,7 @@ var _ = Describe("Saving blocks", func() {
|
||||
}
|
||||
repositoryTwo := postgres.BuildRepository(nodeTwo)
|
||||
|
||||
_, err := repositoryTwo.FindBlockByNumber(123)
|
||||
_, err := repositoryTwo.GetBlock(123)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
@ -74,7 +74,7 @@ var _ = Describe("Saving blocks", func() {
|
||||
|
||||
repository.CreateOrUpdateBlock(block)
|
||||
|
||||
savedBlock, err := repository.FindBlockByNumber(blockNumber)
|
||||
savedBlock, err := repository.GetBlock(blockNumber)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(savedBlock.Reward).To(Equal(blockReward))
|
||||
Expect(savedBlock.Difficulty).To(Equal(difficulty))
|
||||
@ -93,7 +93,7 @@ var _ = Describe("Saving blocks", func() {
|
||||
})
|
||||
|
||||
It("does not find a block when searching for a number that does not exist", func() {
|
||||
_, err := repository.FindBlockByNumber(111)
|
||||
_, err := repository.GetBlock(111)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
@ -106,7 +106,7 @@ var _ = Describe("Saving blocks", func() {
|
||||
|
||||
repository.CreateOrUpdateBlock(block)
|
||||
|
||||
savedBlock, _ := repository.FindBlockByNumber(123)
|
||||
savedBlock, _ := repository.GetBlock(123)
|
||||
Expect(len(savedBlock.Transactions)).To(Equal(1))
|
||||
})
|
||||
|
||||
@ -118,7 +118,7 @@ var _ = Describe("Saving blocks", func() {
|
||||
|
||||
repository.CreateOrUpdateBlock(block)
|
||||
|
||||
savedBlock, _ := repository.FindBlockByNumber(123)
|
||||
savedBlock, _ := repository.GetBlock(123)
|
||||
Expect(len(savedBlock.Transactions)).To(Equal(2))
|
||||
})
|
||||
|
||||
@ -138,7 +138,7 @@ var _ = Describe("Saving blocks", func() {
|
||||
repository.CreateOrUpdateBlock(blockOne)
|
||||
repository.CreateOrUpdateBlock(blockTwo)
|
||||
|
||||
savedBlock, _ := repository.FindBlockByNumber(123)
|
||||
savedBlock, _ := repository.GetBlock(123)
|
||||
Expect(len(savedBlock.Transactions)).To(Equal(2))
|
||||
Expect(savedBlock.Transactions[0].Hash).To(Equal("x678"))
|
||||
Expect(savedBlock.Transactions[1].Hash).To(Equal("x9ab"))
|
||||
@ -163,8 +163,8 @@ var _ = Describe("Saving blocks", func() {
|
||||
|
||||
repository.CreateOrUpdateBlock(blockOne)
|
||||
repositoryTwo.CreateOrUpdateBlock(blockTwo)
|
||||
retrievedBlockOne, _ := repository.FindBlockByNumber(123)
|
||||
retrievedBlockTwo, _ := repositoryTwo.FindBlockByNumber(123)
|
||||
retrievedBlockOne, _ := repository.GetBlock(123)
|
||||
retrievedBlockTwo, _ := repositoryTwo.GetBlock(123)
|
||||
|
||||
Expect(retrievedBlockOne.Transactions[0].Hash).To(Equal("x123"))
|
||||
Expect(retrievedBlockTwo.Transactions[0].Hash).To(Equal("x678"))
|
||||
@ -196,7 +196,7 @@ var _ = Describe("Saving blocks", func() {
|
||||
|
||||
repository.CreateOrUpdateBlock(block)
|
||||
|
||||
savedBlock, _ := repository.FindBlockByNumber(123)
|
||||
savedBlock, _ := repository.GetBlock(123)
|
||||
Expect(len(savedBlock.Transactions)).To(Equal(1))
|
||||
savedTransaction := savedBlock.Transactions[0]
|
||||
Expect(savedTransaction.Data).To(Equal(transaction.Data))
|
||||
@ -271,10 +271,10 @@ var _ = Describe("Saving blocks", func() {
|
||||
|
||||
repository.SetBlocksStatus(int64(blockNumberOfChainHead))
|
||||
|
||||
blockOne, err := repository.FindBlockByNumber(1)
|
||||
blockOne, err := repository.GetBlock(1)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(blockOne.IsFinal).To(Equal(true))
|
||||
blockTwo, err := repository.FindBlockByNumber(24)
|
||||
blockTwo, err := repository.GetBlock(24)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(blockTwo.IsFinal).To(BeFalse())
|
||||
})
|
||||
|
@ -36,7 +36,7 @@ func (db DB) ContractExists(contractHash string) bool {
|
||||
return exists
|
||||
}
|
||||
|
||||
func (db DB) FindContract(contractHash string) (core.Contract, error) {
|
||||
func (db DB) GetContract(contractHash string) (core.Contract, error) {
|
||||
var hash string
|
||||
var abi string
|
||||
contract := db.DB.QueryRow(
|
||||
|
@ -25,7 +25,7 @@ var _ = Describe("Creating contracts", func() {
|
||||
It("returns the contract when it exists", func() {
|
||||
repository.CreateContract(core.Contract{Hash: "x123"})
|
||||
|
||||
contract, err := repository.FindContract("x123")
|
||||
contract, err := repository.GetContract("x123")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(contract.Hash).To(Equal("x123"))
|
||||
|
||||
@ -34,13 +34,13 @@ var _ = Describe("Creating contracts", func() {
|
||||
})
|
||||
|
||||
It("returns err if contract does not exist", func() {
|
||||
_, err := repository.FindContract("x123")
|
||||
_, err := repository.GetContract("x123")
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("returns empty array when no transactions 'To' a contract", func() {
|
||||
repository.CreateContract(core.Contract{Hash: "x123"})
|
||||
contract, err := repository.FindContract("x123")
|
||||
contract, err := repository.GetContract("x123")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(contract.Transactions).To(BeEmpty())
|
||||
})
|
||||
@ -59,7 +59,7 @@ var _ = Describe("Creating contracts", func() {
|
||||
blockRepository.CreateOrUpdateBlock(block)
|
||||
|
||||
repository.CreateContract(core.Contract{Hash: "x123"})
|
||||
contract, err := repository.FindContract("x123")
|
||||
contract, err := repository.GetContract("x123")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
sort.Slice(contract.Transactions, func(i, j int) bool {
|
||||
return contract.Transactions[i].Hash < contract.Transactions[j].Hash
|
||||
@ -76,7 +76,7 @@ var _ = Describe("Creating contracts", func() {
|
||||
Abi: "{\"some\": \"json\"}",
|
||||
Hash: "x123",
|
||||
})
|
||||
contract, err := repository.FindContract("x123")
|
||||
contract, err := repository.GetContract("x123")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(contract.Abi).To(Equal("{\"some\": \"json\"}"))
|
||||
})
|
||||
@ -90,7 +90,7 @@ var _ = Describe("Creating contracts", func() {
|
||||
Abi: "{\"some\": \"different json\"}",
|
||||
Hash: "x123",
|
||||
})
|
||||
contract, err := repository.FindContract("x123")
|
||||
contract, err := repository.GetContract("x123")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(contract.Abi).To(Equal("{\"some\": \"different json\"}"))
|
||||
})
|
||||
|
@ -1,8 +1,16 @@
|
||||
package postgres
|
||||
|
||||
import "github.com/vulcanize/vulcanizedb/pkg/filters"
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
func (db DB) AddFilter(query filters.LogFilter) error {
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
||||
)
|
||||
|
||||
func (db DB) CreateFilter(query filters.LogFilter) error {
|
||||
_, err := db.DB.Exec(
|
||||
`INSERT INTO log_filters
|
||||
(name, from_block, to_block, address, topic0, topic1, topic2, topic3)
|
||||
@ -13,3 +21,53 @@ func (db DB) AddFilter(query filters.LogFilter) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db DB) GetFilter(name string) (filters.LogFilter, error) {
|
||||
lf := DBLogFilter{}
|
||||
err := db.DB.Get(&lf,
|
||||
`SELECT
|
||||
id,
|
||||
name,
|
||||
from_block,
|
||||
to_block,
|
||||
address,
|
||||
json_build_array(topic0, topic1, topic2, topic3) AS topics
|
||||
FROM log_filters
|
||||
WHERE name = $1`, name)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case sql.ErrNoRows:
|
||||
return filters.LogFilter{}, repositories.ErrFilterDoesNotExist(name)
|
||||
default:
|
||||
return filters.LogFilter{}, err
|
||||
}
|
||||
}
|
||||
dbLogFilterToCoreLogFilter(lf)
|
||||
return *lf.LogFilter, nil
|
||||
}
|
||||
|
||||
type DBTopics []*string
|
||||
|
||||
func (t *DBTopics) Scan(src interface{}) error {
|
||||
asBytes, ok := src.([]byte)
|
||||
if !ok {
|
||||
return error(errors.New("scan source was not []byte"))
|
||||
}
|
||||
json.Unmarshal(asBytes, &t)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type DBLogFilter struct {
|
||||
ID int
|
||||
*filters.LogFilter
|
||||
Topics DBTopics
|
||||
}
|
||||
|
||||
func dbLogFilterToCoreLogFilter(lf DBLogFilter) {
|
||||
for i, v := range lf.Topics {
|
||||
if v != nil {
|
||||
lf.LogFilter.Topics[i] = *v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ var _ = Describe("Logs Repository", func() {
|
||||
"",
|
||||
},
|
||||
}
|
||||
err := repository.AddFilter(logFilter)
|
||||
err := repository.CreateFilter(logFilter)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
@ -54,8 +54,52 @@ var _ = Describe("Logs Repository", func() {
|
||||
"",
|
||||
},
|
||||
}
|
||||
err := repository.AddFilter(logFilter)
|
||||
err := repository.CreateFilter(logFilter)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("gets a log filter", func() {
|
||||
|
||||
logFilter1 := filters.LogFilter{
|
||||
Name: "TestFilter1",
|
||||
FromBlock: 1,
|
||||
ToBlock: 2,
|
||||
Address: "0x8888f1f195afa192cfee860698584c030f4c9db1",
|
||||
Topics: core.Topics{
|
||||
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
|
||||
"",
|
||||
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
|
||||
"",
|
||||
},
|
||||
}
|
||||
err := repository.CreateFilter(logFilter1)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
logFilter2 := filters.LogFilter{
|
||||
Name: "TestFilter2",
|
||||
FromBlock: 10,
|
||||
ToBlock: 20,
|
||||
Address: "0x8888f1f195afa192cfee860698584c030f4c9db1",
|
||||
Topics: core.Topics{
|
||||
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
|
||||
"",
|
||||
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
|
||||
"",
|
||||
},
|
||||
}
|
||||
err = repository.CreateFilter(logFilter2)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
logFilter1, err = repository.GetFilter("TestFilter1")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(logFilter1).To(Equal(logFilter1))
|
||||
logFilter1, err = repository.GetFilter("TestFilter1")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(logFilter2).To(Equal(logFilter2))
|
||||
})
|
||||
|
||||
It("returns ErrFilterDoesNotExist error when log does not exist", func() {
|
||||
_, err := repository.GetFilter("TestFilter1")
|
||||
Expect(err).To(Equal(repositories.ErrFilterDoesNotExist("TestFilter1")))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -26,7 +26,7 @@ func (db DB) CreateLogs(logs []core.Log) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db DB) FindLogs(address string, blockNumber int64) []core.Log {
|
||||
func (db DB) GetLogs(address string, blockNumber int64) []core.Log {
|
||||
logRows, _ := db.DB.Query(
|
||||
`SELECT block_number,
|
||||
address,
|
||||
|
@ -35,7 +35,7 @@ var _ = Describe("Logs Repository", func() {
|
||||
}},
|
||||
)
|
||||
|
||||
log := repository.FindLogs("x123", 1)
|
||||
log := repository.GetLogs("x123", 1)
|
||||
|
||||
Expect(log).NotTo(BeNil())
|
||||
Expect(log[0].BlockNumber).To(Equal(int64(1)))
|
||||
@ -49,7 +49,7 @@ var _ = Describe("Logs Repository", func() {
|
||||
})
|
||||
|
||||
It("returns nil if log does not exist", func() {
|
||||
log := repository.FindLogs("x123", 1)
|
||||
log := repository.GetLogs("x123", 1)
|
||||
Expect(log).To(BeNil())
|
||||
})
|
||||
|
||||
@ -82,7 +82,7 @@ var _ = Describe("Logs Repository", func() {
|
||||
}},
|
||||
)
|
||||
|
||||
log := repository.FindLogs("x123", 1)
|
||||
log := repository.GetLogs("x123", 1)
|
||||
|
||||
type logIndex struct {
|
||||
blockNumber int64
|
||||
@ -168,7 +168,7 @@ var _ = Describe("Logs Repository", func() {
|
||||
block := core.Block{Transactions: []core.Transaction{transaction}}
|
||||
err := blockRepository.CreateOrUpdateBlock(block)
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
retrievedLogs := repository.FindLogs("0x99041f808d598b782d5a3e498681c2452a31da08", 4745407)
|
||||
retrievedLogs := repository.GetLogs("0x99041f808d598b782d5a3e498681c2452a31da08", 4745407)
|
||||
|
||||
expected := logs[1:]
|
||||
Expect(retrievedLogs).To(Equal(expected))
|
||||
|
@ -94,7 +94,7 @@ var _ = Describe("Postgres repository", func() {
|
||||
repository, _ := postgres.NewDB(cfg.Database, node)
|
||||
|
||||
err1 := repository.CreateOrUpdateBlock(badBlock)
|
||||
savedBlock, err2 := repository.FindBlockByNumber(123)
|
||||
savedBlock, err2 := repository.GetBlock(123)
|
||||
|
||||
Expect(err1).To(HaveOccurred())
|
||||
Expect(err2).To(HaveOccurred())
|
||||
@ -129,7 +129,7 @@ var _ = Describe("Postgres repository", func() {
|
||||
repository, _ := postgres.NewDB(cfg.Database, node)
|
||||
|
||||
err := repository.CreateLogs([]core.Log{badLog})
|
||||
savedBlock := repository.FindLogs("x123", 1)
|
||||
savedBlock := repository.GetLogs("x123", 1)
|
||||
|
||||
Expect(err).ToNot(BeNil())
|
||||
Expect(savedBlock).To(BeNil())
|
||||
@ -148,7 +148,7 @@ var _ = Describe("Postgres repository", func() {
|
||||
repository, _ := postgres.NewDB(cfg.Database, node)
|
||||
|
||||
err1 := repository.CreateOrUpdateBlock(block)
|
||||
savedBlock, err2 := repository.FindBlockByNumber(123)
|
||||
savedBlock, err2 := repository.GetBlock(123)
|
||||
|
||||
Expect(err1).To(HaveOccurred())
|
||||
Expect(err2).To(HaveOccurred())
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
||||
)
|
||||
|
||||
func (db DB) FindReceipt(txHash string) (core.Receipt, error) {
|
||||
func (db DB) GetReceipt(txHash string) (core.Receipt, error) {
|
||||
row := db.DB.QueryRow(
|
||||
`SELECT contract_address,
|
||||
tx_hash,
|
||||
|
@ -42,7 +42,7 @@ var _ = Describe("Logs Repository", func() {
|
||||
|
||||
block := core.Block{Transactions: []core.Transaction{transaction}}
|
||||
blockRepository.CreateOrUpdateBlock(block)
|
||||
receipt, err := repository.FindReceipt("0xe340558980f89d5f86045ac11e5cc34e4bcec20f9f1e2a427aa39d87114e8223")
|
||||
receipt, err := repository.GetReceipt("0xe340558980f89d5f86045ac11e5cc34e4bcec20f9f1e2a427aa39d87114e8223")
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
//Not currently serializing bloom logs
|
||||
@ -55,7 +55,7 @@ var _ = Describe("Logs Repository", func() {
|
||||
})
|
||||
|
||||
It("returns ErrReceiptDoesNotExist when receipt does not exist", func() {
|
||||
receipt, err := repository.FindReceipt("DOES NOT EXIST")
|
||||
receipt, err := repository.GetReceipt("DOES NOT EXIST")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(receipt).To(BeZero())
|
||||
})
|
||||
@ -76,7 +76,7 @@ var _ = Describe("Logs Repository", func() {
|
||||
}
|
||||
blockRepository.CreateOrUpdateBlock(block)
|
||||
|
||||
_, err := repository.FindReceipt(receipt.TxHash)
|
||||
_, err := repository.GetReceipt(receipt.TxHash)
|
||||
|
||||
Expect(err).To(Not(HaveOccurred()))
|
||||
})
|
||||
|
@ -1,32 +1,19 @@
|
||||
package postgres
|
||||
|
||||
type WatchedEventLog struct {
|
||||
Name string `json:"name"` // name
|
||||
BlockNumber int64 `json:"block_number" db:"block_number"` // block_number
|
||||
Address string `json:"address"` // address
|
||||
TxHash string `json:"tx_hash" db:"tx_hash"` // tx_hash
|
||||
Index int64 `json:"index"` // index
|
||||
Topic0 string `json:"topic0"` // topic0
|
||||
Topic1 string `json:"topic1"` // topic1
|
||||
Topic2 string `json:"topic2"` // topic2
|
||||
Topic3 string `json:"topic3"` // topic3
|
||||
Data string `json:"data"` // data
|
||||
}
|
||||
import (
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
)
|
||||
|
||||
type WatchedEventLogs interface {
|
||||
AllWatchedEventLogs() ([]*WatchedEventLog, error)
|
||||
}
|
||||
|
||||
func (db *DB) AllWatchedEventLogs() ([]*WatchedEventLog, error) {
|
||||
rows, err := db.DB.Queryx(`SELECT name, block_number, address, tx_hash, index, topic0, topic1, topic2, topic3, data FROM watched_event_logs`)
|
||||
func (db DB) GetWatchedEvents(name string) ([]*core.WatchedEvent, error) {
|
||||
rows, err := db.DB.Queryx(`SELECT name, block_number, address, tx_hash, index, topic0, topic1, topic2, topic3, data FROM watched_event_logs where name=$1`, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
lgs := make([]*WatchedEventLog, 0)
|
||||
lgs := make([]*core.WatchedEvent, 0)
|
||||
for rows.Next() {
|
||||
lg := new(WatchedEventLog)
|
||||
lg := new(core.WatchedEvent)
|
||||
err := rows.StructScan(lg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -27,7 +27,7 @@ var _ = Describe("Watched Events Repository", func() {
|
||||
postgres.ClearData(repository)
|
||||
})
|
||||
|
||||
It("retrieves watched logs that match the event filter", func() {
|
||||
It("retrieves watched event logs that match the event filter", func() {
|
||||
filter := filters.LogFilter{
|
||||
Name: "Filter1",
|
||||
FromBlock: 0,
|
||||
@ -45,7 +45,7 @@ var _ = Describe("Watched Events Repository", func() {
|
||||
Data: "",
|
||||
},
|
||||
}
|
||||
expectedWatchedEventLog := []*postgres.WatchedEventLog{
|
||||
expectedWatchedEventLog := []*core.WatchedEvent{
|
||||
{
|
||||
Name: "Filter1",
|
||||
BlockNumber: 0,
|
||||
@ -57,11 +57,57 @@ var _ = Describe("Watched Events Repository", func() {
|
||||
Data: "",
|
||||
},
|
||||
}
|
||||
err := repository.AddFilter(filter)
|
||||
err := repository.CreateFilter(filter)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = repository.CreateLogs(logs)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
matchingLogs, err := repository.AllWatchedEventLogs()
|
||||
matchingLogs, err := repository.GetWatchedEvents("Filter1")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(matchingLogs).To(Equal(expectedWatchedEventLog))
|
||||
|
||||
})
|
||||
|
||||
It("retrieves a watched event log by name", func() {
|
||||
filter := filters.LogFilter{
|
||||
Name: "Filter1",
|
||||
FromBlock: 0,
|
||||
ToBlock: 10,
|
||||
Address: "0x123",
|
||||
Topics: core.Topics{0: "event1=10", 2: "event3=hello"},
|
||||
}
|
||||
logs := []core.Log{
|
||||
{
|
||||
BlockNumber: 0,
|
||||
TxHash: "0x1",
|
||||
Address: "0x123",
|
||||
Topics: core.Topics{0: "event1=10", 2: "event3=hello"},
|
||||
Index: 0,
|
||||
Data: "",
|
||||
},
|
||||
{
|
||||
BlockNumber: 100,
|
||||
TxHash: "",
|
||||
Address: "",
|
||||
Topics: core.Topics{},
|
||||
Index: 0,
|
||||
Data: "",
|
||||
},
|
||||
}
|
||||
expectedWatchedEventLog := []*core.WatchedEvent{{
|
||||
Name: "Filter1",
|
||||
BlockNumber: 0,
|
||||
TxHash: "0x1",
|
||||
Address: "0x123",
|
||||
Topic0: "event1=10",
|
||||
Topic2: "event3=hello",
|
||||
Index: 0,
|
||||
Data: "",
|
||||
}}
|
||||
err := repository.CreateFilter(filter)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = repository.CreateLogs(logs)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
matchingLogs, err := repository.GetWatchedEvents("Filter1")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(matchingLogs).To(Equal(expectedWatchedEventLog))
|
||||
|
||||
|
@ -14,6 +14,7 @@ type Repository interface {
|
||||
LogsRepository
|
||||
ReceiptRepository
|
||||
FilterRepository
|
||||
WatchedEventsRepository
|
||||
}
|
||||
|
||||
var ErrBlockDoesNotExist = func(blockNumber int64) error {
|
||||
@ -22,7 +23,7 @@ var ErrBlockDoesNotExist = func(blockNumber int64) error {
|
||||
|
||||
type BlockRepository interface {
|
||||
CreateOrUpdateBlock(block core.Block) error
|
||||
FindBlockByNumber(blockNumber int64) (core.Block, error)
|
||||
GetBlock(blockNumber int64) (core.Block, error)
|
||||
MissingBlockNumbers(startingBlockNumber int64, endingBlockNumber int64) []int64
|
||||
SetBlocksStatus(chainHead int64)
|
||||
}
|
||||
@ -33,17 +34,22 @@ var ErrContractDoesNotExist = func(contractHash string) error {
|
||||
|
||||
type ContractRepository interface {
|
||||
CreateContract(contract core.Contract) error
|
||||
GetContract(contractHash string) (core.Contract, error)
|
||||
ContractExists(contractHash string) bool
|
||||
FindContract(contractHash string) (core.Contract, error)
|
||||
}
|
||||
|
||||
var ErrFilterDoesNotExist = func(name string) error {
|
||||
return errors.New(fmt.Sprintf("filter %s does not exist", name))
|
||||
}
|
||||
|
||||
type FilterRepository interface {
|
||||
AddFilter(filter filters.LogFilter) error
|
||||
CreateFilter(filter filters.LogFilter) error
|
||||
GetFilter(name string) (filters.LogFilter, error)
|
||||
}
|
||||
|
||||
type LogsRepository interface {
|
||||
FindLogs(address string, blockNumber int64) []core.Log
|
||||
CreateLogs(logs []core.Log) error
|
||||
GetLogs(address string, blockNumber int64) []core.Log
|
||||
}
|
||||
|
||||
var ErrReceiptDoesNotExist = func(txHash string) error {
|
||||
@ -51,5 +57,9 @@ var ErrReceiptDoesNotExist = func(txHash string) error {
|
||||
}
|
||||
|
||||
type ReceiptRepository interface {
|
||||
FindReceipt(txHash string) (core.Receipt, error)
|
||||
GetReceipt(txHash string) (core.Receipt, error)
|
||||
}
|
||||
|
||||
type WatchedEventsRepository interface {
|
||||
GetWatchedEvents(name string) ([]*core.WatchedEvent, error)
|
||||
}
|
||||
|
1
vendor/github.com/neelance/graphql-go/.gitignore
generated
vendored
Normal file
1
vendor/github.com/neelance/graphql-go/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
/internal/tests/testdata/graphql-js
|
24
vendor/github.com/neelance/graphql-go/LICENSE
generated
vendored
Normal file
24
vendor/github.com/neelance/graphql-go/LICENSE
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
Copyright (c) 2016 Richard Musiol. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
57
vendor/github.com/neelance/graphql-go/README.md
generated
vendored
Normal file
57
vendor/github.com/neelance/graphql-go/README.md
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
# graphql-go
|
||||
|
||||
[![Sourcegraph](https://sourcegraph.com/github.com/neelance/graphql-go/-/badge.svg)](https://sourcegraph.com/github.com/neelance/graphql-go?badge)
|
||||
[![Build Status](https://semaphoreci.com/api/v1/neelance/graphql-go/branches/master/badge.svg)](https://semaphoreci.com/neelance/graphql-go)
|
||||
[![GoDoc](https://godoc.org/github.com/neelance/graphql-go?status.svg)](https://godoc.org/github.com/neelance/graphql-go)
|
||||
|
||||
## Status
|
||||
|
||||
The project is under heavy development. It is stable enough so we use it in production at [Sourcegraph](https://sourcegraph.com), but expect changes.
|
||||
|
||||
## Goals
|
||||
|
||||
* [ ] full support of [GraphQL spec (October 2016)](https://facebook.github.io/graphql/)
|
||||
* [ ] propagation of `null` on resolver errors
|
||||
* [x] everything else
|
||||
* [x] minimal API
|
||||
* [x] support for context.Context and OpenTracing
|
||||
* [x] early error detection at application startup by type-checking if the given resolver matches the schema
|
||||
* [x] resolvers are purely based on method sets (e.g. it's up to you if you want to resolve a GraphQL interface with a Go interface or a Go struct)
|
||||
* [ ] nice error messages (no internal panics, even with an invalid schema or resolver; please file a bug if you see an internal panic)
|
||||
* [x] nice errors on resolver validation
|
||||
* [ ] nice errors on all invalid schemas
|
||||
* [ ] nice errors on all invalid queries
|
||||
* [x] panic handling (a panic in a resolver should not take down the whole app)
|
||||
* [x] parallel execution of resolvers
|
||||
|
||||
## (Some) Documentation
|
||||
|
||||
### Resolvers
|
||||
|
||||
A resolver must have one method for each field of the GraphQL type it resolves. The method name has to be [exported](https://golang.org/ref/spec#Exported_identifiers) and match the field's name in a non-case-sensitive way.
|
||||
|
||||
The method has up to two arguments:
|
||||
|
||||
- Optional `context.Context` argument.
|
||||
- Mandatory `*struct { ... }` argument if the corresponding GraphQL field has arguments. The names of the struct fields have to be [exported](https://golang.org/ref/spec#Exported_identifiers) and have to match the names of the GraphQL arguments in a non-case-sensitive way.
|
||||
|
||||
The method has up to two results:
|
||||
|
||||
- The GraphQL field's value as determined by the resolver.
|
||||
- Optional `error` result.
|
||||
|
||||
Example for a simple resolver method:
|
||||
|
||||
```go
|
||||
func (r *helloWorldResolver) Hello() string {
|
||||
return "Hello world!"
|
||||
}
|
||||
```
|
||||
|
||||
The following signature is also allowed:
|
||||
|
||||
```go
|
||||
func (r *helloWorldResolver) Hello(ctx context.Context) (string, error) {
|
||||
return "Hello world!", nil
|
||||
}
|
||||
```
|
41
vendor/github.com/neelance/graphql-go/errors/errors.go
generated
vendored
Normal file
41
vendor/github.com/neelance/graphql-go/errors/errors.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type QueryError struct {
|
||||
Message string `json:"message"`
|
||||
Locations []Location `json:"locations,omitempty"`
|
||||
Path []interface{} `json:"path,omitempty"`
|
||||
Rule string `json:"-"`
|
||||
ResolverError error `json:"-"`
|
||||
}
|
||||
|
||||
type Location struct {
|
||||
Line int `json:"line"`
|
||||
Column int `json:"column"`
|
||||
}
|
||||
|
||||
func (a Location) Before(b Location) bool {
|
||||
return a.Line < b.Line || (a.Line == b.Line && a.Column < b.Column)
|
||||
}
|
||||
|
||||
func Errorf(format string, a ...interface{}) *QueryError {
|
||||
return &QueryError{
|
||||
Message: fmt.Sprintf(format, a...),
|
||||
}
|
||||
}
|
||||
|
||||
func (err *QueryError) Error() string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
str := fmt.Sprintf("graphql: %s", err.Message)
|
||||
for _, loc := range err.Locations {
|
||||
str += fmt.Sprintf(" (line %d, column %d)", loc.Line, loc.Column)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
var _ error = &QueryError{}
|
64
vendor/github.com/neelance/graphql-go/example/starwars/server/server.go
generated
vendored
Normal file
64
vendor/github.com/neelance/graphql-go/example/starwars/server/server.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/neelance/graphql-go"
|
||||
"github.com/neelance/graphql-go/example/starwars"
|
||||
"github.com/neelance/graphql-go/relay"
|
||||
)
|
||||
|
||||
var schema *graphql.Schema
|
||||
|
||||
func init() {
|
||||
schema = graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{})
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(page)
|
||||
}))
|
||||
|
||||
http.Handle("/query", &relay.Handler{Schema: schema})
|
||||
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
|
||||
var page = []byte(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.10.2/graphiql.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.1.0/fetch.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react-dom.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.10.2/graphiql.js"></script>
|
||||
</head>
|
||||
<body style="width: 100%; height: 100%; margin: 0; overflow: hidden;">
|
||||
<div id="graphiql" style="height: 100vh;">Loading...</div>
|
||||
<script>
|
||||
function graphQLFetcher(graphQLParams) {
|
||||
return fetch("/query", {
|
||||
method: "post",
|
||||
body: JSON.stringify(graphQLParams),
|
||||
credentials: "include",
|
||||
}).then(function (response) {
|
||||
return response.text();
|
||||
}).then(function (responseBody) {
|
||||
try {
|
||||
return JSON.parse(responseBody);
|
||||
} catch (error) {
|
||||
return responseBody;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
React.createElement(GraphiQL, {fetcher: graphQLFetcher}),
|
||||
document.getElementById("graphiql")
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
647
vendor/github.com/neelance/graphql-go/example/starwars/starwars.go
generated
vendored
Normal file
647
vendor/github.com/neelance/graphql-go/example/starwars/starwars.go
generated
vendored
Normal file
@ -0,0 +1,647 @@
|
||||
// Package starwars provides a example schema and resolver based on Star Wars characters.
|
||||
//
|
||||
// Source: https://github.com/graphql/graphql.github.io/blob/source/site/_core/swapiSchema.js
|
||||
package starwars
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
graphql "github.com/neelance/graphql-go"
|
||||
)
|
||||
|
||||
var Schema = `
|
||||
schema {
|
||||
query: Query
|
||||
mutation: Mutation
|
||||
}
|
||||
# The query type, represents all of the entry points into our object graph
|
||||
type Query {
|
||||
hero(episode: Episode = NEWHOPE): Character
|
||||
reviews(episode: Episode!): [Review]!
|
||||
search(text: String!): [SearchResult]!
|
||||
character(id: ID!): Character
|
||||
droid(id: ID!): Droid
|
||||
human(id: ID!): Human
|
||||
starship(id: ID!): Starship
|
||||
}
|
||||
# The mutation type, represents all updates we can make to our data
|
||||
type Mutation {
|
||||
createReview(episode: Episode!, review: ReviewInput!): Review
|
||||
}
|
||||
# The episodes in the Star Wars trilogy
|
||||
enum Episode {
|
||||
# Star Wars Episode IV: A New Hope, released in 1977.
|
||||
NEWHOPE
|
||||
# Star Wars Episode V: The Empire Strikes Back, released in 1980.
|
||||
EMPIRE
|
||||
# Star Wars Episode VI: Return of the Jedi, released in 1983.
|
||||
JEDI
|
||||
}
|
||||
# A character from the Star Wars universe
|
||||
interface Character {
|
||||
# The ID of the character
|
||||
id: ID!
|
||||
# The name of the character
|
||||
name: String!
|
||||
# The friends of the character, or an empty list if they have none
|
||||
friends: [Character]
|
||||
# The friends of the character exposed as a connection with edges
|
||||
friendsConnection(first: Int, after: ID): FriendsConnection!
|
||||
# The movies this character appears in
|
||||
appearsIn: [Episode!]!
|
||||
}
|
||||
# Units of height
|
||||
enum LengthUnit {
|
||||
# The standard unit around the world
|
||||
METER
|
||||
# Primarily used in the United States
|
||||
FOOT
|
||||
}
|
||||
# A humanoid creature from the Star Wars universe
|
||||
type Human implements Character {
|
||||
# The ID of the human
|
||||
id: ID!
|
||||
# What this human calls themselves
|
||||
name: String!
|
||||
# Height in the preferred unit, default is meters
|
||||
height(unit: LengthUnit = METER): Float!
|
||||
# Mass in kilograms, or null if unknown
|
||||
mass: Float
|
||||
# This human's friends, or an empty list if they have none
|
||||
friends: [Character]
|
||||
# The friends of the human exposed as a connection with edges
|
||||
friendsConnection(first: Int, after: ID): FriendsConnection!
|
||||
# The movies this human appears in
|
||||
appearsIn: [Episode!]!
|
||||
# A list of starships this person has piloted, or an empty list if none
|
||||
starships: [Starship]
|
||||
}
|
||||
# An autonomous mechanical character in the Star Wars universe
|
||||
type Droid implements Character {
|
||||
# The ID of the droid
|
||||
id: ID!
|
||||
# What others call this droid
|
||||
name: String!
|
||||
# This droid's friends, or an empty list if they have none
|
||||
friends: [Character]
|
||||
# The friends of the droid exposed as a connection with edges
|
||||
friendsConnection(first: Int, after: ID): FriendsConnection!
|
||||
# The movies this droid appears in
|
||||
appearsIn: [Episode!]!
|
||||
# This droid's primary function
|
||||
primaryFunction: String
|
||||
}
|
||||
# A connection object for a character's friends
|
||||
type FriendsConnection {
|
||||
# The total number of friends
|
||||
totalCount: Int!
|
||||
# The edges for each of the character's friends.
|
||||
edges: [FriendsEdge]
|
||||
# A list of the friends, as a convenience when edges are not needed.
|
||||
friends: [Character]
|
||||
# Information for paginating this connection
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
# An edge object for a character's friends
|
||||
type FriendsEdge {
|
||||
# A cursor used for pagination
|
||||
cursor: ID!
|
||||
# The character represented by this friendship edge
|
||||
node: Character
|
||||
}
|
||||
# Information for paginating this connection
|
||||
type PageInfo {
|
||||
startCursor: ID
|
||||
endCursor: ID
|
||||
hasNextPage: Boolean!
|
||||
}
|
||||
# Represents a review for a movie
|
||||
type Review {
|
||||
# The number of stars this review gave, 1-5
|
||||
stars: Int!
|
||||
# Comment about the movie
|
||||
commentary: String
|
||||
}
|
||||
# The input object sent when someone is creating a new review
|
||||
input ReviewInput {
|
||||
# 0-5 stars
|
||||
stars: Int!
|
||||
# Comment about the movie, optional
|
||||
commentary: String
|
||||
}
|
||||
type Starship {
|
||||
# The ID of the starship
|
||||
id: ID!
|
||||
# The name of the starship
|
||||
name: String!
|
||||
# Length of the starship, along the longest axis
|
||||
length(unit: LengthUnit = METER): Float!
|
||||
}
|
||||
union SearchResult = Human | Droid | Starship
|
||||
`
|
||||
|
||||
type human struct {
|
||||
ID graphql.ID
|
||||
Name string
|
||||
Friends []graphql.ID
|
||||
AppearsIn []string
|
||||
Height float64
|
||||
Mass int
|
||||
Starships []graphql.ID
|
||||
}
|
||||
|
||||
var humans = []*human{
|
||||
{
|
||||
ID: "1000",
|
||||
Name: "Luke Skywalker",
|
||||
Friends: []graphql.ID{"1002", "1003", "2000", "2001"},
|
||||
AppearsIn: []string{"NEWHOPE", "EMPIRE", "JEDI"},
|
||||
Height: 1.72,
|
||||
Mass: 77,
|
||||
Starships: []graphql.ID{"3001", "3003"},
|
||||
},
|
||||
{
|
||||
ID: "1001",
|
||||
Name: "Darth Vader",
|
||||
Friends: []graphql.ID{"1004"},
|
||||
AppearsIn: []string{"NEWHOPE", "EMPIRE", "JEDI"},
|
||||
Height: 2.02,
|
||||
Mass: 136,
|
||||
Starships: []graphql.ID{"3002"},
|
||||
},
|
||||
{
|
||||
ID: "1002",
|
||||
Name: "Han Solo",
|
||||
Friends: []graphql.ID{"1000", "1003", "2001"},
|
||||
AppearsIn: []string{"NEWHOPE", "EMPIRE", "JEDI"},
|
||||
Height: 1.8,
|
||||
Mass: 80,
|
||||
Starships: []graphql.ID{"3000", "3003"},
|
||||
},
|
||||
{
|
||||
ID: "1003",
|
||||
Name: "Leia Organa",
|
||||
Friends: []graphql.ID{"1000", "1002", "2000", "2001"},
|
||||
AppearsIn: []string{"NEWHOPE", "EMPIRE", "JEDI"},
|
||||
Height: 1.5,
|
||||
Mass: 49,
|
||||
},
|
||||
{
|
||||
ID: "1004",
|
||||
Name: "Wilhuff Tarkin",
|
||||
Friends: []graphql.ID{"1001"},
|
||||
AppearsIn: []string{"NEWHOPE"},
|
||||
Height: 1.8,
|
||||
Mass: 0,
|
||||
},
|
||||
}
|
||||
|
||||
var humanData = make(map[graphql.ID]*human)
|
||||
|
||||
func init() {
|
||||
for _, h := range humans {
|
||||
humanData[h.ID] = h
|
||||
}
|
||||
}
|
||||
|
||||
type droid struct {
|
||||
ID graphql.ID
|
||||
Name string
|
||||
Friends []graphql.ID
|
||||
AppearsIn []string
|
||||
PrimaryFunction string
|
||||
}
|
||||
|
||||
var droids = []*droid{
|
||||
{
|
||||
ID: "2000",
|
||||
Name: "C-3PO",
|
||||
Friends: []graphql.ID{"1000", "1002", "1003", "2001"},
|
||||
AppearsIn: []string{"NEWHOPE", "EMPIRE", "JEDI"},
|
||||
PrimaryFunction: "Protocol",
|
||||
},
|
||||
{
|
||||
ID: "2001",
|
||||
Name: "R2-D2",
|
||||
Friends: []graphql.ID{"1000", "1002", "1003"},
|
||||
AppearsIn: []string{"NEWHOPE", "EMPIRE", "JEDI"},
|
||||
PrimaryFunction: "Astromech",
|
||||
},
|
||||
}
|
||||
|
||||
var droidData = make(map[graphql.ID]*droid)
|
||||
|
||||
func init() {
|
||||
for _, d := range droids {
|
||||
droidData[d.ID] = d
|
||||
}
|
||||
}
|
||||
|
||||
type starship struct {
|
||||
ID graphql.ID
|
||||
Name string
|
||||
Length float64
|
||||
}
|
||||
|
||||
var starships = []*starship{
|
||||
{
|
||||
ID: "3000",
|
||||
Name: "Millennium Falcon",
|
||||
Length: 34.37,
|
||||
},
|
||||
{
|
||||
ID: "3001",
|
||||
Name: "X-Wing",
|
||||
Length: 12.5,
|
||||
},
|
||||
{
|
||||
ID: "3002",
|
||||
Name: "TIE Advanced x1",
|
||||
Length: 9.2,
|
||||
},
|
||||
{
|
||||
ID: "3003",
|
||||
Name: "Imperial shuttle",
|
||||
Length: 20,
|
||||
},
|
||||
}
|
||||
|
||||
var starshipData = make(map[graphql.ID]*starship)
|
||||
|
||||
func init() {
|
||||
for _, s := range starships {
|
||||
starshipData[s.ID] = s
|
||||
}
|
||||
}
|
||||
|
||||
type review struct {
|
||||
stars int32
|
||||
commentary *string
|
||||
}
|
||||
|
||||
var reviews = make(map[string][]*review)
|
||||
|
||||
type Resolver struct{}
|
||||
|
||||
func (r *Resolver) Hero(args struct{ Episode string }) *characterResolver {
|
||||
if args.Episode == "EMPIRE" {
|
||||
return &characterResolver{&humanResolver{humanData["1000"]}}
|
||||
}
|
||||
return &characterResolver{&droidResolver{droidData["2001"]}}
|
||||
}
|
||||
|
||||
func (r *Resolver) Reviews(args struct{ Episode string }) []*reviewResolver {
|
||||
var l []*reviewResolver
|
||||
for _, review := range reviews[args.Episode] {
|
||||
l = append(l, &reviewResolver{review})
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (r *Resolver) Search(args struct{ Text string }) []*searchResultResolver {
|
||||
var l []*searchResultResolver
|
||||
for _, h := range humans {
|
||||
if strings.Contains(h.Name, args.Text) {
|
||||
l = append(l, &searchResultResolver{&humanResolver{h}})
|
||||
}
|
||||
}
|
||||
for _, d := range droids {
|
||||
if strings.Contains(d.Name, args.Text) {
|
||||
l = append(l, &searchResultResolver{&droidResolver{d}})
|
||||
}
|
||||
}
|
||||
for _, s := range starships {
|
||||
if strings.Contains(s.Name, args.Text) {
|
||||
l = append(l, &searchResultResolver{&starshipResolver{s}})
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (r *Resolver) Character(args struct{ ID graphql.ID }) *characterResolver {
|
||||
if h := humanData[args.ID]; h != nil {
|
||||
return &characterResolver{&humanResolver{h}}
|
||||
}
|
||||
if d := droidData[args.ID]; d != nil {
|
||||
return &characterResolver{&droidResolver{d}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Resolver) Human(args struct{ ID graphql.ID }) *humanResolver {
|
||||
if h := humanData[args.ID]; h != nil {
|
||||
return &humanResolver{h}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Resolver) Droid(args struct{ ID graphql.ID }) *droidResolver {
|
||||
if d := droidData[args.ID]; d != nil {
|
||||
return &droidResolver{d}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Resolver) Starship(args struct{ ID graphql.ID }) *starshipResolver {
|
||||
if s := starshipData[args.ID]; s != nil {
|
||||
return &starshipResolver{s}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Resolver) CreateReview(args *struct {
|
||||
Episode string
|
||||
Review *reviewInput
|
||||
}) *reviewResolver {
|
||||
review := &review{
|
||||
stars: args.Review.Stars,
|
||||
commentary: args.Review.Commentary,
|
||||
}
|
||||
reviews[args.Episode] = append(reviews[args.Episode], review)
|
||||
return &reviewResolver{review}
|
||||
}
|
||||
|
||||
type friendsConnectionArgs struct {
|
||||
First *int32
|
||||
After *graphql.ID
|
||||
}
|
||||
|
||||
type character interface {
|
||||
ID() graphql.ID
|
||||
Name() string
|
||||
Friends() *[]*characterResolver
|
||||
FriendsConnection(friendsConnectionArgs) (*friendsConnectionResolver, error)
|
||||
AppearsIn() []string
|
||||
}
|
||||
|
||||
type characterResolver struct {
|
||||
character
|
||||
}
|
||||
|
||||
func (r *characterResolver) ToHuman() (*humanResolver, bool) {
|
||||
c, ok := r.character.(*humanResolver)
|
||||
return c, ok
|
||||
}
|
||||
|
||||
func (r *characterResolver) ToDroid() (*droidResolver, bool) {
|
||||
c, ok := r.character.(*droidResolver)
|
||||
return c, ok
|
||||
}
|
||||
|
||||
type humanResolver struct {
|
||||
h *human
|
||||
}
|
||||
|
||||
func (r *humanResolver) ID() graphql.ID {
|
||||
return r.h.ID
|
||||
}
|
||||
|
||||
func (r *humanResolver) Name() string {
|
||||
return r.h.Name
|
||||
}
|
||||
|
||||
func (r *humanResolver) Height(args struct{ Unit string }) float64 {
|
||||
return convertLength(r.h.Height, args.Unit)
|
||||
}
|
||||
|
||||
func (r *humanResolver) Mass() *float64 {
|
||||
if r.h.Mass == 0 {
|
||||
return nil
|
||||
}
|
||||
f := float64(r.h.Mass)
|
||||
return &f
|
||||
}
|
||||
|
||||
func (r *humanResolver) Friends() *[]*characterResolver {
|
||||
return resolveCharacters(r.h.Friends)
|
||||
}
|
||||
|
||||
func (r *humanResolver) FriendsConnection(args friendsConnectionArgs) (*friendsConnectionResolver, error) {
|
||||
return newFriendsConnectionResolver(r.h.Friends, args)
|
||||
}
|
||||
|
||||
func (r *humanResolver) AppearsIn() []string {
|
||||
return r.h.AppearsIn
|
||||
}
|
||||
|
||||
func (r *humanResolver) Starships() *[]*starshipResolver {
|
||||
l := make([]*starshipResolver, len(r.h.Starships))
|
||||
for i, id := range r.h.Starships {
|
||||
l[i] = &starshipResolver{starshipData[id]}
|
||||
}
|
||||
return &l
|
||||
}
|
||||
|
||||
type droidResolver struct {
|
||||
d *droid
|
||||
}
|
||||
|
||||
func (r *droidResolver) ID() graphql.ID {
|
||||
return r.d.ID
|
||||
}
|
||||
|
||||
func (r *droidResolver) Name() string {
|
||||
return r.d.Name
|
||||
}
|
||||
|
||||
func (r *droidResolver) Friends() *[]*characterResolver {
|
||||
return resolveCharacters(r.d.Friends)
|
||||
}
|
||||
|
||||
func (r *droidResolver) FriendsConnection(args friendsConnectionArgs) (*friendsConnectionResolver, error) {
|
||||
return newFriendsConnectionResolver(r.d.Friends, args)
|
||||
}
|
||||
|
||||
func (r *droidResolver) AppearsIn() []string {
|
||||
return r.d.AppearsIn
|
||||
}
|
||||
|
||||
func (r *droidResolver) PrimaryFunction() *string {
|
||||
if r.d.PrimaryFunction == "" {
|
||||
return nil
|
||||
}
|
||||
return &r.d.PrimaryFunction
|
||||
}
|
||||
|
||||
type starshipResolver struct {
|
||||
s *starship
|
||||
}
|
||||
|
||||
func (r *starshipResolver) ID() graphql.ID {
|
||||
return r.s.ID
|
||||
}
|
||||
|
||||
func (r *starshipResolver) Name() string {
|
||||
return r.s.Name
|
||||
}
|
||||
|
||||
func (r *starshipResolver) Length(args struct{ Unit string }) float64 {
|
||||
return convertLength(r.s.Length, args.Unit)
|
||||
}
|
||||
|
||||
type searchResultResolver struct {
|
||||
result interface{}
|
||||
}
|
||||
|
||||
func (r *searchResultResolver) ToHuman() (*humanResolver, bool) {
|
||||
res, ok := r.result.(*humanResolver)
|
||||
return res, ok
|
||||
}
|
||||
|
||||
func (r *searchResultResolver) ToDroid() (*droidResolver, bool) {
|
||||
res, ok := r.result.(*droidResolver)
|
||||
return res, ok
|
||||
}
|
||||
|
||||
func (r *searchResultResolver) ToStarship() (*starshipResolver, bool) {
|
||||
res, ok := r.result.(*starshipResolver)
|
||||
return res, ok
|
||||
}
|
||||
|
||||
func convertLength(meters float64, unit string) float64 {
|
||||
switch unit {
|
||||
case "METER":
|
||||
return meters
|
||||
case "FOOT":
|
||||
return meters * 3.28084
|
||||
default:
|
||||
panic("invalid unit")
|
||||
}
|
||||
}
|
||||
|
||||
func resolveCharacters(ids []graphql.ID) *[]*characterResolver {
|
||||
var characters []*characterResolver
|
||||
for _, id := range ids {
|
||||
if c := resolveCharacter(id); c != nil {
|
||||
characters = append(characters, c)
|
||||
}
|
||||
}
|
||||
return &characters
|
||||
}
|
||||
|
||||
func resolveCharacter(id graphql.ID) *characterResolver {
|
||||
if h, ok := humanData[id]; ok {
|
||||
return &characterResolver{&humanResolver{h}}
|
||||
}
|
||||
if d, ok := droidData[id]; ok {
|
||||
return &characterResolver{&droidResolver{d}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type reviewResolver struct {
|
||||
r *review
|
||||
}
|
||||
|
||||
func (r *reviewResolver) Stars() int32 {
|
||||
return r.r.stars
|
||||
}
|
||||
|
||||
func (r *reviewResolver) Commentary() *string {
|
||||
return r.r.commentary
|
||||
}
|
||||
|
||||
type friendsConnectionResolver struct {
|
||||
ids []graphql.ID
|
||||
from int
|
||||
to int
|
||||
}
|
||||
|
||||
func newFriendsConnectionResolver(ids []graphql.ID, args friendsConnectionArgs) (*friendsConnectionResolver, error) {
|
||||
from := 0
|
||||
if args.After != nil {
|
||||
b, err := base64.StdEncoding.DecodeString(string(*args.After))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i, err := strconv.Atoi(strings.TrimPrefix(string(b), "cursor"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
from = i
|
||||
}
|
||||
|
||||
to := len(ids)
|
||||
if args.First != nil {
|
||||
to = from + int(*args.First)
|
||||
if to > len(ids) {
|
||||
to = len(ids)
|
||||
}
|
||||
}
|
||||
|
||||
return &friendsConnectionResolver{
|
||||
ids: ids,
|
||||
from: from,
|
||||
to: to,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *friendsConnectionResolver) TotalCount() int32 {
|
||||
return int32(len(r.ids))
|
||||
}
|
||||
|
||||
func (r *friendsConnectionResolver) Edges() *[]*friendsEdgeResolver {
|
||||
l := make([]*friendsEdgeResolver, r.to-r.from)
|
||||
for i := range l {
|
||||
l[i] = &friendsEdgeResolver{
|
||||
cursor: encodeCursor(r.from + i),
|
||||
id: r.ids[r.from+i],
|
||||
}
|
||||
}
|
||||
return &l
|
||||
}
|
||||
|
||||
func (r *friendsConnectionResolver) Friends() *[]*characterResolver {
|
||||
return resolveCharacters(r.ids[r.from:r.to])
|
||||
}
|
||||
|
||||
func (r *friendsConnectionResolver) PageInfo() *pageInfoResolver {
|
||||
return &pageInfoResolver{
|
||||
startCursor: encodeCursor(r.from),
|
||||
endCursor: encodeCursor(r.to - 1),
|
||||
hasNextPage: r.to < len(r.ids),
|
||||
}
|
||||
}
|
||||
|
||||
func encodeCursor(i int) graphql.ID {
|
||||
return graphql.ID(base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("cursor%d", i+1))))
|
||||
}
|
||||
|
||||
type friendsEdgeResolver struct {
|
||||
cursor graphql.ID
|
||||
id graphql.ID
|
||||
}
|
||||
|
||||
func (r *friendsEdgeResolver) Cursor() graphql.ID {
|
||||
return r.cursor
|
||||
}
|
||||
|
||||
func (r *friendsEdgeResolver) Node() *characterResolver {
|
||||
return resolveCharacter(r.id)
|
||||
}
|
||||
|
||||
type pageInfoResolver struct {
|
||||
startCursor graphql.ID
|
||||
endCursor graphql.ID
|
||||
hasNextPage bool
|
||||
}
|
||||
|
||||
func (r *pageInfoResolver) StartCursor() *graphql.ID {
|
||||
return &r.startCursor
|
||||
}
|
||||
|
||||
func (r *pageInfoResolver) EndCursor() *graphql.ID {
|
||||
return &r.endCursor
|
||||
}
|
||||
|
||||
func (r *pageInfoResolver) HasNextPage() bool {
|
||||
return r.hasNextPage
|
||||
}
|
||||
|
||||
type reviewInput struct {
|
||||
Stars int32
|
||||
Commentary *string
|
||||
}
|
67
vendor/github.com/neelance/graphql-go/gqltesting/testing.go
generated
vendored
Normal file
67
vendor/github.com/neelance/graphql-go/gqltesting/testing.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
package gqltesting
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
graphql "github.com/neelance/graphql-go"
|
||||
)
|
||||
|
||||
// Test is a GraphQL test case to be used with RunTest(s).
|
||||
type Test struct {
|
||||
Context context.Context
|
||||
Schema *graphql.Schema
|
||||
Query string
|
||||
OperationName string
|
||||
Variables map[string]interface{}
|
||||
ExpectedResult string
|
||||
}
|
||||
|
||||
// RunTests runs the given GraphQL test cases as subtests.
|
||||
func RunTests(t *testing.T, tests []*Test) {
|
||||
if len(tests) == 1 {
|
||||
RunTest(t, tests[0])
|
||||
return
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(strconv.Itoa(i+1), func(t *testing.T) {
|
||||
RunTest(t, test)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// RunTest runs a single GraphQL test case.
|
||||
func RunTest(t *testing.T, test *Test) {
|
||||
if test.Context == nil {
|
||||
test.Context = context.Background()
|
||||
}
|
||||
result := test.Schema.Exec(test.Context, test.Query, test.OperationName, test.Variables)
|
||||
if len(result.Errors) != 0 {
|
||||
t.Fatal(result.Errors[0])
|
||||
}
|
||||
got := formatJSON(t, result.Data)
|
||||
|
||||
want := formatJSON(t, []byte(test.ExpectedResult))
|
||||
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Logf("got: %s", got)
|
||||
t.Logf("want: %s", want)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func formatJSON(t *testing.T, data []byte) []byte {
|
||||
var v interface{}
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
t.Fatalf("invalid JSON: %s", err)
|
||||
}
|
||||
formatted, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return formatted
|
||||
}
|
185
vendor/github.com/neelance/graphql-go/graphql.go
generated
vendored
Normal file
185
vendor/github.com/neelance/graphql-go/graphql.go
generated
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
package graphql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/neelance/graphql-go/errors"
|
||||
"github.com/neelance/graphql-go/internal/common"
|
||||
"github.com/neelance/graphql-go/internal/exec"
|
||||
"github.com/neelance/graphql-go/internal/exec/resolvable"
|
||||
"github.com/neelance/graphql-go/internal/exec/selected"
|
||||
"github.com/neelance/graphql-go/internal/query"
|
||||
"github.com/neelance/graphql-go/internal/schema"
|
||||
"github.com/neelance/graphql-go/internal/validation"
|
||||
"github.com/neelance/graphql-go/introspection"
|
||||
"github.com/neelance/graphql-go/log"
|
||||
"github.com/neelance/graphql-go/trace"
|
||||
)
|
||||
|
||||
// ParseSchema parses a GraphQL schema and attaches the given root resolver. It returns an error if
|
||||
// the Go type signature of the resolvers does not match the schema. If nil is passed as the
|
||||
// resolver, then the schema can not be executed, but it may be inspected (e.g. with ToJSON).
|
||||
func ParseSchema(schemaString string, resolver interface{}, opts ...SchemaOpt) (*Schema, error) {
|
||||
s := &Schema{
|
||||
schema: schema.New(),
|
||||
maxParallelism: 10,
|
||||
tracer: trace.OpenTracingTracer{},
|
||||
logger: &log.DefaultLogger{},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
|
||||
if err := s.schema.Parse(schemaString); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resolver != nil {
|
||||
r, err := resolvable.ApplyResolver(s.schema, resolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.res = r
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// MustParseSchema calls ParseSchema and panics on error.
|
||||
func MustParseSchema(schemaString string, resolver interface{}, opts ...SchemaOpt) *Schema {
|
||||
s, err := ParseSchema(schemaString, resolver, opts...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Schema represents a GraphQL schema with an optional resolver.
|
||||
type Schema struct {
|
||||
schema *schema.Schema
|
||||
res *resolvable.Schema
|
||||
|
||||
maxParallelism int
|
||||
tracer trace.Tracer
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// SchemaOpt is an option to pass to ParseSchema or MustParseSchema.
|
||||
type SchemaOpt func(*Schema)
|
||||
|
||||
// MaxParallelism specifies the maximum number of resolvers per request allowed to run in parallel. The default is 10.
|
||||
func MaxParallelism(n int) SchemaOpt {
|
||||
return func(s *Schema) {
|
||||
s.maxParallelism = n
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer is used to trace queries and fields. It defaults to trace.OpenTracingTracer.
|
||||
func Tracer(tracer trace.Tracer) SchemaOpt {
|
||||
return func(s *Schema) {
|
||||
s.tracer = tracer
|
||||
}
|
||||
}
|
||||
|
||||
// Logger is used to log panics durring query execution. It defaults to exec.DefaultLogger.
|
||||
func Logger(logger log.Logger) SchemaOpt {
|
||||
return func(s *Schema) {
|
||||
s.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
// Response represents a typical response of a GraphQL server. It may be encoded to JSON directly or
|
||||
// it may be further processed to a custom response type, for example to include custom error data.
|
||||
type Response struct {
|
||||
Data json.RawMessage `json:"data,omitempty"`
|
||||
Errors []*errors.QueryError `json:"errors,omitempty"`
|
||||
Extensions map[string]interface{} `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates the given query with the schema.
|
||||
func (s *Schema) Validate(queryString string) []*errors.QueryError {
|
||||
doc, qErr := query.Parse(queryString)
|
||||
if qErr != nil {
|
||||
return []*errors.QueryError{qErr}
|
||||
}
|
||||
|
||||
return validation.Validate(s.schema, doc)
|
||||
}
|
||||
|
||||
// Exec executes the given query with the schema's resolver. It panics if the schema was created
|
||||
// without a resolver. If the context get cancelled, no further resolvers will be called and a
|
||||
// the context error will be returned as soon as possible (not immediately).
|
||||
func (s *Schema) Exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) *Response {
|
||||
if s.res == nil {
|
||||
panic("schema created without resolver, can not exec")
|
||||
}
|
||||
return s.exec(ctx, queryString, operationName, variables, s.res)
|
||||
}
|
||||
|
||||
func (s *Schema) exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, res *resolvable.Schema) *Response {
|
||||
doc, qErr := query.Parse(queryString)
|
||||
if qErr != nil {
|
||||
return &Response{Errors: []*errors.QueryError{qErr}}
|
||||
}
|
||||
|
||||
errs := validation.Validate(s.schema, doc)
|
||||
if len(errs) != 0 {
|
||||
return &Response{Errors: errs}
|
||||
}
|
||||
|
||||
op, err := getOperation(doc, operationName)
|
||||
if err != nil {
|
||||
return &Response{Errors: []*errors.QueryError{errors.Errorf("%s", err)}}
|
||||
}
|
||||
|
||||
r := &exec.Request{
|
||||
Request: selected.Request{
|
||||
Doc: doc,
|
||||
Vars: variables,
|
||||
Schema: s.schema,
|
||||
},
|
||||
Limiter: make(chan struct{}, s.maxParallelism),
|
||||
Tracer: s.tracer,
|
||||
Logger: s.logger,
|
||||
}
|
||||
varTypes := make(map[string]*introspection.Type)
|
||||
for _, v := range op.Vars {
|
||||
t, err := common.ResolveType(v.Type, s.schema.Resolve)
|
||||
if err != nil {
|
||||
return &Response{Errors: []*errors.QueryError{err}}
|
||||
}
|
||||
varTypes[v.Name.Name] = introspection.WrapType(t)
|
||||
}
|
||||
traceCtx, finish := s.tracer.TraceQuery(ctx, queryString, operationName, variables, varTypes)
|
||||
data, errs := r.Execute(traceCtx, res, op)
|
||||
finish(errs)
|
||||
|
||||
return &Response{
|
||||
Data: data,
|
||||
Errors: errs,
|
||||
}
|
||||
}
|
||||
|
||||
func getOperation(document *query.Document, operationName string) (*query.Operation, error) {
|
||||
if len(document.Operations) == 0 {
|
||||
return nil, fmt.Errorf("no operations in query document")
|
||||
}
|
||||
|
||||
if operationName == "" {
|
||||
if len(document.Operations) > 1 {
|
||||
return nil, fmt.Errorf("more than one operation in query document and no operation name given")
|
||||
}
|
||||
for _, op := range document.Operations {
|
||||
return op, nil // return the one and only operation
|
||||
}
|
||||
}
|
||||
|
||||
op := document.Operations.Get(operationName)
|
||||
if op == nil {
|
||||
return nil, fmt.Errorf("no operation with name %q", operationName)
|
||||
}
|
||||
return op, nil
|
||||
}
|
1755
vendor/github.com/neelance/graphql-go/graphql_test.go
generated
vendored
Normal file
1755
vendor/github.com/neelance/graphql-go/graphql_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
30
vendor/github.com/neelance/graphql-go/id.go
generated
vendored
Normal file
30
vendor/github.com/neelance/graphql-go/id.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
package graphql
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ID represents GraphQL's "ID" scalar type. A custom type may be used instead.
|
||||
type ID string
|
||||
|
||||
func (_ ID) ImplementsGraphQLType(name string) bool {
|
||||
return name == "ID"
|
||||
}
|
||||
|
||||
func (id *ID) UnmarshalGraphQL(input interface{}) error {
|
||||
var err error
|
||||
switch input := input.(type) {
|
||||
case string:
|
||||
*id = ID(input)
|
||||
case int32:
|
||||
*id = ID(strconv.Itoa(int(input)))
|
||||
default:
|
||||
err = errors.New("wrong type")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (id ID) MarshalJSON() ([]byte, error) {
|
||||
return strconv.AppendQuote(nil, string(id)), nil
|
||||
}
|
32
vendor/github.com/neelance/graphql-go/internal/common/directive.go
generated
vendored
Normal file
32
vendor/github.com/neelance/graphql-go/internal/common/directive.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
package common
|
||||
|
||||
type Directive struct {
|
||||
Name Ident
|
||||
Args ArgumentList
|
||||
}
|
||||
|
||||
func ParseDirectives(l *Lexer) DirectiveList {
|
||||
var directives DirectiveList
|
||||
for l.Peek() == '@' {
|
||||
l.ConsumeToken('@')
|
||||
d := &Directive{}
|
||||
d.Name = l.ConsumeIdentWithLoc()
|
||||
d.Name.Loc.Column--
|
||||
if l.Peek() == '(' {
|
||||
d.Args = ParseArguments(l)
|
||||
}
|
||||
directives = append(directives, d)
|
||||
}
|
||||
return directives
|
||||
}
|
||||
|
||||
type DirectiveList []*Directive
|
||||
|
||||
func (l DirectiveList) Get(name string) *Directive {
|
||||
for _, d := range l {
|
||||
if d.Name.Name == name {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
122
vendor/github.com/neelance/graphql-go/internal/common/lexer.go
generated
vendored
Normal file
122
vendor/github.com/neelance/graphql-go/internal/common/lexer.go
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/neelance/graphql-go/errors"
|
||||
)
|
||||
|
||||
type syntaxError string
|
||||
|
||||
type Lexer struct {
|
||||
sc *scanner.Scanner
|
||||
next rune
|
||||
descComment string
|
||||
}
|
||||
|
||||
type Ident struct {
|
||||
Name string
|
||||
Loc errors.Location
|
||||
}
|
||||
|
||||
func New(sc *scanner.Scanner) *Lexer {
|
||||
l := &Lexer{sc: sc}
|
||||
l.Consume()
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Lexer) CatchSyntaxError(f func()) (errRes *errors.QueryError) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if err, ok := err.(syntaxError); ok {
|
||||
errRes = errors.Errorf("syntax error: %s", err)
|
||||
errRes.Locations = []errors.Location{l.Location()}
|
||||
return
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
f()
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Lexer) Peek() rune {
|
||||
return l.next
|
||||
}
|
||||
|
||||
func (l *Lexer) Consume() {
|
||||
l.descComment = ""
|
||||
for {
|
||||
l.next = l.sc.Scan()
|
||||
if l.next == ',' {
|
||||
continue
|
||||
}
|
||||
if l.next == '#' {
|
||||
if l.sc.Peek() == ' ' {
|
||||
l.sc.Next()
|
||||
}
|
||||
if l.descComment != "" {
|
||||
l.descComment += "\n"
|
||||
}
|
||||
for {
|
||||
next := l.sc.Next()
|
||||
if next == '\n' || next == scanner.EOF {
|
||||
break
|
||||
}
|
||||
l.descComment += string(next)
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Lexer) ConsumeIdent() string {
|
||||
name := l.sc.TokenText()
|
||||
l.ConsumeToken(scanner.Ident)
|
||||
return name
|
||||
}
|
||||
|
||||
func (l *Lexer) ConsumeIdentWithLoc() Ident {
|
||||
loc := l.Location()
|
||||
name := l.sc.TokenText()
|
||||
l.ConsumeToken(scanner.Ident)
|
||||
return Ident{name, loc}
|
||||
}
|
||||
|
||||
func (l *Lexer) ConsumeKeyword(keyword string) {
|
||||
if l.next != scanner.Ident || l.sc.TokenText() != keyword {
|
||||
l.SyntaxError(fmt.Sprintf("unexpected %q, expecting %q", l.sc.TokenText(), keyword))
|
||||
}
|
||||
l.Consume()
|
||||
}
|
||||
|
||||
func (l *Lexer) ConsumeLiteral() *BasicLit {
|
||||
lit := &BasicLit{Type: l.next, Text: l.sc.TokenText()}
|
||||
l.Consume()
|
||||
return lit
|
||||
}
|
||||
|
||||
func (l *Lexer) ConsumeToken(expected rune) {
|
||||
if l.next != expected {
|
||||
l.SyntaxError(fmt.Sprintf("unexpected %q, expecting %s", l.sc.TokenText(), scanner.TokenString(expected)))
|
||||
}
|
||||
l.Consume()
|
||||
}
|
||||
|
||||
func (l *Lexer) DescComment() string {
|
||||
return l.descComment
|
||||
}
|
||||
|
||||
func (l *Lexer) SyntaxError(message string) {
|
||||
panic(syntaxError(message))
|
||||
}
|
||||
|
||||
func (l *Lexer) Location() errors.Location {
|
||||
return errors.Location{
|
||||
Line: l.sc.Line,
|
||||
Column: l.sc.Column,
|
||||
}
|
||||
}
|
206
vendor/github.com/neelance/graphql-go/internal/common/literals.go
generated
vendored
Normal file
206
vendor/github.com/neelance/graphql-go/internal/common/literals.go
generated
vendored
Normal file
@ -0,0 +1,206 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/neelance/graphql-go/errors"
|
||||
)
|
||||
|
||||
type Literal interface {
|
||||
Value(vars map[string]interface{}) interface{}
|
||||
String() string
|
||||
Location() errors.Location
|
||||
}
|
||||
|
||||
type BasicLit struct {
|
||||
Type rune
|
||||
Text string
|
||||
Loc errors.Location
|
||||
}
|
||||
|
||||
func (lit *BasicLit) Value(vars map[string]interface{}) interface{} {
|
||||
switch lit.Type {
|
||||
case scanner.Int:
|
||||
value, err := strconv.ParseInt(lit.Text, 10, 32)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return int32(value)
|
||||
|
||||
case scanner.Float:
|
||||
value, err := strconv.ParseFloat(lit.Text, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return value
|
||||
|
||||
case scanner.String:
|
||||
value, err := strconv.Unquote(lit.Text)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return value
|
||||
|
||||
case scanner.Ident:
|
||||
switch lit.Text {
|
||||
case "true":
|
||||
return true
|
||||
case "false":
|
||||
return false
|
||||
default:
|
||||
return lit.Text
|
||||
}
|
||||
|
||||
default:
|
||||
panic("invalid literal")
|
||||
}
|
||||
}
|
||||
|
||||
func (lit *BasicLit) String() string {
|
||||
return lit.Text
|
||||
}
|
||||
|
||||
func (lit *BasicLit) Location() errors.Location {
|
||||
return lit.Loc
|
||||
}
|
||||
|
||||
type ListLit struct {
|
||||
Entries []Literal
|
||||
Loc errors.Location
|
||||
}
|
||||
|
||||
func (lit *ListLit) Value(vars map[string]interface{}) interface{} {
|
||||
entries := make([]interface{}, len(lit.Entries))
|
||||
for i, entry := range lit.Entries {
|
||||
entries[i] = entry.Value(vars)
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
func (lit *ListLit) String() string {
|
||||
entries := make([]string, len(lit.Entries))
|
||||
for i, entry := range lit.Entries {
|
||||
entries[i] = entry.String()
|
||||
}
|
||||
return "[" + strings.Join(entries, ", ") + "]"
|
||||
}
|
||||
|
||||
func (lit *ListLit) Location() errors.Location {
|
||||
return lit.Loc
|
||||
}
|
||||
|
||||
type ObjectLit struct {
|
||||
Fields []*ObjectLitField
|
||||
Loc errors.Location
|
||||
}
|
||||
|
||||
type ObjectLitField struct {
|
||||
Name Ident
|
||||
Value Literal
|
||||
}
|
||||
|
||||
func (lit *ObjectLit) Value(vars map[string]interface{}) interface{} {
|
||||
fields := make(map[string]interface{}, len(lit.Fields))
|
||||
for _, f := range lit.Fields {
|
||||
fields[f.Name.Name] = f.Value.Value(vars)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
func (lit *ObjectLit) String() string {
|
||||
entries := make([]string, 0, len(lit.Fields))
|
||||
for _, f := range lit.Fields {
|
||||
entries = append(entries, f.Name.Name+": "+f.Value.String())
|
||||
}
|
||||
return "{" + strings.Join(entries, ", ") + "}"
|
||||
}
|
||||
|
||||
func (lit *ObjectLit) Location() errors.Location {
|
||||
return lit.Loc
|
||||
}
|
||||
|
||||
type NullLit struct {
|
||||
Loc errors.Location
|
||||
}
|
||||
|
||||
func (lit *NullLit) Value(vars map[string]interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lit *NullLit) String() string {
|
||||
return "null"
|
||||
}
|
||||
|
||||
func (lit *NullLit) Location() errors.Location {
|
||||
return lit.Loc
|
||||
}
|
||||
|
||||
type Variable struct {
|
||||
Name string
|
||||
Loc errors.Location
|
||||
}
|
||||
|
||||
func (v Variable) Value(vars map[string]interface{}) interface{} {
|
||||
return vars[v.Name]
|
||||
}
|
||||
|
||||
func (v Variable) String() string {
|
||||
return "$" + v.Name
|
||||
}
|
||||
|
||||
func (v *Variable) Location() errors.Location {
|
||||
return v.Loc
|
||||
}
|
||||
|
||||
func ParseLiteral(l *Lexer, constOnly bool) Literal {
|
||||
loc := l.Location()
|
||||
switch l.Peek() {
|
||||
case '$':
|
||||
if constOnly {
|
||||
l.SyntaxError("variable not allowed")
|
||||
panic("unreachable")
|
||||
}
|
||||
l.ConsumeToken('$')
|
||||
return &Variable{l.ConsumeIdent(), loc}
|
||||
|
||||
case scanner.Int, scanner.Float, scanner.String, scanner.Ident:
|
||||
lit := l.ConsumeLiteral()
|
||||
if lit.Type == scanner.Ident && lit.Text == "null" {
|
||||
return &NullLit{loc}
|
||||
}
|
||||
lit.Loc = loc
|
||||
return lit
|
||||
case '-':
|
||||
l.ConsumeToken('-')
|
||||
lit := l.ConsumeLiteral()
|
||||
lit.Text = "-" + lit.Text
|
||||
lit.Loc = loc
|
||||
return lit
|
||||
case '[':
|
||||
l.ConsumeToken('[')
|
||||
var list []Literal
|
||||
for l.Peek() != ']' {
|
||||
list = append(list, ParseLiteral(l, constOnly))
|
||||
}
|
||||
l.ConsumeToken(']')
|
||||
return &ListLit{list, loc}
|
||||
|
||||
case '{':
|
||||
l.ConsumeToken('{')
|
||||
var fields []*ObjectLitField
|
||||
for l.Peek() != '}' {
|
||||
name := l.ConsumeIdentWithLoc()
|
||||
l.ConsumeToken(':')
|
||||
value := ParseLiteral(l, constOnly)
|
||||
fields = append(fields, &ObjectLitField{name, value})
|
||||
}
|
||||
l.ConsumeToken('}')
|
||||
return &ObjectLit{fields, loc}
|
||||
|
||||
default:
|
||||
l.SyntaxError("invalid value")
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
80
vendor/github.com/neelance/graphql-go/internal/common/types.go
generated
vendored
Normal file
80
vendor/github.com/neelance/graphql-go/internal/common/types.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/neelance/graphql-go/errors"
|
||||
)
|
||||
|
||||
type Type interface {
|
||||
Kind() string
|
||||
String() string
|
||||
}
|
||||
|
||||
type List struct {
|
||||
OfType Type
|
||||
}
|
||||
|
||||
type NonNull struct {
|
||||
OfType Type
|
||||
}
|
||||
|
||||
type TypeName struct {
|
||||
Ident
|
||||
}
|
||||
|
||||
func (*List) Kind() string { return "LIST" }
|
||||
func (*NonNull) Kind() string { return "NON_NULL" }
|
||||
func (*TypeName) Kind() string { panic("TypeName needs to be resolved to actual type") }
|
||||
|
||||
func (t *List) String() string { return "[" + t.OfType.String() + "]" }
|
||||
func (t *NonNull) String() string { return t.OfType.String() + "!" }
|
||||
func (*TypeName) String() string { panic("TypeName needs to be resolved to actual type") }
|
||||
|
||||
func ParseType(l *Lexer) Type {
|
||||
t := parseNullType(l)
|
||||
if l.Peek() == '!' {
|
||||
l.ConsumeToken('!')
|
||||
return &NonNull{OfType: t}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func parseNullType(l *Lexer) Type {
|
||||
if l.Peek() == '[' {
|
||||
l.ConsumeToken('[')
|
||||
ofType := ParseType(l)
|
||||
l.ConsumeToken(']')
|
||||
return &List{OfType: ofType}
|
||||
}
|
||||
|
||||
return &TypeName{Ident: l.ConsumeIdentWithLoc()}
|
||||
}
|
||||
|
||||
type Resolver func(name string) Type
|
||||
|
||||
func ResolveType(t Type, resolver Resolver) (Type, *errors.QueryError) {
|
||||
switch t := t.(type) {
|
||||
case *List:
|
||||
ofType, err := ResolveType(t.OfType, resolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &List{OfType: ofType}, nil
|
||||
case *NonNull:
|
||||
ofType, err := ResolveType(t.OfType, resolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &NonNull{OfType: ofType}, nil
|
||||
case *TypeName:
|
||||
refT := resolver(t.Name)
|
||||
if refT == nil {
|
||||
err := errors.Errorf("Unknown type %q.", t.Name)
|
||||
err.Rule = "KnownTypeNames"
|
||||
err.Locations = []errors.Location{t.Loc}
|
||||
return nil, err
|
||||
}
|
||||
return refT, nil
|
||||
default:
|
||||
return t, nil
|
||||
}
|
||||
}
|
77
vendor/github.com/neelance/graphql-go/internal/common/values.go
generated
vendored
Normal file
77
vendor/github.com/neelance/graphql-go/internal/common/values.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/neelance/graphql-go/errors"
|
||||
)
|
||||
|
||||
type InputValue struct {
|
||||
Name Ident
|
||||
Type Type
|
||||
Default Literal
|
||||
Desc string
|
||||
Loc errors.Location
|
||||
TypeLoc errors.Location
|
||||
}
|
||||
|
||||
type InputValueList []*InputValue
|
||||
|
||||
func (l InputValueList) Get(name string) *InputValue {
|
||||
for _, v := range l {
|
||||
if v.Name.Name == name {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseInputValue(l *Lexer) *InputValue {
|
||||
p := &InputValue{}
|
||||
p.Loc = l.Location()
|
||||
p.Desc = l.DescComment()
|
||||
p.Name = l.ConsumeIdentWithLoc()
|
||||
l.ConsumeToken(':')
|
||||
p.TypeLoc = l.Location()
|
||||
p.Type = ParseType(l)
|
||||
if l.Peek() == '=' {
|
||||
l.ConsumeToken('=')
|
||||
p.Default = ParseLiteral(l, true)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
type Argument struct {
|
||||
Name Ident
|
||||
Value Literal
|
||||
}
|
||||
|
||||
type ArgumentList []Argument
|
||||
|
||||
func (l ArgumentList) Get(name string) (Literal, bool) {
|
||||
for _, arg := range l {
|
||||
if arg.Name.Name == name {
|
||||
return arg.Value, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (l ArgumentList) MustGet(name string) Literal {
|
||||
value, ok := l.Get(name)
|
||||
if !ok {
|
||||
panic("argument not found")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func ParseArguments(l *Lexer) ArgumentList {
|
||||
var args ArgumentList
|
||||
l.ConsumeToken('(')
|
||||
for l.Peek() != ')' {
|
||||
name := l.ConsumeIdentWithLoc()
|
||||
l.ConsumeToken(':')
|
||||
value := ParseLiteral(l, false)
|
||||
args = append(args, Argument{Name: name, Value: value})
|
||||
}
|
||||
l.ConsumeToken(')')
|
||||
return args
|
||||
}
|
313
vendor/github.com/neelance/graphql-go/internal/exec/exec.go
generated
vendored
Normal file
313
vendor/github.com/neelance/graphql-go/internal/exec/exec.go
generated
vendored
Normal file
@ -0,0 +1,313 @@
|
||||
package exec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/neelance/graphql-go/errors"
|
||||
"github.com/neelance/graphql-go/internal/common"
|
||||
"github.com/neelance/graphql-go/internal/exec/resolvable"
|
||||
"github.com/neelance/graphql-go/internal/exec/selected"
|
||||
"github.com/neelance/graphql-go/internal/query"
|
||||
"github.com/neelance/graphql-go/internal/schema"
|
||||
"github.com/neelance/graphql-go/log"
|
||||
"github.com/neelance/graphql-go/trace"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
selected.Request
|
||||
Limiter chan struct{}
|
||||
Tracer trace.Tracer
|
||||
Logger log.Logger
|
||||
}
|
||||
|
||||
type fieldResult struct {
|
||||
name string
|
||||
value []byte
|
||||
}
|
||||
|
||||
func (r *Request) handlePanic(ctx context.Context) {
|
||||
if value := recover(); value != nil {
|
||||
r.Logger.LogPanic(ctx, value)
|
||||
r.AddError(makePanicError(value))
|
||||
}
|
||||
}
|
||||
|
||||
func makePanicError(value interface{}) *errors.QueryError {
|
||||
return errors.Errorf("graphql: panic occurred: %v", value)
|
||||
}
|
||||
|
||||
func (r *Request) Execute(ctx context.Context, s *resolvable.Schema, op *query.Operation) ([]byte, []*errors.QueryError) {
|
||||
var out bytes.Buffer
|
||||
func() {
|
||||
defer r.handlePanic(ctx)
|
||||
sels := selected.ApplyOperation(&r.Request, s, op)
|
||||
r.execSelections(ctx, sels, nil, s.Resolver, &out, op.Type == query.Mutation)
|
||||
}()
|
||||
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, []*errors.QueryError{errors.Errorf("%s", err)}
|
||||
}
|
||||
|
||||
return out.Bytes(), r.Errs
|
||||
}
|
||||
|
||||
type fieldToExec struct {
|
||||
field *selected.SchemaField
|
||||
sels []selected.Selection
|
||||
resolver reflect.Value
|
||||
out *bytes.Buffer
|
||||
}
|
||||
|
||||
func (r *Request) execSelections(ctx context.Context, sels []selected.Selection, path *pathSegment, resolver reflect.Value, out *bytes.Buffer, serially bool) {
|
||||
async := !serially && selected.HasAsyncSel(sels)
|
||||
|
||||
var fields []*fieldToExec
|
||||
collectFieldsToResolve(sels, resolver, &fields, make(map[string]*fieldToExec))
|
||||
|
||||
if async {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(fields))
|
||||
for _, f := range fields {
|
||||
go func(f *fieldToExec) {
|
||||
defer wg.Done()
|
||||
defer r.handlePanic(ctx)
|
||||
f.out = new(bytes.Buffer)
|
||||
execFieldSelection(ctx, r, f, &pathSegment{path, f.field.Alias}, true)
|
||||
}(f)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
out.WriteByte('{')
|
||||
for i, f := range fields {
|
||||
if i > 0 {
|
||||
out.WriteByte(',')
|
||||
}
|
||||
out.WriteByte('"')
|
||||
out.WriteString(f.field.Alias)
|
||||
out.WriteByte('"')
|
||||
out.WriteByte(':')
|
||||
if async {
|
||||
out.Write(f.out.Bytes())
|
||||
continue
|
||||
}
|
||||
f.out = out
|
||||
execFieldSelection(ctx, r, f, &pathSegment{path, f.field.Alias}, false)
|
||||
}
|
||||
out.WriteByte('}')
|
||||
}
|
||||
|
||||
func collectFieldsToResolve(sels []selected.Selection, resolver reflect.Value, fields *[]*fieldToExec, fieldByAlias map[string]*fieldToExec) {
|
||||
for _, sel := range sels {
|
||||
switch sel := sel.(type) {
|
||||
case *selected.SchemaField:
|
||||
field, ok := fieldByAlias[sel.Alias]
|
||||
if !ok { // validation already checked for conflict (TODO)
|
||||
field = &fieldToExec{field: sel, resolver: resolver}
|
||||
fieldByAlias[sel.Alias] = field
|
||||
*fields = append(*fields, field)
|
||||
}
|
||||
field.sels = append(field.sels, sel.Sels...)
|
||||
|
||||
case *selected.TypenameField:
|
||||
sf := &selected.SchemaField{
|
||||
Field: resolvable.MetaFieldTypename,
|
||||
Alias: sel.Alias,
|
||||
FixedResult: reflect.ValueOf(typeOf(sel, resolver)),
|
||||
}
|
||||
*fields = append(*fields, &fieldToExec{field: sf, resolver: resolver})
|
||||
|
||||
case *selected.TypeAssertion:
|
||||
out := resolver.Method(sel.MethodIndex).Call(nil)
|
||||
if !out[1].Bool() {
|
||||
continue
|
||||
}
|
||||
collectFieldsToResolve(sel.Sels, out[0], fields, fieldByAlias)
|
||||
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func typeOf(tf *selected.TypenameField, resolver reflect.Value) string {
|
||||
if len(tf.TypeAssertions) == 0 {
|
||||
return tf.Name
|
||||
}
|
||||
for name, a := range tf.TypeAssertions {
|
||||
out := resolver.Method(a.MethodIndex).Call(nil)
|
||||
if out[1].Bool() {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func execFieldSelection(ctx context.Context, r *Request, f *fieldToExec, path *pathSegment, applyLimiter bool) {
|
||||
if applyLimiter {
|
||||
r.Limiter <- struct{}{}
|
||||
}
|
||||
|
||||
var result reflect.Value
|
||||
var err *errors.QueryError
|
||||
|
||||
traceCtx, finish := r.Tracer.TraceField(ctx, f.field.TraceLabel, f.field.TypeName, f.field.Name, !f.field.Async, f.field.Args)
|
||||
defer func() {
|
||||
finish(err)
|
||||
}()
|
||||
|
||||
err = func() (err *errors.QueryError) {
|
||||
defer func() {
|
||||
if panicValue := recover(); panicValue != nil {
|
||||
r.Logger.LogPanic(ctx, panicValue)
|
||||
err = makePanicError(panicValue)
|
||||
err.Path = path.toSlice()
|
||||
}
|
||||
}()
|
||||
|
||||
if f.field.FixedResult.IsValid() {
|
||||
result = f.field.FixedResult
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := traceCtx.Err(); err != nil {
|
||||
return errors.Errorf("%s", err) // don't execute any more resolvers if context got cancelled
|
||||
}
|
||||
|
||||
var in []reflect.Value
|
||||
if f.field.HasContext {
|
||||
in = append(in, reflect.ValueOf(traceCtx))
|
||||
}
|
||||
if f.field.ArgsPacker != nil {
|
||||
in = append(in, f.field.PackedArgs)
|
||||
}
|
||||
callOut := f.resolver.Method(f.field.MethodIndex).Call(in)
|
||||
result = callOut[0]
|
||||
if f.field.HasError && !callOut[1].IsNil() {
|
||||
resolverErr := callOut[1].Interface().(error)
|
||||
err := errors.Errorf("%s", resolverErr)
|
||||
err.Path = path.toSlice()
|
||||
err.ResolverError = resolverErr
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
|
||||
if applyLimiter {
|
||||
<-r.Limiter
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
r.AddError(err)
|
||||
f.out.WriteString("null") // TODO handle non-nil
|
||||
return
|
||||
}
|
||||
|
||||
r.execSelectionSet(traceCtx, f.sels, f.field.Type, path, result, f.out)
|
||||
}
|
||||
|
||||
func (r *Request) execSelectionSet(ctx context.Context, sels []selected.Selection, typ common.Type, path *pathSegment, resolver reflect.Value, out *bytes.Buffer) {
|
||||
t, nonNull := unwrapNonNull(typ)
|
||||
switch t := t.(type) {
|
||||
case *schema.Object, *schema.Interface, *schema.Union:
|
||||
if resolver.Kind() == reflect.Ptr && resolver.IsNil() {
|
||||
if nonNull {
|
||||
panic(errors.Errorf("got nil for non-null %q", t))
|
||||
}
|
||||
out.WriteString("null")
|
||||
return
|
||||
}
|
||||
|
||||
r.execSelections(ctx, sels, path, resolver, out, false)
|
||||
return
|
||||
}
|
||||
|
||||
if !nonNull {
|
||||
if resolver.IsNil() {
|
||||
out.WriteString("null")
|
||||
return
|
||||
}
|
||||
resolver = resolver.Elem()
|
||||
}
|
||||
|
||||
switch t := t.(type) {
|
||||
case *common.List:
|
||||
l := resolver.Len()
|
||||
|
||||
if selected.HasAsyncSel(sels) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(l)
|
||||
entryouts := make([]bytes.Buffer, l)
|
||||
for i := 0; i < l; i++ {
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
defer r.handlePanic(ctx)
|
||||
r.execSelectionSet(ctx, sels, t.OfType, &pathSegment{path, i}, resolver.Index(i), &entryouts[i])
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
out.WriteByte('[')
|
||||
for i, entryout := range entryouts {
|
||||
if i > 0 {
|
||||
out.WriteByte(',')
|
||||
}
|
||||
out.Write(entryout.Bytes())
|
||||
}
|
||||
out.WriteByte(']')
|
||||
return
|
||||
}
|
||||
|
||||
out.WriteByte('[')
|
||||
for i := 0; i < l; i++ {
|
||||
if i > 0 {
|
||||
out.WriteByte(',')
|
||||
}
|
||||
r.execSelectionSet(ctx, sels, t.OfType, &pathSegment{path, i}, resolver.Index(i), out)
|
||||
}
|
||||
out.WriteByte(']')
|
||||
|
||||
case *schema.Scalar:
|
||||
v := resolver.Interface()
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
panic(errors.Errorf("could not marshal %v", v))
|
||||
}
|
||||
out.Write(data)
|
||||
|
||||
case *schema.Enum:
|
||||
out.WriteByte('"')
|
||||
out.WriteString(resolver.String())
|
||||
out.WriteByte('"')
|
||||
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func unwrapNonNull(t common.Type) (common.Type, bool) {
|
||||
if nn, ok := t.(*common.NonNull); ok {
|
||||
return nn.OfType, true
|
||||
}
|
||||
return t, false
|
||||
}
|
||||
|
||||
type marshaler interface {
|
||||
MarshalJSON() ([]byte, error)
|
||||
}
|
||||
|
||||
type pathSegment struct {
|
||||
parent *pathSegment
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (p *pathSegment) toSlice() []interface{} {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return append(p.parent.toSlice(), p.value)
|
||||
}
|
367
vendor/github.com/neelance/graphql-go/internal/exec/packer/packer.go
generated
vendored
Normal file
367
vendor/github.com/neelance/graphql-go/internal/exec/packer/packer.go
generated
vendored
Normal file
@ -0,0 +1,367 @@
|
||||
package packer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/neelance/graphql-go/errors"
|
||||
"github.com/neelance/graphql-go/internal/common"
|
||||
"github.com/neelance/graphql-go/internal/schema"
|
||||
)
|
||||
|
||||
type packer interface {
|
||||
Pack(value interface{}) (reflect.Value, error)
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
packerMap map[typePair]*packerMapEntry
|
||||
structPackers []*StructPacker
|
||||
}
|
||||
|
||||
type typePair struct {
|
||||
graphQLType common.Type
|
||||
resolverType reflect.Type
|
||||
}
|
||||
|
||||
type packerMapEntry struct {
|
||||
packer packer
|
||||
targets []*packer
|
||||
}
|
||||
|
||||
func NewBuilder() *Builder {
|
||||
return &Builder{
|
||||
packerMap: make(map[typePair]*packerMapEntry),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) Finish() error {
|
||||
for _, entry := range b.packerMap {
|
||||
for _, target := range entry.targets {
|
||||
*target = entry.packer
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range b.structPackers {
|
||||
p.defaultStruct = reflect.New(p.structType).Elem()
|
||||
for _, f := range p.fields {
|
||||
if defaultVal := f.field.Default; defaultVal != nil {
|
||||
v, err := f.fieldPacker.Pack(defaultVal.Value(nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.defaultStruct.FieldByIndex(f.fieldIndex).Set(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) assignPacker(target *packer, schemaType common.Type, reflectType reflect.Type) error {
|
||||
k := typePair{schemaType, reflectType}
|
||||
ref, ok := b.packerMap[k]
|
||||
if !ok {
|
||||
ref = &packerMapEntry{}
|
||||
b.packerMap[k] = ref
|
||||
var err error
|
||||
ref.packer, err = b.makePacker(schemaType, reflectType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
ref.targets = append(ref.targets, target)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) makePacker(schemaType common.Type, reflectType reflect.Type) (packer, error) {
|
||||
t, nonNull := unwrapNonNull(schemaType)
|
||||
if !nonNull {
|
||||
if reflectType.Kind() != reflect.Ptr {
|
||||
return nil, fmt.Errorf("%s is not a pointer", reflectType)
|
||||
}
|
||||
elemType := reflectType.Elem()
|
||||
addPtr := true
|
||||
if _, ok := t.(*schema.InputObject); ok {
|
||||
elemType = reflectType // keep pointer for input objects
|
||||
addPtr = false
|
||||
}
|
||||
elem, err := b.makeNonNullPacker(t, elemType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &nullPacker{
|
||||
elemPacker: elem,
|
||||
valueType: reflectType,
|
||||
addPtr: addPtr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return b.makeNonNullPacker(t, reflectType)
|
||||
}
|
||||
|
||||
func (b *Builder) makeNonNullPacker(schemaType common.Type, reflectType reflect.Type) (packer, error) {
|
||||
if u, ok := reflect.New(reflectType).Interface().(Unmarshaler); ok {
|
||||
if !u.ImplementsGraphQLType(schemaType.String()) {
|
||||
return nil, fmt.Errorf("can not unmarshal %s into %s", schemaType, reflectType)
|
||||
}
|
||||
return &unmarshalerPacker{
|
||||
ValueType: reflectType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
switch t := schemaType.(type) {
|
||||
case *schema.Scalar:
|
||||
return &ValuePacker{
|
||||
ValueType: reflectType,
|
||||
}, nil
|
||||
|
||||
case *schema.Enum:
|
||||
want := reflect.TypeOf("")
|
||||
if reflectType != want {
|
||||
return nil, fmt.Errorf("wrong type, expected %s", want)
|
||||
}
|
||||
return &ValuePacker{
|
||||
ValueType: reflectType,
|
||||
}, nil
|
||||
|
||||
case *schema.InputObject:
|
||||
e, err := b.MakeStructPacker(t.Values, reflectType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e, nil
|
||||
|
||||
case *common.List:
|
||||
if reflectType.Kind() != reflect.Slice {
|
||||
return nil, fmt.Errorf("expected slice, got %s", reflectType)
|
||||
}
|
||||
p := &listPacker{
|
||||
sliceType: reflectType,
|
||||
}
|
||||
if err := b.assignPacker(&p.elem, t.OfType, reflectType.Elem()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
|
||||
case *schema.Object, *schema.Interface, *schema.Union:
|
||||
return nil, fmt.Errorf("type of kind %s can not be used as input", t.Kind())
|
||||
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) MakeStructPacker(values common.InputValueList, typ reflect.Type) (*StructPacker, error) {
|
||||
structType := typ
|
||||
usePtr := false
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
structType = typ.Elem()
|
||||
usePtr = true
|
||||
}
|
||||
if structType.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("expected struct or pointer to struct, got %s", typ)
|
||||
}
|
||||
|
||||
var fields []*structPackerField
|
||||
for _, v := range values {
|
||||
fe := &structPackerField{field: v}
|
||||
fx := func(n string) bool {
|
||||
return strings.EqualFold(stripUnderscore(n), stripUnderscore(v.Name.Name))
|
||||
}
|
||||
|
||||
sf, ok := structType.FieldByNameFunc(fx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing argument %q", v.Name)
|
||||
}
|
||||
if sf.PkgPath != "" {
|
||||
return nil, fmt.Errorf("field %q must be exported", sf.Name)
|
||||
}
|
||||
fe.fieldIndex = sf.Index
|
||||
|
||||
ft := v.Type
|
||||
if v.Default != nil {
|
||||
ft, _ = unwrapNonNull(ft)
|
||||
ft = &common.NonNull{OfType: ft}
|
||||
}
|
||||
|
||||
if err := b.assignPacker(&fe.fieldPacker, ft, sf.Type); err != nil {
|
||||
return nil, fmt.Errorf("field %q: %s", sf.Name, err)
|
||||
}
|
||||
|
||||
fields = append(fields, fe)
|
||||
}
|
||||
|
||||
p := &StructPacker{
|
||||
structType: structType,
|
||||
usePtr: usePtr,
|
||||
fields: fields,
|
||||
}
|
||||
b.structPackers = append(b.structPackers, p)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
type StructPacker struct {
|
||||
structType reflect.Type
|
||||
usePtr bool
|
||||
defaultStruct reflect.Value
|
||||
fields []*structPackerField
|
||||
}
|
||||
|
||||
type structPackerField struct {
|
||||
field *common.InputValue
|
||||
fieldIndex []int
|
||||
fieldPacker packer
|
||||
}
|
||||
|
||||
func (p *StructPacker) Pack(value interface{}) (reflect.Value, error) {
|
||||
if value == nil {
|
||||
return reflect.Value{}, errors.Errorf("got null for non-null")
|
||||
}
|
||||
|
||||
values := value.(map[string]interface{})
|
||||
v := reflect.New(p.structType)
|
||||
v.Elem().Set(p.defaultStruct)
|
||||
for _, f := range p.fields {
|
||||
if value, ok := values[f.field.Name.Name]; ok {
|
||||
packed, err := f.fieldPacker.Pack(value)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
v.Elem().FieldByIndex(f.fieldIndex).Set(packed)
|
||||
}
|
||||
}
|
||||
if !p.usePtr {
|
||||
return v.Elem(), nil
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type listPacker struct {
|
||||
sliceType reflect.Type
|
||||
elem packer
|
||||
}
|
||||
|
||||
func (e *listPacker) Pack(value interface{}) (reflect.Value, error) {
|
||||
list, ok := value.([]interface{})
|
||||
if !ok {
|
||||
list = []interface{}{value}
|
||||
}
|
||||
|
||||
v := reflect.MakeSlice(e.sliceType, len(list), len(list))
|
||||
for i := range list {
|
||||
packed, err := e.elem.Pack(list[i])
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
v.Index(i).Set(packed)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type nullPacker struct {
|
||||
elemPacker packer
|
||||
valueType reflect.Type
|
||||
addPtr bool
|
||||
}
|
||||
|
||||
func (p *nullPacker) Pack(value interface{}) (reflect.Value, error) {
|
||||
if value == nil {
|
||||
return reflect.Zero(p.valueType), nil
|
||||
}
|
||||
|
||||
v, err := p.elemPacker.Pack(value)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
|
||||
if p.addPtr {
|
||||
ptr := reflect.New(p.valueType.Elem())
|
||||
ptr.Elem().Set(v)
|
||||
return ptr, nil
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type ValuePacker struct {
|
||||
ValueType reflect.Type
|
||||
}
|
||||
|
||||
func (p *ValuePacker) Pack(value interface{}) (reflect.Value, error) {
|
||||
if value == nil {
|
||||
return reflect.Value{}, errors.Errorf("got null for non-null")
|
||||
}
|
||||
|
||||
coerced, err := unmarshalInput(p.ValueType, value)
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("could not unmarshal %#v (%T) into %s: %s", value, value, p.ValueType, err)
|
||||
}
|
||||
return reflect.ValueOf(coerced), nil
|
||||
}
|
||||
|
||||
type unmarshalerPacker struct {
|
||||
ValueType reflect.Type
|
||||
}
|
||||
|
||||
func (p *unmarshalerPacker) Pack(value interface{}) (reflect.Value, error) {
|
||||
if value == nil {
|
||||
return reflect.Value{}, errors.Errorf("got null for non-null")
|
||||
}
|
||||
|
||||
v := reflect.New(p.ValueType)
|
||||
if err := v.Interface().(Unmarshaler).UnmarshalGraphQL(value); err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
return v.Elem(), nil
|
||||
}
|
||||
|
||||
type Unmarshaler interface {
|
||||
ImplementsGraphQLType(name string) bool
|
||||
UnmarshalGraphQL(input interface{}) error
|
||||
}
|
||||
|
||||
func unmarshalInput(typ reflect.Type, input interface{}) (interface{}, error) {
|
||||
if reflect.TypeOf(input) == typ {
|
||||
return input, nil
|
||||
}
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.Int32:
|
||||
switch input := input.(type) {
|
||||
case int:
|
||||
if input < math.MinInt32 || input > math.MaxInt32 {
|
||||
return nil, fmt.Errorf("not a 32-bit integer")
|
||||
}
|
||||
return int32(input), nil
|
||||
case float64:
|
||||
coerced := int32(input)
|
||||
if input < math.MinInt32 || input > math.MaxInt32 || float64(coerced) != input {
|
||||
return nil, fmt.Errorf("not a 32-bit integer")
|
||||
}
|
||||
return coerced, nil
|
||||
}
|
||||
|
||||
case reflect.Float64:
|
||||
switch input := input.(type) {
|
||||
case int32:
|
||||
return float64(input), nil
|
||||
case int:
|
||||
return float64(input), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("incompatible type")
|
||||
}
|
||||
|
||||
func unwrapNonNull(t common.Type) (common.Type, bool) {
|
||||
if nn, ok := t.(*common.NonNull); ok {
|
||||
return nn.OfType, true
|
||||
}
|
||||
return t, false
|
||||
}
|
||||
|
||||
func stripUnderscore(s string) string {
|
||||
return strings.Replace(s, "_", "", -1)
|
||||
}
|
58
vendor/github.com/neelance/graphql-go/internal/exec/resolvable/meta.go
generated
vendored
Normal file
58
vendor/github.com/neelance/graphql-go/internal/exec/resolvable/meta.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
package resolvable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/neelance/graphql-go/internal/common"
|
||||
"github.com/neelance/graphql-go/internal/schema"
|
||||
"github.com/neelance/graphql-go/introspection"
|
||||
)
|
||||
|
||||
var MetaSchema *Object
|
||||
var MetaType *Object
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
b := newBuilder(schema.Meta)
|
||||
|
||||
metaSchema := schema.Meta.Types["__Schema"].(*schema.Object)
|
||||
MetaSchema, err = b.makeObjectExec(metaSchema.Name, metaSchema.Fields, nil, false, reflect.TypeOf(&introspection.Schema{}))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
metaType := schema.Meta.Types["__Type"].(*schema.Object)
|
||||
MetaType, err = b.makeObjectExec(metaType.Name, metaType.Fields, nil, false, reflect.TypeOf(&introspection.Type{}))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := b.finish(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
var MetaFieldTypename = Field{
|
||||
Field: schema.Field{
|
||||
Name: "__typename",
|
||||
Type: &common.NonNull{OfType: schema.Meta.Types["String"]},
|
||||
},
|
||||
TraceLabel: fmt.Sprintf("GraphQL field: __typename"),
|
||||
}
|
||||
|
||||
var MetaFieldSchema = Field{
|
||||
Field: schema.Field{
|
||||
Name: "__schema",
|
||||
Type: schema.Meta.Types["__Schema"],
|
||||
},
|
||||
TraceLabel: fmt.Sprintf("GraphQL field: __schema"),
|
||||
}
|
||||
|
||||
var MetaFieldType = Field{
|
||||
Field: schema.Field{
|
||||
Name: "__type",
|
||||
Type: schema.Meta.Types["__Type"],
|
||||
},
|
||||
TraceLabel: fmt.Sprintf("GraphQL field: __type"),
|
||||
}
|
331
vendor/github.com/neelance/graphql-go/internal/exec/resolvable/resolvable.go
generated
vendored
Normal file
331
vendor/github.com/neelance/graphql-go/internal/exec/resolvable/resolvable.go
generated
vendored
Normal file
@ -0,0 +1,331 @@
|
||||
package resolvable
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/neelance/graphql-go/internal/common"
|
||||
"github.com/neelance/graphql-go/internal/exec/packer"
|
||||
"github.com/neelance/graphql-go/internal/schema"
|
||||
)
|
||||
|
||||
type Schema struct {
|
||||
schema.Schema
|
||||
Query Resolvable
|
||||
Mutation Resolvable
|
||||
Resolver reflect.Value
|
||||
}
|
||||
|
||||
type Resolvable interface {
|
||||
isResolvable()
|
||||
}
|
||||
|
||||
type Object struct {
|
||||
Name string
|
||||
Fields map[string]*Field
|
||||
TypeAssertions map[string]*TypeAssertion
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
schema.Field
|
||||
TypeName string
|
||||
MethodIndex int
|
||||
HasContext bool
|
||||
ArgsPacker *packer.StructPacker
|
||||
HasError bool
|
||||
ValueExec Resolvable
|
||||
TraceLabel string
|
||||
}
|
||||
|
||||
type TypeAssertion struct {
|
||||
MethodIndex int
|
||||
TypeExec Resolvable
|
||||
}
|
||||
|
||||
type List struct {
|
||||
Elem Resolvable
|
||||
}
|
||||
|
||||
type Scalar struct{}
|
||||
|
||||
func (*Object) isResolvable() {}
|
||||
func (*List) isResolvable() {}
|
||||
func (*Scalar) isResolvable() {}
|
||||
|
||||
func ApplyResolver(s *schema.Schema, resolver interface{}) (*Schema, error) {
|
||||
b := newBuilder(s)
|
||||
|
||||
var query, mutation Resolvable
|
||||
|
||||
if t, ok := s.EntryPoints["query"]; ok {
|
||||
if err := b.assignExec(&query, t, reflect.TypeOf(resolver)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if t, ok := s.EntryPoints["mutation"]; ok {
|
||||
if err := b.assignExec(&mutation, t, reflect.TypeOf(resolver)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.finish(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Schema{
|
||||
Schema: *s,
|
||||
Resolver: reflect.ValueOf(resolver),
|
||||
Query: query,
|
||||
Mutation: mutation,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type execBuilder struct {
|
||||
schema *schema.Schema
|
||||
resMap map[typePair]*resMapEntry
|
||||
packerBuilder *packer.Builder
|
||||
}
|
||||
|
||||
type typePair struct {
|
||||
graphQLType common.Type
|
||||
resolverType reflect.Type
|
||||
}
|
||||
|
||||
type resMapEntry struct {
|
||||
exec Resolvable
|
||||
targets []*Resolvable
|
||||
}
|
||||
|
||||
func newBuilder(s *schema.Schema) *execBuilder {
|
||||
return &execBuilder{
|
||||
schema: s,
|
||||
resMap: make(map[typePair]*resMapEntry),
|
||||
packerBuilder: packer.NewBuilder(),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *execBuilder) finish() error {
|
||||
for _, entry := range b.resMap {
|
||||
for _, target := range entry.targets {
|
||||
*target = entry.exec
|
||||
}
|
||||
}
|
||||
|
||||
return b.packerBuilder.Finish()
|
||||
}
|
||||
|
||||
func (b *execBuilder) assignExec(target *Resolvable, t common.Type, resolverType reflect.Type) error {
|
||||
k := typePair{t, resolverType}
|
||||
ref, ok := b.resMap[k]
|
||||
if !ok {
|
||||
ref = &resMapEntry{}
|
||||
b.resMap[k] = ref
|
||||
var err error
|
||||
ref.exec, err = b.makeExec(t, resolverType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
ref.targets = append(ref.targets, target)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *execBuilder) makeExec(t common.Type, resolverType reflect.Type) (Resolvable, error) {
|
||||
var nonNull bool
|
||||
t, nonNull = unwrapNonNull(t)
|
||||
|
||||
switch t := t.(type) {
|
||||
case *schema.Object:
|
||||
return b.makeObjectExec(t.Name, t.Fields, nil, nonNull, resolverType)
|
||||
|
||||
case *schema.Interface:
|
||||
return b.makeObjectExec(t.Name, t.Fields, t.PossibleTypes, nonNull, resolverType)
|
||||
|
||||
case *schema.Union:
|
||||
return b.makeObjectExec(t.Name, nil, t.PossibleTypes, nonNull, resolverType)
|
||||
}
|
||||
|
||||
if !nonNull {
|
||||
if resolverType.Kind() != reflect.Ptr {
|
||||
return nil, fmt.Errorf("%s is not a pointer", resolverType)
|
||||
}
|
||||
resolverType = resolverType.Elem()
|
||||
}
|
||||
|
||||
switch t := t.(type) {
|
||||
case *schema.Scalar:
|
||||
return makeScalarExec(t, resolverType)
|
||||
|
||||
case *schema.Enum:
|
||||
return &Scalar{}, nil
|
||||
|
||||
case *common.List:
|
||||
if resolverType.Kind() != reflect.Slice {
|
||||
return nil, fmt.Errorf("%s is not a slice", resolverType)
|
||||
}
|
||||
e := &List{}
|
||||
if err := b.assignExec(&e.Elem, t.OfType, resolverType.Elem()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e, nil
|
||||
|
||||
default:
|
||||
panic("invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
func makeScalarExec(t *schema.Scalar, resolverType reflect.Type) (Resolvable, error) {
|
||||
implementsType := false
|
||||
switch r := reflect.New(resolverType).Interface().(type) {
|
||||
case *int32:
|
||||
implementsType = (t.Name == "Int")
|
||||
case *float64:
|
||||
implementsType = (t.Name == "Float")
|
||||
case *string:
|
||||
implementsType = (t.Name == "String")
|
||||
case *bool:
|
||||
implementsType = (t.Name == "Boolean")
|
||||
case packer.Unmarshaler:
|
||||
implementsType = r.ImplementsGraphQLType(t.Name)
|
||||
}
|
||||
if !implementsType {
|
||||
return nil, fmt.Errorf("can not use %s as %s", resolverType, t.Name)
|
||||
}
|
||||
return &Scalar{}, nil
|
||||
}
|
||||
|
||||
func (b *execBuilder) makeObjectExec(typeName string, fields schema.FieldList, possibleTypes []*schema.Object, nonNull bool, resolverType reflect.Type) (*Object, error) {
|
||||
if !nonNull {
|
||||
if resolverType.Kind() != reflect.Ptr && resolverType.Kind() != reflect.Interface {
|
||||
return nil, fmt.Errorf("%s is not a pointer or interface", resolverType)
|
||||
}
|
||||
}
|
||||
|
||||
methodHasReceiver := resolverType.Kind() != reflect.Interface
|
||||
|
||||
Fields := make(map[string]*Field)
|
||||
for _, f := range fields {
|
||||
methodIndex := findMethod(resolverType, f.Name)
|
||||
if methodIndex == -1 {
|
||||
hint := ""
|
||||
if findMethod(reflect.PtrTo(resolverType), f.Name) != -1 {
|
||||
hint = " (hint: the method exists on the pointer type)"
|
||||
}
|
||||
return nil, fmt.Errorf("%s does not resolve %q: missing method for field %q%s", resolverType, typeName, f.Name, hint)
|
||||
}
|
||||
|
||||
m := resolverType.Method(methodIndex)
|
||||
fe, err := b.makeFieldExec(typeName, f, m, methodIndex, methodHasReceiver)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s\n\treturned by (%s).%s", err, resolverType, m.Name)
|
||||
}
|
||||
Fields[f.Name] = fe
|
||||
}
|
||||
|
||||
typeAssertions := make(map[string]*TypeAssertion)
|
||||
for _, impl := range possibleTypes {
|
||||
methodIndex := findMethod(resolverType, "to"+impl.Name)
|
||||
if methodIndex == -1 {
|
||||
return nil, fmt.Errorf("%s does not resolve %q: missing method %q to convert to %q", resolverType, typeName, "to"+impl.Name, impl.Name)
|
||||
}
|
||||
if resolverType.Method(methodIndex).Type.NumOut() != 2 {
|
||||
return nil, fmt.Errorf("%s does not resolve %q: method %q should return a value and a bool indicating success", resolverType, typeName, "to"+impl.Name)
|
||||
}
|
||||
a := &TypeAssertion{
|
||||
MethodIndex: methodIndex,
|
||||
}
|
||||
if err := b.assignExec(&a.TypeExec, impl, resolverType.Method(methodIndex).Type.Out(0)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typeAssertions[impl.Name] = a
|
||||
}
|
||||
|
||||
return &Object{
|
||||
Name: typeName,
|
||||
Fields: Fields,
|
||||
TypeAssertions: typeAssertions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
|
||||
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
func (b *execBuilder) makeFieldExec(typeName string, f *schema.Field, m reflect.Method, methodIndex int, methodHasReceiver bool) (*Field, error) {
|
||||
in := make([]reflect.Type, m.Type.NumIn())
|
||||
for i := range in {
|
||||
in[i] = m.Type.In(i)
|
||||
}
|
||||
if methodHasReceiver {
|
||||
in = in[1:] // first parameter is receiver
|
||||
}
|
||||
|
||||
hasContext := len(in) > 0 && in[0] == contextType
|
||||
if hasContext {
|
||||
in = in[1:]
|
||||
}
|
||||
|
||||
var argsPacker *packer.StructPacker
|
||||
if len(f.Args) > 0 {
|
||||
if len(in) == 0 {
|
||||
return nil, fmt.Errorf("must have parameter for field arguments")
|
||||
}
|
||||
var err error
|
||||
argsPacker, err = b.packerBuilder.MakeStructPacker(f.Args, in[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
in = in[1:]
|
||||
}
|
||||
|
||||
if len(in) > 0 {
|
||||
return nil, fmt.Errorf("too many parameters")
|
||||
}
|
||||
|
||||
if m.Type.NumOut() > 2 {
|
||||
return nil, fmt.Errorf("too many return values")
|
||||
}
|
||||
|
||||
hasError := m.Type.NumOut() == 2
|
||||
if hasError {
|
||||
if m.Type.Out(1) != errorType {
|
||||
return nil, fmt.Errorf(`must have "error" as its second return value`)
|
||||
}
|
||||
}
|
||||
|
||||
fe := &Field{
|
||||
Field: *f,
|
||||
TypeName: typeName,
|
||||
MethodIndex: methodIndex,
|
||||
HasContext: hasContext,
|
||||
ArgsPacker: argsPacker,
|
||||
HasError: hasError,
|
||||
TraceLabel: fmt.Sprintf("GraphQL field: %s.%s", typeName, f.Name),
|
||||
}
|
||||
if err := b.assignExec(&fe.ValueExec, f.Type, m.Type.Out(0)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fe, nil
|
||||
}
|
||||
|
||||
func findMethod(t reflect.Type, name string) int {
|
||||
for i := 0; i < t.NumMethod(); i++ {
|
||||
if strings.EqualFold(stripUnderscore(name), stripUnderscore(t.Method(i).Name)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func unwrapNonNull(t common.Type) (common.Type, bool) {
|
||||
if nn, ok := t.(*common.NonNull); ok {
|
||||
return nn.OfType, true
|
||||
}
|
||||
return t, false
|
||||
}
|
||||
|
||||
func stripUnderscore(s string) string {
|
||||
return strings.Replace(s, "_", "", -1)
|
||||
}
|
238
vendor/github.com/neelance/graphql-go/internal/exec/selected/selected.go
generated
vendored
Normal file
238
vendor/github.com/neelance/graphql-go/internal/exec/selected/selected.go
generated
vendored
Normal file
@ -0,0 +1,238 @@
|
||||
package selected
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/neelance/graphql-go/errors"
|
||||
"github.com/neelance/graphql-go/internal/common"
|
||||
"github.com/neelance/graphql-go/internal/exec/packer"
|
||||
"github.com/neelance/graphql-go/internal/exec/resolvable"
|
||||
"github.com/neelance/graphql-go/internal/query"
|
||||
"github.com/neelance/graphql-go/internal/schema"
|
||||
"github.com/neelance/graphql-go/introspection"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
Schema *schema.Schema
|
||||
Doc *query.Document
|
||||
Vars map[string]interface{}
|
||||
Mu sync.Mutex
|
||||
Errs []*errors.QueryError
|
||||
}
|
||||
|
||||
func (r *Request) AddError(err *errors.QueryError) {
|
||||
r.Mu.Lock()
|
||||
r.Errs = append(r.Errs, err)
|
||||
r.Mu.Unlock()
|
||||
}
|
||||
|
||||
func ApplyOperation(r *Request, s *resolvable.Schema, op *query.Operation) []Selection {
|
||||
var obj *resolvable.Object
|
||||
switch op.Type {
|
||||
case query.Query:
|
||||
obj = s.Query.(*resolvable.Object)
|
||||
case query.Mutation:
|
||||
obj = s.Mutation.(*resolvable.Object)
|
||||
}
|
||||
return applySelectionSet(r, obj, op.Selections)
|
||||
}
|
||||
|
||||
type Selection interface {
|
||||
isSelection()
|
||||
}
|
||||
|
||||
type SchemaField struct {
|
||||
resolvable.Field
|
||||
Alias string
|
||||
Args map[string]interface{}
|
||||
PackedArgs reflect.Value
|
||||
Sels []Selection
|
||||
Async bool
|
||||
FixedResult reflect.Value
|
||||
}
|
||||
|
||||
type TypeAssertion struct {
|
||||
resolvable.TypeAssertion
|
||||
Sels []Selection
|
||||
}
|
||||
|
||||
type TypenameField struct {
|
||||
resolvable.Object
|
||||
Alias string
|
||||
}
|
||||
|
||||
func (*SchemaField) isSelection() {}
|
||||
func (*TypeAssertion) isSelection() {}
|
||||
func (*TypenameField) isSelection() {}
|
||||
|
||||
func applySelectionSet(r *Request, e *resolvable.Object, sels []query.Selection) (flattenedSels []Selection) {
|
||||
for _, sel := range sels {
|
||||
switch sel := sel.(type) {
|
||||
case *query.Field:
|
||||
field := sel
|
||||
if skipByDirective(r, field.Directives) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch field.Name.Name {
|
||||
case "__typename":
|
||||
flattenedSels = append(flattenedSels, &TypenameField{
|
||||
Object: *e,
|
||||
Alias: field.Alias.Name,
|
||||
})
|
||||
|
||||
case "__schema":
|
||||
flattenedSels = append(flattenedSels, &SchemaField{
|
||||
Field: resolvable.MetaFieldSchema,
|
||||
Alias: field.Alias.Name,
|
||||
Sels: applySelectionSet(r, resolvable.MetaSchema, field.Selections),
|
||||
Async: true,
|
||||
FixedResult: reflect.ValueOf(introspection.WrapSchema(r.Schema)),
|
||||
})
|
||||
|
||||
case "__type":
|
||||
p := packer.ValuePacker{ValueType: reflect.TypeOf("")}
|
||||
v, err := p.Pack(field.Arguments.MustGet("name").Value(r.Vars))
|
||||
if err != nil {
|
||||
r.AddError(errors.Errorf("%s", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
t, ok := r.Schema.Types[v.String()]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
flattenedSels = append(flattenedSels, &SchemaField{
|
||||
Field: resolvable.MetaFieldType,
|
||||
Alias: field.Alias.Name,
|
||||
Sels: applySelectionSet(r, resolvable.MetaType, field.Selections),
|
||||
Async: true,
|
||||
FixedResult: reflect.ValueOf(introspection.WrapType(t)),
|
||||
})
|
||||
|
||||
default:
|
||||
fe := e.Fields[field.Name.Name]
|
||||
|
||||
var args map[string]interface{}
|
||||
var packedArgs reflect.Value
|
||||
if fe.ArgsPacker != nil {
|
||||
args = make(map[string]interface{})
|
||||
for _, arg := range field.Arguments {
|
||||
args[arg.Name.Name] = arg.Value.Value(r.Vars)
|
||||
}
|
||||
var err error
|
||||
packedArgs, err = fe.ArgsPacker.Pack(args)
|
||||
if err != nil {
|
||||
r.AddError(errors.Errorf("%s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fieldSels := applyField(r, fe.ValueExec, field.Selections)
|
||||
flattenedSels = append(flattenedSels, &SchemaField{
|
||||
Field: *fe,
|
||||
Alias: field.Alias.Name,
|
||||
Args: args,
|
||||
PackedArgs: packedArgs,
|
||||
Sels: fieldSels,
|
||||
Async: fe.HasContext || fe.ArgsPacker != nil || fe.HasError || HasAsyncSel(fieldSels),
|
||||
})
|
||||
}
|
||||
|
||||
case *query.InlineFragment:
|
||||
frag := sel
|
||||
if skipByDirective(r, frag.Directives) {
|
||||
continue
|
||||
}
|
||||
flattenedSels = append(flattenedSels, applyFragment(r, e, &frag.Fragment)...)
|
||||
|
||||
case *query.FragmentSpread:
|
||||
spread := sel
|
||||
if skipByDirective(r, spread.Directives) {
|
||||
continue
|
||||
}
|
||||
flattenedSels = append(flattenedSels, applyFragment(r, e, &r.Doc.Fragments.Get(spread.Name.Name).Fragment)...)
|
||||
|
||||
default:
|
||||
panic("invalid type")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func applyFragment(r *Request, e *resolvable.Object, frag *query.Fragment) []Selection {
|
||||
if frag.On.Name != "" && frag.On.Name != e.Name {
|
||||
a, ok := e.TypeAssertions[frag.On.Name]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("%q does not implement %q", frag.On, e.Name)) // TODO proper error handling
|
||||
}
|
||||
|
||||
return []Selection{&TypeAssertion{
|
||||
TypeAssertion: *a,
|
||||
Sels: applySelectionSet(r, a.TypeExec.(*resolvable.Object), frag.Selections),
|
||||
}}
|
||||
}
|
||||
return applySelectionSet(r, e, frag.Selections)
|
||||
}
|
||||
|
||||
func applyField(r *Request, e resolvable.Resolvable, sels []query.Selection) []Selection {
|
||||
switch e := e.(type) {
|
||||
case *resolvable.Object:
|
||||
return applySelectionSet(r, e, sels)
|
||||
case *resolvable.List:
|
||||
return applyField(r, e.Elem, sels)
|
||||
case *resolvable.Scalar:
|
||||
return nil
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func skipByDirective(r *Request, directives common.DirectiveList) bool {
|
||||
if d := directives.Get("skip"); d != nil {
|
||||
p := packer.ValuePacker{ValueType: reflect.TypeOf(false)}
|
||||
v, err := p.Pack(d.Args.MustGet("if").Value(r.Vars))
|
||||
if err != nil {
|
||||
r.AddError(errors.Errorf("%s", err))
|
||||
}
|
||||
if err == nil && v.Bool() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if d := directives.Get("include"); d != nil {
|
||||
p := packer.ValuePacker{ValueType: reflect.TypeOf(false)}
|
||||
v, err := p.Pack(d.Args.MustGet("if").Value(r.Vars))
|
||||
if err != nil {
|
||||
r.AddError(errors.Errorf("%s", err))
|
||||
}
|
||||
if err == nil && !v.Bool() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func HasAsyncSel(sels []Selection) bool {
|
||||
for _, sel := range sels {
|
||||
switch sel := sel.(type) {
|
||||
case *SchemaField:
|
||||
if sel.Async {
|
||||
return true
|
||||
}
|
||||
case *TypeAssertion:
|
||||
if HasAsyncSel(sel.Sels) {
|
||||
return true
|
||||
}
|
||||
case *TypenameField:
|
||||
// sync
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
240
vendor/github.com/neelance/graphql-go/internal/query/query.go
generated
vendored
Normal file
240
vendor/github.com/neelance/graphql-go/internal/query/query.go
generated
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/neelance/graphql-go/errors"
|
||||
"github.com/neelance/graphql-go/internal/common"
|
||||
)
|
||||
|
||||
type Document struct {
|
||||
Operations OperationList
|
||||
Fragments FragmentList
|
||||
}
|
||||
|
||||
type OperationList []*Operation
|
||||
|
||||
func (l OperationList) Get(name string) *Operation {
|
||||
for _, f := range l {
|
||||
if f.Name.Name == name {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FragmentList []*FragmentDecl
|
||||
|
||||
func (l FragmentList) Get(name string) *FragmentDecl {
|
||||
for _, f := range l {
|
||||
if f.Name.Name == name {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Operation struct {
|
||||
Type OperationType
|
||||
Name common.Ident
|
||||
Vars common.InputValueList
|
||||
Selections []Selection
|
||||
Directives common.DirectiveList
|
||||
Loc errors.Location
|
||||
}
|
||||
|
||||
type OperationType string
|
||||
|
||||
const (
|
||||
Query OperationType = "QUERY"
|
||||
Mutation = "MUTATION"
|
||||
Subscription = "SUBSCRIPTION"
|
||||
)
|
||||
|
||||
type Fragment struct {
|
||||
On common.TypeName
|
||||
Selections []Selection
|
||||
}
|
||||
|
||||
type FragmentDecl struct {
|
||||
Fragment
|
||||
Name common.Ident
|
||||
Directives common.DirectiveList
|
||||
Loc errors.Location
|
||||
}
|
||||
|
||||
type Selection interface {
|
||||
isSelection()
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Alias common.Ident
|
||||
Name common.Ident
|
||||
Arguments common.ArgumentList
|
||||
Directives common.DirectiveList
|
||||
Selections []Selection
|
||||
SelectionSetLoc errors.Location
|
||||
}
|
||||
|
||||
type InlineFragment struct {
|
||||
Fragment
|
||||
Directives common.DirectiveList
|
||||
Loc errors.Location
|
||||
}
|
||||
|
||||
type FragmentSpread struct {
|
||||
Name common.Ident
|
||||
Directives common.DirectiveList
|
||||
Loc errors.Location
|
||||
}
|
||||
|
||||
func (Field) isSelection() {}
|
||||
func (InlineFragment) isSelection() {}
|
||||
func (FragmentSpread) isSelection() {}
|
||||
|
||||
func Parse(queryString string) (*Document, *errors.QueryError) {
|
||||
sc := &scanner.Scanner{
|
||||
Mode: scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings,
|
||||
}
|
||||
sc.Init(strings.NewReader(queryString))
|
||||
|
||||
l := common.New(sc)
|
||||
var doc *Document
|
||||
err := l.CatchSyntaxError(func() {
|
||||
doc = parseDocument(l)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
func parseDocument(l *common.Lexer) *Document {
|
||||
d := &Document{}
|
||||
for l.Peek() != scanner.EOF {
|
||||
if l.Peek() == '{' {
|
||||
op := &Operation{Type: Query, Loc: l.Location()}
|
||||
op.Selections = parseSelectionSet(l)
|
||||
d.Operations = append(d.Operations, op)
|
||||
continue
|
||||
}
|
||||
|
||||
loc := l.Location()
|
||||
switch x := l.ConsumeIdent(); x {
|
||||
case "query":
|
||||
op := parseOperation(l, Query)
|
||||
op.Loc = loc
|
||||
d.Operations = append(d.Operations, op)
|
||||
|
||||
case "mutation":
|
||||
d.Operations = append(d.Operations, parseOperation(l, Mutation))
|
||||
|
||||
case "subscription":
|
||||
d.Operations = append(d.Operations, parseOperation(l, Subscription))
|
||||
|
||||
case "fragment":
|
||||
frag := parseFragment(l)
|
||||
frag.Loc = loc
|
||||
d.Fragments = append(d.Fragments, frag)
|
||||
|
||||
default:
|
||||
l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "fragment"`, x))
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func parseOperation(l *common.Lexer, opType OperationType) *Operation {
|
||||
op := &Operation{Type: opType}
|
||||
op.Name.Loc = l.Location()
|
||||
if l.Peek() == scanner.Ident {
|
||||
op.Name = l.ConsumeIdentWithLoc()
|
||||
}
|
||||
op.Directives = common.ParseDirectives(l)
|
||||
if l.Peek() == '(' {
|
||||
l.ConsumeToken('(')
|
||||
for l.Peek() != ')' {
|
||||
loc := l.Location()
|
||||
l.ConsumeToken('$')
|
||||
iv := common.ParseInputValue(l)
|
||||
iv.Loc = loc
|
||||
op.Vars = append(op.Vars, iv)
|
||||
}
|
||||
l.ConsumeToken(')')
|
||||
}
|
||||
op.Selections = parseSelectionSet(l)
|
||||
return op
|
||||
}
|
||||
|
||||
func parseFragment(l *common.Lexer) *FragmentDecl {
|
||||
f := &FragmentDecl{}
|
||||
f.Name = l.ConsumeIdentWithLoc()
|
||||
l.ConsumeKeyword("on")
|
||||
f.On = common.TypeName{Ident: l.ConsumeIdentWithLoc()}
|
||||
f.Directives = common.ParseDirectives(l)
|
||||
f.Selections = parseSelectionSet(l)
|
||||
return f
|
||||
}
|
||||
|
||||
func parseSelectionSet(l *common.Lexer) []Selection {
|
||||
var sels []Selection
|
||||
l.ConsumeToken('{')
|
||||
for l.Peek() != '}' {
|
||||
sels = append(sels, parseSelection(l))
|
||||
}
|
||||
l.ConsumeToken('}')
|
||||
return sels
|
||||
}
|
||||
|
||||
func parseSelection(l *common.Lexer) Selection {
|
||||
if l.Peek() == '.' {
|
||||
return parseSpread(l)
|
||||
}
|
||||
return parseField(l)
|
||||
}
|
||||
|
||||
func parseField(l *common.Lexer) *Field {
|
||||
f := &Field{}
|
||||
f.Alias = l.ConsumeIdentWithLoc()
|
||||
f.Name = f.Alias
|
||||
if l.Peek() == ':' {
|
||||
l.ConsumeToken(':')
|
||||
f.Name = l.ConsumeIdentWithLoc()
|
||||
}
|
||||
if l.Peek() == '(' {
|
||||
f.Arguments = common.ParseArguments(l)
|
||||
}
|
||||
f.Directives = common.ParseDirectives(l)
|
||||
if l.Peek() == '{' {
|
||||
f.SelectionSetLoc = l.Location()
|
||||
f.Selections = parseSelectionSet(l)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func parseSpread(l *common.Lexer) Selection {
|
||||
loc := l.Location()
|
||||
l.ConsumeToken('.')
|
||||
l.ConsumeToken('.')
|
||||
l.ConsumeToken('.')
|
||||
|
||||
f := &InlineFragment{Loc: loc}
|
||||
if l.Peek() == scanner.Ident {
|
||||
ident := l.ConsumeIdentWithLoc()
|
||||
if ident.Name != "on" {
|
||||
fs := &FragmentSpread{
|
||||
Name: ident,
|
||||
Loc: loc,
|
||||
}
|
||||
fs.Directives = common.ParseDirectives(l)
|
||||
return fs
|
||||
}
|
||||
f.On = common.TypeName{Ident: l.ConsumeIdentWithLoc()}
|
||||
}
|
||||
f.Directives = common.ParseDirectives(l)
|
||||
f.Selections = parseSelectionSet(l)
|
||||
return f
|
||||
}
|
190
vendor/github.com/neelance/graphql-go/internal/schema/meta.go
generated
vendored
Normal file
190
vendor/github.com/neelance/graphql-go/internal/schema/meta.go
generated
vendored
Normal file
@ -0,0 +1,190 @@
|
||||
package schema
|
||||
|
||||
var Meta *Schema
|
||||
|
||||
func init() {
|
||||
Meta = &Schema{} // bootstrap
|
||||
Meta = New()
|
||||
if err := Meta.Parse(metaSrc); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
var metaSrc = `
|
||||
# The ` + "`" + `Int` + "`" + ` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.
|
||||
scalar Int
|
||||
|
||||
# The ` + "`" + `Float` + "`" + ` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).
|
||||
scalar Float
|
||||
|
||||
# The ` + "`" + `String` + "`" + ` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
|
||||
scalar String
|
||||
|
||||
# The ` + "`" + `Boolean` + "`" + ` scalar type represents ` + "`" + `true` + "`" + ` or ` + "`" + `false` + "`" + `.
|
||||
scalar Boolean
|
||||
|
||||
# The ` + "`" + `ID` + "`" + ` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as ` + "`" + `"4"` + "`" + `) or integer (such as ` + "`" + `4` + "`" + `) input value will be accepted as an ID.
|
||||
scalar ID
|
||||
|
||||
# Directs the executor to include this field or fragment only when the ` + "`" + `if` + "`" + ` argument is true.
|
||||
directive @include(
|
||||
# Included when true.
|
||||
if: Boolean!
|
||||
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
|
||||
|
||||
# Directs the executor to skip this field or fragment when the ` + "`" + `if` + "`" + ` argument is true.
|
||||
directive @skip(
|
||||
# Skipped when true.
|
||||
if: Boolean!
|
||||
) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
|
||||
|
||||
# Marks an element of a GraphQL schema as no longer supported.
|
||||
directive @deprecated(
|
||||
# Explains why this element was deprecated, usually also including a suggestion
|
||||
# for how to access supported similar data. Formatted in
|
||||
# [Markdown](https://daringfireball.net/projects/markdown/).
|
||||
reason: String = "No longer supported"
|
||||
) on FIELD_DEFINITION | ENUM_VALUE
|
||||
|
||||
# A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.
|
||||
#
|
||||
# In some cases, you need to provide options to alter GraphQL's execution behavior
|
||||
# in ways field arguments will not suffice, such as conditionally including or
|
||||
# skipping a field. Directives provide this by describing additional information
|
||||
# to the executor.
|
||||
type __Directive {
|
||||
name: String!
|
||||
description: String
|
||||
locations: [__DirectiveLocation!]!
|
||||
args: [__InputValue!]!
|
||||
}
|
||||
|
||||
# A Directive can be adjacent to many parts of the GraphQL language, a
|
||||
# __DirectiveLocation describes one such possible adjacencies.
|
||||
enum __DirectiveLocation {
|
||||
# Location adjacent to a query operation.
|
||||
QUERY
|
||||
# Location adjacent to a mutation operation.
|
||||
MUTATION
|
||||
# Location adjacent to a subscription operation.
|
||||
SUBSCRIPTION
|
||||
# Location adjacent to a field.
|
||||
FIELD
|
||||
# Location adjacent to a fragment definition.
|
||||
FRAGMENT_DEFINITION
|
||||
# Location adjacent to a fragment spread.
|
||||
FRAGMENT_SPREAD
|
||||
# Location adjacent to an inline fragment.
|
||||
INLINE_FRAGMENT
|
||||
# Location adjacent to a schema definition.
|
||||
SCHEMA
|
||||
# Location adjacent to a scalar definition.
|
||||
SCALAR
|
||||
# Location adjacent to an object type definition.
|
||||
OBJECT
|
||||
# Location adjacent to a field definition.
|
||||
FIELD_DEFINITION
|
||||
# Location adjacent to an argument definition.
|
||||
ARGUMENT_DEFINITION
|
||||
# Location adjacent to an interface definition.
|
||||
INTERFACE
|
||||
# Location adjacent to a union definition.
|
||||
UNION
|
||||
# Location adjacent to an enum definition.
|
||||
ENUM
|
||||
# Location adjacent to an enum value definition.
|
||||
ENUM_VALUE
|
||||
# Location adjacent to an input object type definition.
|
||||
INPUT_OBJECT
|
||||
# Location adjacent to an input object field definition.
|
||||
INPUT_FIELD_DEFINITION
|
||||
}
|
||||
|
||||
# One possible value for a given Enum. Enum values are unique values, not a
|
||||
# placeholder for a string or numeric value. However an Enum value is returned in
|
||||
# a JSON response as a string.
|
||||
type __EnumValue {
|
||||
name: String!
|
||||
description: String
|
||||
isDeprecated: Boolean!
|
||||
deprecationReason: String
|
||||
}
|
||||
|
||||
# Object and Interface types are described by a list of Fields, each of which has
|
||||
# a name, potentially a list of arguments, and a return type.
|
||||
type __Field {
|
||||
name: String!
|
||||
description: String
|
||||
args: [__InputValue!]!
|
||||
type: __Type!
|
||||
isDeprecated: Boolean!
|
||||
deprecationReason: String
|
||||
}
|
||||
|
||||
# Arguments provided to Fields or Directives and the input fields of an
|
||||
# InputObject are represented as Input Values which describe their type and
|
||||
# optionally a default value.
|
||||
type __InputValue {
|
||||
name: String!
|
||||
description: String
|
||||
type: __Type!
|
||||
# A GraphQL-formatted string representing the default value for this input value.
|
||||
defaultValue: String
|
||||
}
|
||||
|
||||
# A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all
|
||||
# available types and directives on the server, as well as the entry points for
|
||||
# query, mutation, and subscription operations.
|
||||
type __Schema {
|
||||
# A list of all types supported by this server.
|
||||
types: [__Type!]!
|
||||
# The type that query operations will be rooted at.
|
||||
queryType: __Type!
|
||||
# If this server supports mutation, the type that mutation operations will be rooted at.
|
||||
mutationType: __Type
|
||||
# If this server support subscription, the type that subscription operations will be rooted at.
|
||||
subscriptionType: __Type
|
||||
# A list of all directives supported by this server.
|
||||
directives: [__Directive!]!
|
||||
}
|
||||
|
||||
# The fundamental unit of any GraphQL Schema is the type. There are many kinds of
|
||||
# types in GraphQL as represented by the ` + "`" + `__TypeKind` + "`" + ` enum.
|
||||
#
|
||||
# Depending on the kind of a type, certain fields describe information about that
|
||||
# type. Scalar types provide no information beyond a name and description, while
|
||||
# Enum types provide their values. Object and Interface types provide the fields
|
||||
# they describe. Abstract types, Union and Interface, provide the Object types
|
||||
# possible at runtime. List and NonNull types compose other types.
|
||||
type __Type {
|
||||
kind: __TypeKind!
|
||||
name: String
|
||||
description: String
|
||||
fields(includeDeprecated: Boolean = false): [__Field!]
|
||||
interfaces: [__Type!]
|
||||
possibleTypes: [__Type!]
|
||||
enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
|
||||
inputFields: [__InputValue!]
|
||||
ofType: __Type
|
||||
}
|
||||
|
||||
# An enum describing what kind of type a given ` + "`" + `__Type` + "`" + ` is.
|
||||
enum __TypeKind {
|
||||
# Indicates this type is a scalar.
|
||||
SCALAR
|
||||
# Indicates this type is an object. ` + "`" + `fields` + "`" + ` and ` + "`" + `interfaces` + "`" + ` are valid fields.
|
||||
OBJECT
|
||||
# Indicates this type is an interface. ` + "`" + `fields` + "`" + ` and ` + "`" + `possibleTypes` + "`" + ` are valid fields.
|
||||
INTERFACE
|
||||
# Indicates this type is a union. ` + "`" + `possibleTypes` + "`" + ` is a valid field.
|
||||
UNION
|
||||
# Indicates this type is an enum. ` + "`" + `enumValues` + "`" + ` is a valid field.
|
||||
ENUM
|
||||
# Indicates this type is an input object. ` + "`" + `inputFields` + "`" + ` is a valid field.
|
||||
INPUT_OBJECT
|
||||
# Indicates this type is a list. ` + "`" + `ofType` + "`" + ` is a valid field.
|
||||
LIST
|
||||
# Indicates this type is a non-null. ` + "`" + `ofType` + "`" + ` is a valid field.
|
||||
NON_NULL
|
||||
}
|
||||
`
|
462
vendor/github.com/neelance/graphql-go/internal/schema/schema.go
generated
vendored
Normal file
462
vendor/github.com/neelance/graphql-go/internal/schema/schema.go
generated
vendored
Normal file
@ -0,0 +1,462 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/neelance/graphql-go/errors"
|
||||
"github.com/neelance/graphql-go/internal/common"
|
||||
)
|
||||
|
||||
type Schema struct {
|
||||
EntryPoints map[string]NamedType
|
||||
Types map[string]NamedType
|
||||
Directives map[string]*DirectiveDecl
|
||||
|
||||
entryPointNames map[string]string
|
||||
objects []*Object
|
||||
unions []*Union
|
||||
enums []*Enum
|
||||
}
|
||||
|
||||
func (s *Schema) Resolve(name string) common.Type {
|
||||
return s.Types[name]
|
||||
}
|
||||
|
||||
type NamedType interface {
|
||||
common.Type
|
||||
TypeName() string
|
||||
Description() string
|
||||
}
|
||||
|
||||
type Scalar struct {
|
||||
Name string
|
||||
Desc string
|
||||
}
|
||||
|
||||
type Object struct {
|
||||
Name string
|
||||
Interfaces []*Interface
|
||||
Fields FieldList
|
||||
Desc string
|
||||
|
||||
interfaceNames []string
|
||||
}
|
||||
|
||||
type Interface struct {
|
||||
Name string
|
||||
PossibleTypes []*Object
|
||||
Fields FieldList
|
||||
Desc string
|
||||
}
|
||||
|
||||
type Union struct {
|
||||
Name string
|
||||
PossibleTypes []*Object
|
||||
Desc string
|
||||
|
||||
typeNames []string
|
||||
}
|
||||
|
||||
type Enum struct {
|
||||
Name string
|
||||
Values []*EnumValue
|
||||
Desc string
|
||||
}
|
||||
|
||||
type EnumValue struct {
|
||||
Name string
|
||||
Directives common.DirectiveList
|
||||
Desc string
|
||||
}
|
||||
|
||||
type InputObject struct {
|
||||
Name string
|
||||
Desc string
|
||||
Values common.InputValueList
|
||||
}
|
||||
|
||||
type FieldList []*Field
|
||||
|
||||
func (l FieldList) Get(name string) *Field {
|
||||
for _, f := range l {
|
||||
if f.Name == name {
|
||||
return f
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l FieldList) Names() []string {
|
||||
names := make([]string, len(l))
|
||||
for i, f := range l {
|
||||
names[i] = f.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
type DirectiveDecl struct {
|
||||
Name string
|
||||
Desc string
|
||||
Locs []string
|
||||
Args common.InputValueList
|
||||
}
|
||||
|
||||
func (*Scalar) Kind() string { return "SCALAR" }
|
||||
func (*Object) Kind() string { return "OBJECT" }
|
||||
func (*Interface) Kind() string { return "INTERFACE" }
|
||||
func (*Union) Kind() string { return "UNION" }
|
||||
func (*Enum) Kind() string { return "ENUM" }
|
||||
func (*InputObject) Kind() string { return "INPUT_OBJECT" }
|
||||
|
||||
func (t *Scalar) String() string { return t.Name }
|
||||
func (t *Object) String() string { return t.Name }
|
||||
func (t *Interface) String() string { return t.Name }
|
||||
func (t *Union) String() string { return t.Name }
|
||||
func (t *Enum) String() string { return t.Name }
|
||||
func (t *InputObject) String() string { return t.Name }
|
||||
|
||||
func (t *Scalar) TypeName() string { return t.Name }
|
||||
func (t *Object) TypeName() string { return t.Name }
|
||||
func (t *Interface) TypeName() string { return t.Name }
|
||||
func (t *Union) TypeName() string { return t.Name }
|
||||
func (t *Enum) TypeName() string { return t.Name }
|
||||
func (t *InputObject) TypeName() string { return t.Name }
|
||||
|
||||
func (t *Scalar) Description() string { return t.Desc }
|
||||
func (t *Object) Description() string { return t.Desc }
|
||||
func (t *Interface) Description() string { return t.Desc }
|
||||
func (t *Union) Description() string { return t.Desc }
|
||||
func (t *Enum) Description() string { return t.Desc }
|
||||
func (t *InputObject) Description() string { return t.Desc }
|
||||
|
||||
type Field struct {
|
||||
Name string
|
||||
Args common.InputValueList
|
||||
Type common.Type
|
||||
Directives common.DirectiveList
|
||||
Desc string
|
||||
}
|
||||
|
||||
func New() *Schema {
|
||||
s := &Schema{
|
||||
entryPointNames: make(map[string]string),
|
||||
Types: make(map[string]NamedType),
|
||||
Directives: make(map[string]*DirectiveDecl),
|
||||
}
|
||||
for n, t := range Meta.Types {
|
||||
s.Types[n] = t
|
||||
}
|
||||
for n, d := range Meta.Directives {
|
||||
s.Directives[n] = d
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Schema) Parse(schemaString string) error {
|
||||
sc := &scanner.Scanner{
|
||||
Mode: scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings,
|
||||
}
|
||||
sc.Init(strings.NewReader(schemaString))
|
||||
|
||||
l := common.New(sc)
|
||||
err := l.CatchSyntaxError(func() {
|
||||
parseSchema(s, l)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, t := range s.Types {
|
||||
if err := resolveNamedType(s, t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, d := range s.Directives {
|
||||
for _, arg := range d.Args {
|
||||
t, err := common.ResolveType(arg.Type, s.Resolve)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
arg.Type = t
|
||||
}
|
||||
}
|
||||
|
||||
s.EntryPoints = make(map[string]NamedType)
|
||||
for key, name := range s.entryPointNames {
|
||||
t, ok := s.Types[name]
|
||||
if !ok {
|
||||
if !ok {
|
||||
return errors.Errorf("type %q not found", name)
|
||||
}
|
||||
}
|
||||
s.EntryPoints[key] = t
|
||||
}
|
||||
|
||||
for _, obj := range s.objects {
|
||||
obj.Interfaces = make([]*Interface, len(obj.interfaceNames))
|
||||
for i, intfName := range obj.interfaceNames {
|
||||
t, ok := s.Types[intfName]
|
||||
if !ok {
|
||||
return errors.Errorf("interface %q not found", intfName)
|
||||
}
|
||||
intf, ok := t.(*Interface)
|
||||
if !ok {
|
||||
return errors.Errorf("type %q is not an interface", intfName)
|
||||
}
|
||||
obj.Interfaces[i] = intf
|
||||
intf.PossibleTypes = append(intf.PossibleTypes, obj)
|
||||
}
|
||||
}
|
||||
|
||||
for _, union := range s.unions {
|
||||
union.PossibleTypes = make([]*Object, len(union.typeNames))
|
||||
for i, name := range union.typeNames {
|
||||
t, ok := s.Types[name]
|
||||
if !ok {
|
||||
return errors.Errorf("object type %q not found", name)
|
||||
}
|
||||
obj, ok := t.(*Object)
|
||||
if !ok {
|
||||
return errors.Errorf("type %q is not an object", name)
|
||||
}
|
||||
union.PossibleTypes[i] = obj
|
||||
}
|
||||
}
|
||||
|
||||
for _, enum := range s.enums {
|
||||
for _, value := range enum.Values {
|
||||
if err := resolveDirectives(s, value.Directives); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveNamedType(s *Schema, t NamedType) error {
|
||||
switch t := t.(type) {
|
||||
case *Object:
|
||||
for _, f := range t.Fields {
|
||||
if err := resolveField(s, f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case *Interface:
|
||||
for _, f := range t.Fields {
|
||||
if err := resolveField(s, f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case *InputObject:
|
||||
if err := resolveInputObject(s, t.Values); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveField(s *Schema, f *Field) error {
|
||||
t, err := common.ResolveType(f.Type, s.Resolve)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Type = t
|
||||
if err := resolveDirectives(s, f.Directives); err != nil {
|
||||
return err
|
||||
}
|
||||
return resolveInputObject(s, f.Args)
|
||||
}
|
||||
|
||||
func resolveDirectives(s *Schema, directives common.DirectiveList) error {
|
||||
for _, d := range directives {
|
||||
dirName := d.Name.Name
|
||||
dd, ok := s.Directives[dirName]
|
||||
if !ok {
|
||||
return errors.Errorf("directive %q not found", dirName)
|
||||
}
|
||||
for _, arg := range d.Args {
|
||||
if dd.Args.Get(arg.Name.Name) == nil {
|
||||
return errors.Errorf("invalid argument %q for directive %q", arg.Name.Name, dirName)
|
||||
}
|
||||
}
|
||||
for _, arg := range dd.Args {
|
||||
if _, ok := d.Args.Get(arg.Name.Name); !ok {
|
||||
d.Args = append(d.Args, common.Argument{Name: arg.Name, Value: arg.Default})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveInputObject(s *Schema, values common.InputValueList) error {
|
||||
for _, v := range values {
|
||||
t, err := common.ResolveType(v.Type, s.Resolve)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Type = t
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseSchema(s *Schema, l *common.Lexer) {
|
||||
for l.Peek() != scanner.EOF {
|
||||
desc := l.DescComment()
|
||||
switch x := l.ConsumeIdent(); x {
|
||||
case "schema":
|
||||
l.ConsumeToken('{')
|
||||
for l.Peek() != '}' {
|
||||
name := l.ConsumeIdent()
|
||||
l.ConsumeToken(':')
|
||||
typ := l.ConsumeIdent()
|
||||
s.entryPointNames[name] = typ
|
||||
}
|
||||
l.ConsumeToken('}')
|
||||
case "type":
|
||||
obj := parseObjectDecl(l)
|
||||
obj.Desc = desc
|
||||
s.Types[obj.Name] = obj
|
||||
s.objects = append(s.objects, obj)
|
||||
case "interface":
|
||||
intf := parseInterfaceDecl(l)
|
||||
intf.Desc = desc
|
||||
s.Types[intf.Name] = intf
|
||||
case "union":
|
||||
union := parseUnionDecl(l)
|
||||
union.Desc = desc
|
||||
s.Types[union.Name] = union
|
||||
s.unions = append(s.unions, union)
|
||||
case "enum":
|
||||
enum := parseEnumDecl(l)
|
||||
enum.Desc = desc
|
||||
s.Types[enum.Name] = enum
|
||||
s.enums = append(s.enums, enum)
|
||||
case "input":
|
||||
input := parseInputDecl(l)
|
||||
input.Desc = desc
|
||||
s.Types[input.Name] = input
|
||||
case "scalar":
|
||||
name := l.ConsumeIdent()
|
||||
s.Types[name] = &Scalar{Name: name, Desc: desc}
|
||||
case "directive":
|
||||
directive := parseDirectiveDecl(l)
|
||||
directive.Desc = desc
|
||||
s.Directives[directive.Name] = directive
|
||||
default:
|
||||
l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union", "input", "scalar" or "directive"`, x))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseObjectDecl(l *common.Lexer) *Object {
|
||||
o := &Object{}
|
||||
o.Name = l.ConsumeIdent()
|
||||
if l.Peek() == scanner.Ident {
|
||||
l.ConsumeKeyword("implements")
|
||||
for {
|
||||
o.interfaceNames = append(o.interfaceNames, l.ConsumeIdent())
|
||||
if l.Peek() == '{' {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
l.ConsumeToken('{')
|
||||
o.Fields = parseFields(l)
|
||||
l.ConsumeToken('}')
|
||||
return o
|
||||
}
|
||||
|
||||
func parseInterfaceDecl(l *common.Lexer) *Interface {
|
||||
i := &Interface{}
|
||||
i.Name = l.ConsumeIdent()
|
||||
l.ConsumeToken('{')
|
||||
i.Fields = parseFields(l)
|
||||
l.ConsumeToken('}')
|
||||
return i
|
||||
}
|
||||
|
||||
func parseUnionDecl(l *common.Lexer) *Union {
|
||||
union := &Union{}
|
||||
union.Name = l.ConsumeIdent()
|
||||
l.ConsumeToken('=')
|
||||
union.typeNames = []string{l.ConsumeIdent()}
|
||||
for l.Peek() == '|' {
|
||||
l.ConsumeToken('|')
|
||||
union.typeNames = append(union.typeNames, l.ConsumeIdent())
|
||||
}
|
||||
return union
|
||||
}
|
||||
|
||||
func parseInputDecl(l *common.Lexer) *InputObject {
|
||||
i := &InputObject{}
|
||||
i.Name = l.ConsumeIdent()
|
||||
l.ConsumeToken('{')
|
||||
for l.Peek() != '}' {
|
||||
i.Values = append(i.Values, common.ParseInputValue(l))
|
||||
}
|
||||
l.ConsumeToken('}')
|
||||
return i
|
||||
}
|
||||
|
||||
func parseEnumDecl(l *common.Lexer) *Enum {
|
||||
enum := &Enum{}
|
||||
enum.Name = l.ConsumeIdent()
|
||||
l.ConsumeToken('{')
|
||||
for l.Peek() != '}' {
|
||||
v := &EnumValue{}
|
||||
v.Desc = l.DescComment()
|
||||
v.Name = l.ConsumeIdent()
|
||||
v.Directives = common.ParseDirectives(l)
|
||||
enum.Values = append(enum.Values, v)
|
||||
}
|
||||
l.ConsumeToken('}')
|
||||
return enum
|
||||
}
|
||||
|
||||
func parseDirectiveDecl(l *common.Lexer) *DirectiveDecl {
|
||||
d := &DirectiveDecl{}
|
||||
l.ConsumeToken('@')
|
||||
d.Name = l.ConsumeIdent()
|
||||
if l.Peek() == '(' {
|
||||
l.ConsumeToken('(')
|
||||
for l.Peek() != ')' {
|
||||
v := common.ParseInputValue(l)
|
||||
d.Args = append(d.Args, v)
|
||||
}
|
||||
l.ConsumeToken(')')
|
||||
}
|
||||
l.ConsumeKeyword("on")
|
||||
for {
|
||||
loc := l.ConsumeIdent()
|
||||
d.Locs = append(d.Locs, loc)
|
||||
if l.Peek() != '|' {
|
||||
break
|
||||
}
|
||||
l.ConsumeToken('|')
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func parseFields(l *common.Lexer) FieldList {
|
||||
var fields FieldList
|
||||
for l.Peek() != '}' {
|
||||
f := &Field{}
|
||||
f.Desc = l.DescComment()
|
||||
f.Name = l.ConsumeIdent()
|
||||
if l.Peek() == '(' {
|
||||
l.ConsumeToken('(')
|
||||
for l.Peek() != ')' {
|
||||
f.Args = append(f.Args, common.ParseInputValue(l))
|
||||
}
|
||||
l.ConsumeToken(')')
|
||||
}
|
||||
l.ConsumeToken(':')
|
||||
f.Type = common.ParseType(l)
|
||||
f.Directives = common.ParseDirectives(l)
|
||||
fields = append(fields, f)
|
||||
}
|
||||
return fields
|
||||
}
|
75
vendor/github.com/neelance/graphql-go/internal/tests/all_test.go
generated
vendored
Normal file
75
vendor/github.com/neelance/graphql-go/internal/tests/all_test.go
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/neelance/graphql-go/errors"
|
||||
"github.com/neelance/graphql-go/internal/query"
|
||||
"github.com/neelance/graphql-go/internal/schema"
|
||||
"github.com/neelance/graphql-go/internal/validation"
|
||||
)
|
||||
|
||||
type Test struct {
|
||||
Name string
|
||||
Rule string
|
||||
Schema int
|
||||
Query string
|
||||
Errors []*errors.QueryError
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
f, err := os.Open("testdata/tests.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var testData struct {
|
||||
Schemas []string
|
||||
Tests []*Test
|
||||
}
|
||||
if err := json.NewDecoder(f).Decode(&testData); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
schemas := make([]*schema.Schema, len(testData.Schemas))
|
||||
for i, schemaStr := range testData.Schemas {
|
||||
schemas[i] = schema.New()
|
||||
if err := schemas[i].Parse(schemaStr); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range testData.Tests {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
d, err := query.Parse(test.Query)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
errs := validation.Validate(schemas[test.Schema], d)
|
||||
got := []*errors.QueryError{}
|
||||
for _, err := range errs {
|
||||
if err.Rule == test.Rule {
|
||||
err.Rule = ""
|
||||
got = append(got, err)
|
||||
}
|
||||
}
|
||||
sortLocations(test.Errors)
|
||||
sortLocations(got)
|
||||
if !reflect.DeepEqual(test.Errors, got) {
|
||||
t.Errorf("wrong errors\nexpected: %v\ngot: %v", test.Errors, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func sortLocations(errs []*errors.QueryError) {
|
||||
for _, err := range errs {
|
||||
locs := err.Locations
|
||||
sort.Slice(locs, func(i, j int) bool { return locs[i].Before(locs[j]) })
|
||||
}
|
||||
}
|
1
vendor/github.com/neelance/graphql-go/internal/tests/empty.go
generated
vendored
Normal file
1
vendor/github.com/neelance/graphql-go/internal/tests/empty.go
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
package tests
|
33
vendor/github.com/neelance/graphql-go/internal/tests/testdata/LICENSE
generated
vendored
Normal file
33
vendor/github.com/neelance/graphql-go/internal/tests/testdata/LICENSE
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
The files in this testdata directory are derived from the graphql-js project:
|
||||
https://github.com/graphql/graphql-js
|
||||
|
||||
BSD License
|
||||
|
||||
For GraphQL software
|
||||
|
||||
Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
110
vendor/github.com/neelance/graphql-go/internal/tests/testdata/export.js
generated
vendored
Normal file
110
vendor/github.com/neelance/graphql-go/internal/tests/testdata/export.js
generated
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
import fs from 'fs';
|
||||
import Module from 'module';
|
||||
import { testSchema } from './src/validation/__tests__/harness';
|
||||
import { printSchema } from './src/utilities';
|
||||
|
||||
let schemas = [];
|
||||
function registerSchema(schema) {
|
||||
for (let i = 0; i < schemas.length; i++) {
|
||||
if (schemas[i] == schema) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
schemas.push(schema);
|
||||
return schemas.length - 1;
|
||||
}
|
||||
|
||||
const harness = {
|
||||
expectPassesRule(rule, queryString) {
|
||||
harness.expectPassesRuleWithSchema(testSchema, rule, queryString);
|
||||
},
|
||||
expectPassesRuleWithSchema(schema, rule, queryString, errors) {
|
||||
tests.push({
|
||||
name: names.join('/'),
|
||||
rule: rule.name,
|
||||
schema: registerSchema(schema),
|
||||
query: queryString,
|
||||
errors: [],
|
||||
});
|
||||
},
|
||||
expectFailsRule(rule, queryString, errors) {
|
||||
harness.expectFailsRuleWithSchema(testSchema, rule, queryString, errors);
|
||||
},
|
||||
expectFailsRuleWithSchema(schema, rule, queryString, errors) {
|
||||
tests.push({
|
||||
name: names.join('/'),
|
||||
rule: rule.name,
|
||||
schema: registerSchema(schema),
|
||||
query: queryString,
|
||||
errors: errors,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let tests = [];
|
||||
let names = []
|
||||
const fakeModules = {
|
||||
'mocha': {
|
||||
describe(name, f) {
|
||||
switch (name) {
|
||||
case 'within schema language':
|
||||
return;
|
||||
}
|
||||
names.push(name);
|
||||
f();
|
||||
names.pop();
|
||||
},
|
||||
it(name, f) {
|
||||
switch (name) {
|
||||
case 'ignores type definitions':
|
||||
case 'reports correctly when a non-exclusive follows an exclusive':
|
||||
case 'disallows differing subfields':
|
||||
return;
|
||||
}
|
||||
names.push(name);
|
||||
f();
|
||||
names.pop();
|
||||
},
|
||||
},
|
||||
'./harness': harness,
|
||||
};
|
||||
|
||||
const originalLoader = Module._load;
|
||||
Module._load = function(request, parent, isMain) {
|
||||
return fakeModules[request] || originalLoader(request, parent, isMain);
|
||||
};
|
||||
|
||||
require('./src/validation/__tests__/ArgumentsOfCorrectType-test');
|
||||
require('./src/validation/__tests__/DefaultValuesOfCorrectType-test');
|
||||
require('./src/validation/__tests__/FieldsOnCorrectType-test');
|
||||
require('./src/validation/__tests__/FragmentsOnCompositeTypes-test');
|
||||
require('./src/validation/__tests__/KnownArgumentNames-test');
|
||||
require('./src/validation/__tests__/KnownDirectives-test');
|
||||
require('./src/validation/__tests__/KnownFragmentNames-test');
|
||||
require('./src/validation/__tests__/KnownTypeNames-test');
|
||||
require('./src/validation/__tests__/LoneAnonymousOperation-test');
|
||||
require('./src/validation/__tests__/NoFragmentCycles-test');
|
||||
require('./src/validation/__tests__/NoUndefinedVariables-test');
|
||||
require('./src/validation/__tests__/NoUnusedFragments-test');
|
||||
require('./src/validation/__tests__/NoUnusedVariables-test');
|
||||
require('./src/validation/__tests__/OverlappingFieldsCanBeMerged-test');
|
||||
require('./src/validation/__tests__/PossibleFragmentSpreads-test');
|
||||
require('./src/validation/__tests__/ProvidedNonNullArguments-test');
|
||||
require('./src/validation/__tests__/ScalarLeafs-test');
|
||||
require('./src/validation/__tests__/UniqueArgumentNames-test');
|
||||
require('./src/validation/__tests__/UniqueDirectivesPerLocation-test');
|
||||
require('./src/validation/__tests__/UniqueFragmentNames-test');
|
||||
require('./src/validation/__tests__/UniqueInputFieldNames-test');
|
||||
require('./src/validation/__tests__/UniqueOperationNames-test');
|
||||
require('./src/validation/__tests__/UniqueVariableNames-test');
|
||||
require('./src/validation/__tests__/VariablesAreInputTypes-test');
|
||||
require('./src/validation/__tests__/VariablesInAllowedPosition-test');
|
||||
|
||||
let output = JSON.stringify({
|
||||
schemas: schemas.map(s => printSchema(s)),
|
||||
tests: tests,
|
||||
}, null, 2)
|
||||
output = output.replace(' Did you mean to use an inline fragment on \\"Dog\\" or \\"Cat\\"?', '');
|
||||
output = output.replace(' Did you mean to use an inline fragment on \\"Being\\", \\"Pet\\", \\"Canine\\", \\"Dog\\", or \\"Cat\\"?', '');
|
||||
output = output.replace(' Did you mean \\"Pet\\"?', '');
|
||||
fs.writeFileSync("tests.json", output);
|
4
vendor/github.com/neelance/graphql-go/internal/tests/testdata/gen.go
generated
vendored
Normal file
4
vendor/github.com/neelance/graphql-go/internal/tests/testdata/gen.go
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
package testdata
|
||||
|
||||
//go:generate cp export.js graphql-js/export.js
|
||||
//go:generate babel-node graphql-js/export.js
|
4948
vendor/github.com/neelance/graphql-go/internal/tests/testdata/tests.json
generated
vendored
Normal file
4948
vendor/github.com/neelance/graphql-go/internal/tests/testdata/tests.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
71
vendor/github.com/neelance/graphql-go/internal/validation/suggestion.go
generated
vendored
Normal file
71
vendor/github.com/neelance/graphql-go/internal/validation/suggestion.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func makeSuggestion(prefix string, options []string, input string) string {
|
||||
var selected []string
|
||||
distances := make(map[string]int)
|
||||
for _, opt := range options {
|
||||
distance := levenshteinDistance(input, opt)
|
||||
threshold := max(len(input)/2, max(len(opt)/2, 1))
|
||||
if distance < threshold {
|
||||
selected = append(selected, opt)
|
||||
distances[opt] = distance
|
||||
}
|
||||
}
|
||||
|
||||
if len(selected) == 0 {
|
||||
return ""
|
||||
}
|
||||
sort.Slice(selected, func(i, j int) bool {
|
||||
return distances[selected[i]] < distances[selected[j]]
|
||||
})
|
||||
|
||||
parts := make([]string, len(selected))
|
||||
for i, opt := range selected {
|
||||
parts[i] = strconv.Quote(opt)
|
||||
}
|
||||
if len(parts) > 1 {
|
||||
parts[len(parts)-1] = "or " + parts[len(parts)-1]
|
||||
}
|
||||
return fmt.Sprintf(" %s %s?", prefix, strings.Join(parts, ", "))
|
||||
}
|
||||
|
||||
func levenshteinDistance(s1, s2 string) int {
|
||||
column := make([]int, len(s1)+1)
|
||||
for y := range s1 {
|
||||
column[y+1] = y + 1
|
||||
}
|
||||
for x, rx := range s2 {
|
||||
column[0] = x + 1
|
||||
lastdiag := x
|
||||
for y, ry := range s1 {
|
||||
olddiag := column[y+1]
|
||||
if rx != ry {
|
||||
lastdiag++
|
||||
}
|
||||
column[y+1] = min(column[y+1]+1, min(column[y]+1, lastdiag))
|
||||
lastdiag = olddiag
|
||||
}
|
||||
}
|
||||
return column[len(s1)]
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
860
vendor/github.com/neelance/graphql-go/internal/validation/validation.go
generated
vendored
Normal file
860
vendor/github.com/neelance/graphql-go/internal/validation/validation.go
generated
vendored
Normal file
@ -0,0 +1,860 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/neelance/graphql-go/errors"
|
||||
"github.com/neelance/graphql-go/internal/common"
|
||||
"github.com/neelance/graphql-go/internal/query"
|
||||
"github.com/neelance/graphql-go/internal/schema"
|
||||
)
|
||||
|
||||
type varSet map[*common.InputValue]struct{}
|
||||
|
||||
type selectionPair struct{ a, b query.Selection }
|
||||
|
||||
type fieldInfo struct {
|
||||
sf *schema.Field
|
||||
parent schema.NamedType
|
||||
}
|
||||
|
||||
type context struct {
|
||||
schema *schema.Schema
|
||||
doc *query.Document
|
||||
errs []*errors.QueryError
|
||||
opErrs map[*query.Operation][]*errors.QueryError
|
||||
usedVars map[*query.Operation]varSet
|
||||
fieldMap map[*query.Field]fieldInfo
|
||||
overlapValidated map[selectionPair]struct{}
|
||||
}
|
||||
|
||||
func (c *context) addErr(loc errors.Location, rule string, format string, a ...interface{}) {
|
||||
c.addErrMultiLoc([]errors.Location{loc}, rule, format, a...)
|
||||
}
|
||||
|
||||
func (c *context) addErrMultiLoc(locs []errors.Location, rule string, format string, a ...interface{}) {
|
||||
c.errs = append(c.errs, &errors.QueryError{
|
||||
Message: fmt.Sprintf(format, a...),
|
||||
Locations: locs,
|
||||
Rule: rule,
|
||||
})
|
||||
}
|
||||
|
||||
type opContext struct {
|
||||
*context
|
||||
ops []*query.Operation
|
||||
}
|
||||
|
||||
func Validate(s *schema.Schema, doc *query.Document) []*errors.QueryError {
|
||||
c := &context{
|
||||
schema: s,
|
||||
doc: doc,
|
||||
opErrs: make(map[*query.Operation][]*errors.QueryError),
|
||||
usedVars: make(map[*query.Operation]varSet),
|
||||
fieldMap: make(map[*query.Field]fieldInfo),
|
||||
overlapValidated: make(map[selectionPair]struct{}),
|
||||
}
|
||||
|
||||
opNames := make(nameSet)
|
||||
fragUsedBy := make(map[*query.FragmentDecl][]*query.Operation)
|
||||
for _, op := range doc.Operations {
|
||||
c.usedVars[op] = make(varSet)
|
||||
opc := &opContext{c, []*query.Operation{op}}
|
||||
|
||||
if op.Name.Name == "" && len(doc.Operations) != 1 {
|
||||
c.addErr(op.Loc, "LoneAnonymousOperation", "This anonymous operation must be the only defined operation.")
|
||||
}
|
||||
if op.Name.Name != "" {
|
||||
validateName(c, opNames, op.Name, "UniqueOperationNames", "operation")
|
||||
}
|
||||
|
||||
validateDirectives(opc, string(op.Type), op.Directives)
|
||||
|
||||
varNames := make(nameSet)
|
||||
for _, v := range op.Vars {
|
||||
validateName(c, varNames, v.Name, "UniqueVariableNames", "variable")
|
||||
|
||||
t := resolveType(c, v.Type)
|
||||
if !canBeInput(t) {
|
||||
c.addErr(v.TypeLoc, "VariablesAreInputTypes", "Variable %q cannot be non-input type %q.", "$"+v.Name.Name, t)
|
||||
}
|
||||
|
||||
if v.Default != nil {
|
||||
validateLiteral(opc, v.Default)
|
||||
|
||||
if t != nil {
|
||||
if nn, ok := t.(*common.NonNull); ok {
|
||||
c.addErr(v.Default.Location(), "DefaultValuesOfCorrectType", "Variable %q of type %q is required and will not use the default value. Perhaps you meant to use type %q.", "$"+v.Name.Name, t, nn.OfType)
|
||||
}
|
||||
|
||||
if ok, reason := validateValueType(opc, v.Default, t); !ok {
|
||||
c.addErr(v.Default.Location(), "DefaultValuesOfCorrectType", "Variable %q of type %q has invalid default value %s.\n%s", "$"+v.Name.Name, t, v.Default, reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var entryPoint schema.NamedType
|
||||
switch op.Type {
|
||||
case query.Query:
|
||||
entryPoint = s.EntryPoints["query"]
|
||||
case query.Mutation:
|
||||
entryPoint = s.EntryPoints["mutation"]
|
||||
case query.Subscription:
|
||||
entryPoint = s.EntryPoints["subscription"]
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
validateSelectionSet(opc, op.Selections, entryPoint)
|
||||
|
||||
fragUsed := make(map[*query.FragmentDecl]struct{})
|
||||
markUsedFragments(c, op.Selections, fragUsed)
|
||||
for frag := range fragUsed {
|
||||
fragUsedBy[frag] = append(fragUsedBy[frag], op)
|
||||
}
|
||||
}
|
||||
|
||||
fragNames := make(nameSet)
|
||||
fragVisited := make(map[*query.FragmentDecl]struct{})
|
||||
for _, frag := range doc.Fragments {
|
||||
opc := &opContext{c, fragUsedBy[frag]}
|
||||
|
||||
validateName(c, fragNames, frag.Name, "UniqueFragmentNames", "fragment")
|
||||
validateDirectives(opc, "FRAGMENT_DEFINITION", frag.Directives)
|
||||
|
||||
t := unwrapType(resolveType(c, &frag.On))
|
||||
// continue even if t is nil
|
||||
if t != nil && !canBeFragment(t) {
|
||||
c.addErr(frag.On.Loc, "FragmentsOnCompositeTypes", "Fragment %q cannot condition on non composite type %q.", frag.Name.Name, t)
|
||||
continue
|
||||
}
|
||||
|
||||
validateSelectionSet(opc, frag.Selections, t)
|
||||
|
||||
if _, ok := fragVisited[frag]; !ok {
|
||||
detectFragmentCycle(c, frag.Selections, fragVisited, nil, map[string]int{frag.Name.Name: 0})
|
||||
}
|
||||
}
|
||||
|
||||
for _, frag := range doc.Fragments {
|
||||
if len(fragUsedBy[frag]) == 0 {
|
||||
c.addErr(frag.Loc, "NoUnusedFragments", "Fragment %q is never used.", frag.Name.Name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, op := range doc.Operations {
|
||||
c.errs = append(c.errs, c.opErrs[op]...)
|
||||
|
||||
opUsedVars := c.usedVars[op]
|
||||
for _, v := range op.Vars {
|
||||
if _, ok := opUsedVars[v]; !ok {
|
||||
opSuffix := ""
|
||||
if op.Name.Name != "" {
|
||||
opSuffix = fmt.Sprintf(" in operation %q", op.Name.Name)
|
||||
}
|
||||
c.addErr(v.Loc, "NoUnusedVariables", "Variable %q is never used%s.", "$"+v.Name.Name, opSuffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c.errs
|
||||
}
|
||||
|
||||
func validateSelectionSet(c *opContext, sels []query.Selection, t schema.NamedType) {
|
||||
for _, sel := range sels {
|
||||
validateSelection(c, sel, t)
|
||||
}
|
||||
|
||||
for i, a := range sels {
|
||||
for _, b := range sels[i+1:] {
|
||||
c.validateOverlap(a, b, nil, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateSelection(c *opContext, sel query.Selection, t schema.NamedType) {
|
||||
switch sel := sel.(type) {
|
||||
case *query.Field:
|
||||
validateDirectives(c, "FIELD", sel.Directives)
|
||||
|
||||
fieldName := sel.Name.Name
|
||||
var f *schema.Field
|
||||
switch fieldName {
|
||||
case "__typename":
|
||||
f = &schema.Field{
|
||||
Name: "__typename",
|
||||
Type: c.schema.Types["String"],
|
||||
}
|
||||
case "__schema":
|
||||
f = &schema.Field{
|
||||
Name: "__schema",
|
||||
Type: c.schema.Types["__Schema"],
|
||||
}
|
||||
case "__type":
|
||||
f = &schema.Field{
|
||||
Name: "__type",
|
||||
Args: common.InputValueList{
|
||||
&common.InputValue{
|
||||
Name: common.Ident{Name: "name"},
|
||||
Type: &common.NonNull{OfType: c.schema.Types["String"]},
|
||||
},
|
||||
},
|
||||
Type: c.schema.Types["__Type"],
|
||||
}
|
||||
default:
|
||||
f = fields(t).Get(fieldName)
|
||||
if f == nil && t != nil {
|
||||
suggestion := makeSuggestion("Did you mean", fields(t).Names(), fieldName)
|
||||
c.addErr(sel.Alias.Loc, "FieldsOnCorrectType", "Cannot query field %q on type %q.%s", fieldName, t, suggestion)
|
||||
}
|
||||
}
|
||||
c.fieldMap[sel] = fieldInfo{sf: f, parent: t}
|
||||
|
||||
validateArgumentLiterals(c, sel.Arguments)
|
||||
if f != nil {
|
||||
validateArgumentTypes(c, sel.Arguments, f.Args, sel.Alias.Loc,
|
||||
func() string { return fmt.Sprintf("field %q of type %q", fieldName, t) },
|
||||
func() string { return fmt.Sprintf("Field %q", fieldName) },
|
||||
)
|
||||
}
|
||||
|
||||
var ft common.Type
|
||||
if f != nil {
|
||||
ft = f.Type
|
||||
sf := hasSubfields(ft)
|
||||
if sf && sel.Selections == nil {
|
||||
c.addErr(sel.Alias.Loc, "ScalarLeafs", "Field %q of type %q must have a selection of subfields. Did you mean \"%s { ... }\"?", fieldName, ft, fieldName)
|
||||
}
|
||||
if !sf && sel.Selections != nil {
|
||||
c.addErr(sel.SelectionSetLoc, "ScalarLeafs", "Field %q must not have a selection since type %q has no subfields.", fieldName, ft)
|
||||
}
|
||||
}
|
||||
if sel.Selections != nil {
|
||||
validateSelectionSet(c, sel.Selections, unwrapType(ft))
|
||||
}
|
||||
|
||||
case *query.InlineFragment:
|
||||
validateDirectives(c, "INLINE_FRAGMENT", sel.Directives)
|
||||
if sel.On.Name != "" {
|
||||
fragTyp := unwrapType(resolveType(c.context, &sel.On))
|
||||
if fragTyp != nil && !compatible(t, fragTyp) {
|
||||
c.addErr(sel.Loc, "PossibleFragmentSpreads", "Fragment cannot be spread here as objects of type %q can never be of type %q.", t, fragTyp)
|
||||
}
|
||||
t = fragTyp
|
||||
// continue even if t is nil
|
||||
}
|
||||
if t != nil && !canBeFragment(t) {
|
||||
c.addErr(sel.On.Loc, "FragmentsOnCompositeTypes", "Fragment cannot condition on non composite type %q.", t)
|
||||
return
|
||||
}
|
||||
validateSelectionSet(c, sel.Selections, unwrapType(t))
|
||||
|
||||
case *query.FragmentSpread:
|
||||
validateDirectives(c, "FRAGMENT_SPREAD", sel.Directives)
|
||||
frag := c.doc.Fragments.Get(sel.Name.Name)
|
||||
if frag == nil {
|
||||
c.addErr(sel.Name.Loc, "KnownFragmentNames", "Unknown fragment %q.", sel.Name.Name)
|
||||
return
|
||||
}
|
||||
fragTyp := c.schema.Types[frag.On.Name]
|
||||
if !compatible(t, fragTyp) {
|
||||
c.addErr(sel.Loc, "PossibleFragmentSpreads", "Fragment %q cannot be spread here as objects of type %q can never be of type %q.", frag.Name.Name, t, fragTyp)
|
||||
}
|
||||
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func compatible(a, b common.Type) bool {
|
||||
for _, pta := range possibleTypes(a) {
|
||||
for _, ptb := range possibleTypes(b) {
|
||||
if pta == ptb {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func possibleTypes(t common.Type) []*schema.Object {
|
||||
switch t := t.(type) {
|
||||
case *schema.Object:
|
||||
return []*schema.Object{t}
|
||||
case *schema.Interface:
|
||||
return t.PossibleTypes
|
||||
case *schema.Union:
|
||||
return t.PossibleTypes
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func markUsedFragments(c *context, sels []query.Selection, fragUsed map[*query.FragmentDecl]struct{}) {
|
||||
for _, sel := range sels {
|
||||
switch sel := sel.(type) {
|
||||
case *query.Field:
|
||||
if sel.Selections != nil {
|
||||
markUsedFragments(c, sel.Selections, fragUsed)
|
||||
}
|
||||
|
||||
case *query.InlineFragment:
|
||||
markUsedFragments(c, sel.Selections, fragUsed)
|
||||
|
||||
case *query.FragmentSpread:
|
||||
frag := c.doc.Fragments.Get(sel.Name.Name)
|
||||
if frag == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := fragUsed[frag]; ok {
|
||||
return
|
||||
}
|
||||
fragUsed[frag] = struct{}{}
|
||||
markUsedFragments(c, frag.Selections, fragUsed)
|
||||
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func detectFragmentCycle(c *context, sels []query.Selection, fragVisited map[*query.FragmentDecl]struct{}, spreadPath []*query.FragmentSpread, spreadPathIndex map[string]int) {
|
||||
for _, sel := range sels {
|
||||
detectFragmentCycleSel(c, sel, fragVisited, spreadPath, spreadPathIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func detectFragmentCycleSel(c *context, sel query.Selection, fragVisited map[*query.FragmentDecl]struct{}, spreadPath []*query.FragmentSpread, spreadPathIndex map[string]int) {
|
||||
switch sel := sel.(type) {
|
||||
case *query.Field:
|
||||
if sel.Selections != nil {
|
||||
detectFragmentCycle(c, sel.Selections, fragVisited, spreadPath, spreadPathIndex)
|
||||
}
|
||||
|
||||
case *query.InlineFragment:
|
||||
detectFragmentCycle(c, sel.Selections, fragVisited, spreadPath, spreadPathIndex)
|
||||
|
||||
case *query.FragmentSpread:
|
||||
frag := c.doc.Fragments.Get(sel.Name.Name)
|
||||
if frag == nil {
|
||||
return
|
||||
}
|
||||
|
||||
spreadPath = append(spreadPath, sel)
|
||||
if i, ok := spreadPathIndex[frag.Name.Name]; ok {
|
||||
cyclePath := spreadPath[i:]
|
||||
via := ""
|
||||
if len(cyclePath) > 1 {
|
||||
names := make([]string, len(cyclePath)-1)
|
||||
for i, frag := range cyclePath[:len(cyclePath)-1] {
|
||||
names[i] = frag.Name.Name
|
||||
}
|
||||
via = " via " + strings.Join(names, ", ")
|
||||
}
|
||||
|
||||
locs := make([]errors.Location, len(cyclePath))
|
||||
for i, frag := range cyclePath {
|
||||
locs[i] = frag.Loc
|
||||
}
|
||||
c.addErrMultiLoc(locs, "NoFragmentCycles", "Cannot spread fragment %q within itself%s.", frag.Name.Name, via)
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := fragVisited[frag]; ok {
|
||||
return
|
||||
}
|
||||
fragVisited[frag] = struct{}{}
|
||||
|
||||
spreadPathIndex[frag.Name.Name] = len(spreadPath)
|
||||
detectFragmentCycle(c, frag.Selections, fragVisited, spreadPath, spreadPathIndex)
|
||||
delete(spreadPathIndex, frag.Name.Name)
|
||||
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) validateOverlap(a, b query.Selection, reasons *[]string, locs *[]errors.Location) {
|
||||
if a == b {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := c.overlapValidated[selectionPair{a, b}]; ok {
|
||||
return
|
||||
}
|
||||
c.overlapValidated[selectionPair{a, b}] = struct{}{}
|
||||
c.overlapValidated[selectionPair{b, a}] = struct{}{}
|
||||
|
||||
switch a := a.(type) {
|
||||
case *query.Field:
|
||||
switch b := b.(type) {
|
||||
case *query.Field:
|
||||
if b.Alias.Loc.Before(a.Alias.Loc) {
|
||||
a, b = b, a
|
||||
}
|
||||
if reasons2, locs2 := c.validateFieldOverlap(a, b); len(reasons2) != 0 {
|
||||
locs2 = append(locs2, a.Alias.Loc, b.Alias.Loc)
|
||||
if reasons == nil {
|
||||
c.addErrMultiLoc(locs2, "OverlappingFieldsCanBeMerged", "Fields %q conflict because %s. Use different aliases on the fields to fetch both if this was intentional.", a.Alias.Name, strings.Join(reasons2, " and "))
|
||||
return
|
||||
}
|
||||
for _, r := range reasons2 {
|
||||
*reasons = append(*reasons, fmt.Sprintf("subfields %q conflict because %s", a.Alias.Name, r))
|
||||
}
|
||||
*locs = append(*locs, locs2...)
|
||||
}
|
||||
|
||||
case *query.InlineFragment:
|
||||
for _, sel := range b.Selections {
|
||||
c.validateOverlap(a, sel, reasons, locs)
|
||||
}
|
||||
|
||||
case *query.FragmentSpread:
|
||||
if frag := c.doc.Fragments.Get(b.Name.Name); frag != nil {
|
||||
for _, sel := range frag.Selections {
|
||||
c.validateOverlap(a, sel, reasons, locs)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
case *query.InlineFragment:
|
||||
for _, sel := range a.Selections {
|
||||
c.validateOverlap(sel, b, reasons, locs)
|
||||
}
|
||||
|
||||
case *query.FragmentSpread:
|
||||
if frag := c.doc.Fragments.Get(a.Name.Name); frag != nil {
|
||||
for _, sel := range frag.Selections {
|
||||
c.validateOverlap(sel, b, reasons, locs)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) validateFieldOverlap(a, b *query.Field) ([]string, []errors.Location) {
|
||||
if a.Alias.Name != b.Alias.Name {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if asf := c.fieldMap[a].sf; asf != nil {
|
||||
if bsf := c.fieldMap[b].sf; bsf != nil {
|
||||
if !typesCompatible(asf.Type, bsf.Type) {
|
||||
return []string{fmt.Sprintf("they return conflicting types %s and %s", asf.Type, bsf.Type)}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
at := c.fieldMap[a].parent
|
||||
bt := c.fieldMap[b].parent
|
||||
if at == nil || bt == nil || at == bt {
|
||||
if a.Name.Name != b.Name.Name {
|
||||
return []string{fmt.Sprintf("%s and %s are different fields", a.Name.Name, b.Name.Name)}, nil
|
||||
}
|
||||
|
||||
if argumentsConflict(a.Arguments, b.Arguments) {
|
||||
return []string{"they have differing arguments"}, nil
|
||||
}
|
||||
}
|
||||
|
||||
var reasons []string
|
||||
var locs []errors.Location
|
||||
for _, a2 := range a.Selections {
|
||||
for _, b2 := range b.Selections {
|
||||
c.validateOverlap(a2, b2, &reasons, &locs)
|
||||
}
|
||||
}
|
||||
return reasons, locs
|
||||
}
|
||||
|
||||
func argumentsConflict(a, b common.ArgumentList) bool {
|
||||
if len(a) != len(b) {
|
||||
return true
|
||||
}
|
||||
for _, argA := range a {
|
||||
valB, ok := b.Get(argA.Name.Name)
|
||||
if !ok || !reflect.DeepEqual(argA.Value.Value(nil), valB.Value(nil)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func fields(t common.Type) schema.FieldList {
|
||||
switch t := t.(type) {
|
||||
case *schema.Object:
|
||||
return t.Fields
|
||||
case *schema.Interface:
|
||||
return t.Fields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func unwrapType(t common.Type) schema.NamedType {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
for {
|
||||
switch t2 := t.(type) {
|
||||
case schema.NamedType:
|
||||
return t2
|
||||
case *common.List:
|
||||
t = t2.OfType
|
||||
case *common.NonNull:
|
||||
t = t2.OfType
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveType(c *context, t common.Type) common.Type {
|
||||
t2, err := common.ResolveType(t, c.schema.Resolve)
|
||||
if err != nil {
|
||||
c.errs = append(c.errs, err)
|
||||
}
|
||||
return t2
|
||||
}
|
||||
|
||||
func validateDirectives(c *opContext, loc string, directives common.DirectiveList) {
|
||||
directiveNames := make(nameSet)
|
||||
for _, d := range directives {
|
||||
dirName := d.Name.Name
|
||||
validateNameCustomMsg(c.context, directiveNames, d.Name, "UniqueDirectivesPerLocation", func() string {
|
||||
return fmt.Sprintf("The directive %q can only be used once at this location.", dirName)
|
||||
})
|
||||
|
||||
validateArgumentLiterals(c, d.Args)
|
||||
|
||||
dd, ok := c.schema.Directives[dirName]
|
||||
if !ok {
|
||||
c.addErr(d.Name.Loc, "KnownDirectives", "Unknown directive %q.", dirName)
|
||||
continue
|
||||
}
|
||||
|
||||
locOK := false
|
||||
for _, allowedLoc := range dd.Locs {
|
||||
if loc == allowedLoc {
|
||||
locOK = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !locOK {
|
||||
c.addErr(d.Name.Loc, "KnownDirectives", "Directive %q may not be used on %s.", dirName, loc)
|
||||
}
|
||||
|
||||
validateArgumentTypes(c, d.Args, dd.Args, d.Name.Loc,
|
||||
func() string { return fmt.Sprintf("directive %q", "@"+dirName) },
|
||||
func() string { return fmt.Sprintf("Directive %q", "@"+dirName) },
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type nameSet map[string]errors.Location
|
||||
|
||||
func validateName(c *context, set nameSet, name common.Ident, rule string, kind string) {
|
||||
validateNameCustomMsg(c, set, name, rule, func() string {
|
||||
return fmt.Sprintf("There can be only one %s named %q.", kind, name.Name)
|
||||
})
|
||||
}
|
||||
|
||||
func validateNameCustomMsg(c *context, set nameSet, name common.Ident, rule string, msg func() string) {
|
||||
if loc, ok := set[name.Name]; ok {
|
||||
c.addErrMultiLoc([]errors.Location{loc, name.Loc}, rule, msg())
|
||||
return
|
||||
}
|
||||
set[name.Name] = name.Loc
|
||||
return
|
||||
}
|
||||
|
||||
func validateArgumentTypes(c *opContext, args common.ArgumentList, argDecls common.InputValueList, loc errors.Location, owner1, owner2 func() string) {
|
||||
for _, selArg := range args {
|
||||
arg := argDecls.Get(selArg.Name.Name)
|
||||
if arg == nil {
|
||||
c.addErr(selArg.Name.Loc, "KnownArgumentNames", "Unknown argument %q on %s.", selArg.Name.Name, owner1())
|
||||
continue
|
||||
}
|
||||
value := selArg.Value
|
||||
if ok, reason := validateValueType(c, value, arg.Type); !ok {
|
||||
c.addErr(value.Location(), "ArgumentsOfCorrectType", "Argument %q has invalid value %s.\n%s", arg.Name.Name, value, reason)
|
||||
}
|
||||
}
|
||||
for _, decl := range argDecls {
|
||||
if _, ok := decl.Type.(*common.NonNull); ok {
|
||||
if _, ok := args.Get(decl.Name.Name); !ok {
|
||||
c.addErr(loc, "ProvidedNonNullArguments", "%s argument %q of type %q is required but not provided.", owner2(), decl.Name.Name, decl.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateArgumentLiterals(c *opContext, args common.ArgumentList) {
|
||||
argNames := make(nameSet)
|
||||
for _, arg := range args {
|
||||
validateName(c.context, argNames, arg.Name, "UniqueArgumentNames", "argument")
|
||||
validateLiteral(c, arg.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func validateLiteral(c *opContext, l common.Literal) {
|
||||
switch l := l.(type) {
|
||||
case *common.ObjectLit:
|
||||
fieldNames := make(nameSet)
|
||||
for _, f := range l.Fields {
|
||||
validateName(c.context, fieldNames, f.Name, "UniqueInputFieldNames", "input field")
|
||||
validateLiteral(c, f.Value)
|
||||
}
|
||||
case *common.ListLit:
|
||||
for _, entry := range l.Entries {
|
||||
validateLiteral(c, entry)
|
||||
}
|
||||
case *common.Variable:
|
||||
for _, op := range c.ops {
|
||||
v := op.Vars.Get(l.Name)
|
||||
if v == nil {
|
||||
byOp := ""
|
||||
if op.Name.Name != "" {
|
||||
byOp = fmt.Sprintf(" by operation %q", op.Name.Name)
|
||||
}
|
||||
c.opErrs[op] = append(c.opErrs[op], &errors.QueryError{
|
||||
Message: fmt.Sprintf("Variable %q is not defined%s.", "$"+l.Name, byOp),
|
||||
Locations: []errors.Location{l.Loc, op.Loc},
|
||||
Rule: "NoUndefinedVariables",
|
||||
})
|
||||
continue
|
||||
}
|
||||
c.usedVars[op][v] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateValueType(c *opContext, v common.Literal, t common.Type) (bool, string) {
|
||||
if v, ok := v.(*common.Variable); ok {
|
||||
for _, op := range c.ops {
|
||||
if v2 := op.Vars.Get(v.Name); v2 != nil {
|
||||
t2, err := common.ResolveType(v2.Type, c.schema.Resolve)
|
||||
if _, ok := t2.(*common.NonNull); !ok && v2.Default != nil {
|
||||
t2 = &common.NonNull{OfType: t2}
|
||||
}
|
||||
if err == nil && !typeCanBeUsedAs(t2, t) {
|
||||
c.addErrMultiLoc([]errors.Location{v2.Loc, v.Loc}, "VariablesInAllowedPosition", "Variable %q of type %q used in position expecting type %q.", "$"+v.Name, t2, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
if nn, ok := t.(*common.NonNull); ok {
|
||||
if isNull(v) {
|
||||
return false, fmt.Sprintf("Expected %q, found null.", t)
|
||||
}
|
||||
t = nn.OfType
|
||||
}
|
||||
if isNull(v) {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
switch t := t.(type) {
|
||||
case *schema.Scalar, *schema.Enum:
|
||||
if lit, ok := v.(*common.BasicLit); ok {
|
||||
if validateBasicLit(lit, t) {
|
||||
return true, ""
|
||||
}
|
||||
}
|
||||
|
||||
case *common.List:
|
||||
list, ok := v.(*common.ListLit)
|
||||
if !ok {
|
||||
return validateValueType(c, v, t.OfType) // single value instead of list
|
||||
}
|
||||
for i, entry := range list.Entries {
|
||||
if ok, reason := validateValueType(c, entry, t.OfType); !ok {
|
||||
return false, fmt.Sprintf("In element #%d: %s", i, reason)
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
|
||||
case *schema.InputObject:
|
||||
v, ok := v.(*common.ObjectLit)
|
||||
if !ok {
|
||||
return false, fmt.Sprintf("Expected %q, found not an object.", t)
|
||||
}
|
||||
for _, f := range v.Fields {
|
||||
name := f.Name.Name
|
||||
iv := t.Values.Get(name)
|
||||
if iv == nil {
|
||||
return false, fmt.Sprintf("In field %q: Unknown field.", name)
|
||||
}
|
||||
if ok, reason := validateValueType(c, f.Value, iv.Type); !ok {
|
||||
return false, fmt.Sprintf("In field %q: %s", name, reason)
|
||||
}
|
||||
}
|
||||
for _, iv := range t.Values {
|
||||
found := false
|
||||
for _, f := range v.Fields {
|
||||
if f.Name.Name == iv.Name.Name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if _, ok := iv.Type.(*common.NonNull); ok && iv.Default == nil {
|
||||
return false, fmt.Sprintf("In field %q: Expected %q, found null.", iv.Name.Name, iv.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
return false, fmt.Sprintf("Expected type %q, found %s.", t, v)
|
||||
}
|
||||
|
||||
func validateBasicLit(v *common.BasicLit, t common.Type) bool {
|
||||
switch t := t.(type) {
|
||||
case *schema.Scalar:
|
||||
switch t.Name {
|
||||
case "Int":
|
||||
if v.Type != scanner.Int {
|
||||
return false
|
||||
}
|
||||
f, err := strconv.ParseFloat(v.Text, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return f >= math.MinInt32 && f <= math.MaxInt32
|
||||
case "Float":
|
||||
return v.Type == scanner.Int || v.Type == scanner.Float
|
||||
case "String":
|
||||
return v.Type == scanner.String
|
||||
case "Boolean":
|
||||
return v.Type == scanner.Ident && (v.Text == "true" || v.Text == "false")
|
||||
case "ID":
|
||||
return v.Type == scanner.Int || v.Type == scanner.String
|
||||
default:
|
||||
//TODO: Type-check against expected type by Unmarshalling
|
||||
return true
|
||||
}
|
||||
|
||||
case *schema.Enum:
|
||||
if v.Type != scanner.Ident {
|
||||
return false
|
||||
}
|
||||
for _, option := range t.Values {
|
||||
if option.Name == v.Text {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func canBeFragment(t common.Type) bool {
|
||||
switch t.(type) {
|
||||
case *schema.Object, *schema.Interface, *schema.Union:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func canBeInput(t common.Type) bool {
|
||||
switch t := t.(type) {
|
||||
case *schema.InputObject, *schema.Scalar, *schema.Enum:
|
||||
return true
|
||||
case *common.List:
|
||||
return canBeInput(t.OfType)
|
||||
case *common.NonNull:
|
||||
return canBeInput(t.OfType)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func hasSubfields(t common.Type) bool {
|
||||
switch t := t.(type) {
|
||||
case *schema.Object, *schema.Interface, *schema.Union:
|
||||
return true
|
||||
case *common.List:
|
||||
return hasSubfields(t.OfType)
|
||||
case *common.NonNull:
|
||||
return hasSubfields(t.OfType)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isLeaf(t common.Type) bool {
|
||||
switch t.(type) {
|
||||
case *schema.Scalar, *schema.Enum:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isNull(lit interface{}) bool {
|
||||
_, ok := lit.(*common.NullLit)
|
||||
return ok
|
||||
}
|
||||
|
||||
func typesCompatible(a, b common.Type) bool {
|
||||
al, aIsList := a.(*common.List)
|
||||
bl, bIsList := b.(*common.List)
|
||||
if aIsList || bIsList {
|
||||
return aIsList && bIsList && typesCompatible(al.OfType, bl.OfType)
|
||||
}
|
||||
|
||||
ann, aIsNN := a.(*common.NonNull)
|
||||
bnn, bIsNN := b.(*common.NonNull)
|
||||
if aIsNN || bIsNN {
|
||||
return aIsNN && bIsNN && typesCompatible(ann.OfType, bnn.OfType)
|
||||
}
|
||||
|
||||
if isLeaf(a) || isLeaf(b) {
|
||||
return a == b
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func typeCanBeUsedAs(t, as common.Type) bool {
|
||||
nnT, okT := t.(*common.NonNull)
|
||||
if okT {
|
||||
t = nnT.OfType
|
||||
}
|
||||
|
||||
nnAs, okAs := as.(*common.NonNull)
|
||||
if okAs {
|
||||
as = nnAs.OfType
|
||||
if !okT {
|
||||
return false // nullable can not be used as non-null
|
||||
}
|
||||
}
|
||||
|
||||
if t == as {
|
||||
return true
|
||||
}
|
||||
|
||||
if lT, ok := t.(*common.List); ok {
|
||||
if lAs, ok := as.(*common.List); ok {
|
||||
return typeCanBeUsedAs(lT.OfType, lAs.OfType)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
117
vendor/github.com/neelance/graphql-go/introspection.go
generated
vendored
Normal file
117
vendor/github.com/neelance/graphql-go/introspection.go
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
package graphql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/neelance/graphql-go/internal/exec/resolvable"
|
||||
"github.com/neelance/graphql-go/introspection"
|
||||
)
|
||||
|
||||
// Inspect allows inspection of the given schema.
|
||||
func (s *Schema) Inspect() *introspection.Schema {
|
||||
return introspection.WrapSchema(s.schema)
|
||||
}
|
||||
|
||||
// ToJSON encodes the schema in a JSON format used by tools like Relay.
|
||||
func (s *Schema) ToJSON() ([]byte, error) {
|
||||
result := s.exec(context.Background(), introspectionQuery, "", nil, &resolvable.Schema{
|
||||
Query: &resolvable.Object{},
|
||||
Schema: *s.schema,
|
||||
})
|
||||
if len(result.Errors) != 0 {
|
||||
panic(result.Errors[0])
|
||||
}
|
||||
return json.MarshalIndent(result.Data, "", "\t")
|
||||
}
|
||||
|
||||
var introspectionQuery = `
|
||||
query {
|
||||
__schema {
|
||||
queryType { name }
|
||||
mutationType { name }
|
||||
subscriptionType { name }
|
||||
types {
|
||||
...FullType
|
||||
}
|
||||
directives {
|
||||
name
|
||||
description
|
||||
locations
|
||||
args {
|
||||
...InputValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fragment FullType on __Type {
|
||||
kind
|
||||
name
|
||||
description
|
||||
fields(includeDeprecated: true) {
|
||||
name
|
||||
description
|
||||
args {
|
||||
...InputValue
|
||||
}
|
||||
type {
|
||||
...TypeRef
|
||||
}
|
||||
isDeprecated
|
||||
deprecationReason
|
||||
}
|
||||
inputFields {
|
||||
...InputValue
|
||||
}
|
||||
interfaces {
|
||||
...TypeRef
|
||||
}
|
||||
enumValues(includeDeprecated: true) {
|
||||
name
|
||||
description
|
||||
isDeprecated
|
||||
deprecationReason
|
||||
}
|
||||
possibleTypes {
|
||||
...TypeRef
|
||||
}
|
||||
}
|
||||
fragment InputValue on __InputValue {
|
||||
name
|
||||
description
|
||||
type { ...TypeRef }
|
||||
defaultValue
|
||||
}
|
||||
fragment TypeRef on __Type {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
313
vendor/github.com/neelance/graphql-go/introspection/introspection.go
generated
vendored
Normal file
313
vendor/github.com/neelance/graphql-go/introspection/introspection.go
generated
vendored
Normal file
@ -0,0 +1,313 @@
|
||||
package introspection
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/neelance/graphql-go/internal/common"
|
||||
"github.com/neelance/graphql-go/internal/schema"
|
||||
)
|
||||
|
||||
type Schema struct {
|
||||
schema *schema.Schema
|
||||
}
|
||||
|
||||
// WrapSchema is only used internally.
|
||||
func WrapSchema(schema *schema.Schema) *Schema {
|
||||
return &Schema{schema}
|
||||
}
|
||||
|
||||
func (r *Schema) Types() []*Type {
|
||||
var names []string
|
||||
for name := range r.schema.Types {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
l := make([]*Type, len(names))
|
||||
for i, name := range names {
|
||||
l[i] = &Type{r.schema.Types[name]}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (r *Schema) Directives() []*Directive {
|
||||
var names []string
|
||||
for name := range r.schema.Directives {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
l := make([]*Directive, len(names))
|
||||
for i, name := range names {
|
||||
l[i] = &Directive{r.schema.Directives[name]}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (r *Schema) QueryType() *Type {
|
||||
t, ok := r.schema.EntryPoints["query"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &Type{t}
|
||||
}
|
||||
|
||||
func (r *Schema) MutationType() *Type {
|
||||
t, ok := r.schema.EntryPoints["mutation"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &Type{t}
|
||||
}
|
||||
|
||||
func (r *Schema) SubscriptionType() *Type {
|
||||
t, ok := r.schema.EntryPoints["subscription"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &Type{t}
|
||||
}
|
||||
|
||||
type Type struct {
|
||||
typ common.Type
|
||||
}
|
||||
|
||||
// WrapType is only used internally.
|
||||
func WrapType(typ common.Type) *Type {
|
||||
return &Type{typ}
|
||||
}
|
||||
|
||||
func (r *Type) Kind() string {
|
||||
return r.typ.Kind()
|
||||
}
|
||||
|
||||
func (r *Type) Name() *string {
|
||||
if named, ok := r.typ.(schema.NamedType); ok {
|
||||
name := named.TypeName()
|
||||
return &name
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Type) Description() *string {
|
||||
if named, ok := r.typ.(schema.NamedType); ok {
|
||||
desc := named.Description()
|
||||
if desc == "" {
|
||||
return nil
|
||||
}
|
||||
return &desc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Type) Fields(args *struct{ IncludeDeprecated bool }) *[]*Field {
|
||||
var fields schema.FieldList
|
||||
switch t := r.typ.(type) {
|
||||
case *schema.Object:
|
||||
fields = t.Fields
|
||||
case *schema.Interface:
|
||||
fields = t.Fields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
var l []*Field
|
||||
for _, f := range fields {
|
||||
if d := f.Directives.Get("deprecated"); d == nil || args.IncludeDeprecated {
|
||||
l = append(l, &Field{f})
|
||||
}
|
||||
}
|
||||
return &l
|
||||
}
|
||||
|
||||
func (r *Type) Interfaces() *[]*Type {
|
||||
t, ok := r.typ.(*schema.Object)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
l := make([]*Type, len(t.Interfaces))
|
||||
for i, intf := range t.Interfaces {
|
||||
l[i] = &Type{intf}
|
||||
}
|
||||
return &l
|
||||
}
|
||||
|
||||
func (r *Type) PossibleTypes() *[]*Type {
|
||||
var possibleTypes []*schema.Object
|
||||
switch t := r.typ.(type) {
|
||||
case *schema.Interface:
|
||||
possibleTypes = t.PossibleTypes
|
||||
case *schema.Union:
|
||||
possibleTypes = t.PossibleTypes
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
l := make([]*Type, len(possibleTypes))
|
||||
for i, intf := range possibleTypes {
|
||||
l[i] = &Type{intf}
|
||||
}
|
||||
return &l
|
||||
}
|
||||
|
||||
func (r *Type) EnumValues(args *struct{ IncludeDeprecated bool }) *[]*EnumValue {
|
||||
t, ok := r.typ.(*schema.Enum)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var l []*EnumValue
|
||||
for _, v := range t.Values {
|
||||
if d := v.Directives.Get("deprecated"); d == nil || args.IncludeDeprecated {
|
||||
l = append(l, &EnumValue{v})
|
||||
}
|
||||
}
|
||||
return &l
|
||||
}
|
||||
|
||||
func (r *Type) InputFields() *[]*InputValue {
|
||||
t, ok := r.typ.(*schema.InputObject)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
l := make([]*InputValue, len(t.Values))
|
||||
for i, v := range t.Values {
|
||||
l[i] = &InputValue{v}
|
||||
}
|
||||
return &l
|
||||
}
|
||||
|
||||
func (r *Type) OfType() *Type {
|
||||
switch t := r.typ.(type) {
|
||||
case *common.List:
|
||||
return &Type{t.OfType}
|
||||
case *common.NonNull:
|
||||
return &Type{t.OfType}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
field *schema.Field
|
||||
}
|
||||
|
||||
func (r *Field) Name() string {
|
||||
return r.field.Name
|
||||
}
|
||||
|
||||
func (r *Field) Description() *string {
|
||||
if r.field.Desc == "" {
|
||||
return nil
|
||||
}
|
||||
return &r.field.Desc
|
||||
}
|
||||
|
||||
func (r *Field) Args() []*InputValue {
|
||||
l := make([]*InputValue, len(r.field.Args))
|
||||
for i, v := range r.field.Args {
|
||||
l[i] = &InputValue{v}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (r *Field) Type() *Type {
|
||||
return &Type{r.field.Type}
|
||||
}
|
||||
|
||||
func (r *Field) IsDeprecated() bool {
|
||||
return r.field.Directives.Get("deprecated") != nil
|
||||
}
|
||||
|
||||
func (r *Field) DeprecationReason() *string {
|
||||
d := r.field.Directives.Get("deprecated")
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
reason := d.Args.MustGet("reason").Value(nil).(string)
|
||||
return &reason
|
||||
}
|
||||
|
||||
type InputValue struct {
|
||||
value *common.InputValue
|
||||
}
|
||||
|
||||
func (r *InputValue) Name() string {
|
||||
return r.value.Name.Name
|
||||
}
|
||||
|
||||
func (r *InputValue) Description() *string {
|
||||
if r.value.Desc == "" {
|
||||
return nil
|
||||
}
|
||||
return &r.value.Desc
|
||||
}
|
||||
|
||||
func (r *InputValue) Type() *Type {
|
||||
return &Type{r.value.Type}
|
||||
}
|
||||
|
||||
func (r *InputValue) DefaultValue() *string {
|
||||
if r.value.Default == nil {
|
||||
return nil
|
||||
}
|
||||
s := r.value.Default.String()
|
||||
return &s
|
||||
}
|
||||
|
||||
type EnumValue struct {
|
||||
value *schema.EnumValue
|
||||
}
|
||||
|
||||
func (r *EnumValue) Name() string {
|
||||
return r.value.Name
|
||||
}
|
||||
|
||||
func (r *EnumValue) Description() *string {
|
||||
if r.value.Desc == "" {
|
||||
return nil
|
||||
}
|
||||
return &r.value.Desc
|
||||
}
|
||||
|
||||
func (r *EnumValue) IsDeprecated() bool {
|
||||
return r.value.Directives.Get("deprecated") != nil
|
||||
}
|
||||
|
||||
func (r *EnumValue) DeprecationReason() *string {
|
||||
d := r.value.Directives.Get("deprecated")
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
reason := d.Args.MustGet("reason").Value(nil).(string)
|
||||
return &reason
|
||||
}
|
||||
|
||||
type Directive struct {
|
||||
directive *schema.DirectiveDecl
|
||||
}
|
||||
|
||||
func (r *Directive) Name() string {
|
||||
return r.directive.Name
|
||||
}
|
||||
|
||||
func (r *Directive) Description() *string {
|
||||
if r.directive.Desc == "" {
|
||||
return nil
|
||||
}
|
||||
return &r.directive.Desc
|
||||
}
|
||||
|
||||
func (r *Directive) Locations() []string {
|
||||
return r.directive.Locs
|
||||
}
|
||||
|
||||
func (r *Directive) Args() []*InputValue {
|
||||
l := make([]*InputValue, len(r.directive.Args))
|
||||
for i, v := range r.directive.Args {
|
||||
l[i] = &InputValue{v}
|
||||
}
|
||||
return l
|
||||
}
|
23
vendor/github.com/neelance/graphql-go/log/log.go
generated
vendored
Normal file
23
vendor/github.com/neelance/graphql-go/log/log.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Logger is the interface used to log panics that occur durring query execution. It is setable via graphql.ParseSchema
|
||||
type Logger interface {
|
||||
LogPanic(ctx context.Context, value interface{})
|
||||
}
|
||||
|
||||
// DefaultLogger is the default logger used to log panics that occur durring query execution
|
||||
type DefaultLogger struct{}
|
||||
|
||||
// LogPanic is used to log recovered panic values that occur durring query execution
|
||||
func (l *DefaultLogger) LogPanic(_ context.Context, value interface{}) {
|
||||
const size = 64 << 10
|
||||
buf := make([]byte, size)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
log.Printf("graphql: panic occurred: %v\n%s", value, buf)
|
||||
}
|
70
vendor/github.com/neelance/graphql-go/relay/relay.go
generated
vendored
Normal file
70
vendor/github.com/neelance/graphql-go/relay/relay.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
graphql "github.com/neelance/graphql-go"
|
||||
)
|
||||
|
||||
func MarshalID(kind string, spec interface{}) graphql.ID {
|
||||
d, err := json.Marshal(spec)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("relay.MarshalID: %s", err))
|
||||
}
|
||||
return graphql.ID(base64.URLEncoding.EncodeToString(append([]byte(kind+":"), d...)))
|
||||
}
|
||||
|
||||
func UnmarshalKind(id graphql.ID) string {
|
||||
s, err := base64.URLEncoding.DecodeString(string(id))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
i := strings.IndexByte(string(s), ':')
|
||||
if i == -1 {
|
||||
return ""
|
||||
}
|
||||
return string(s[:i])
|
||||
}
|
||||
|
||||
func UnmarshalSpec(id graphql.ID, v interface{}) error {
|
||||
s, err := base64.URLEncoding.DecodeString(string(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i := strings.IndexByte(string(s), ':')
|
||||
if i == -1 {
|
||||
return errors.New("invalid graphql.ID")
|
||||
}
|
||||
return json.Unmarshal([]byte(s[i+1:]), v)
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
Schema *graphql.Schema
|
||||
}
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
var params struct {
|
||||
Query string `json:"query"`
|
||||
OperationName string `json:"operationName"`
|
||||
Variables map[string]interface{} `json:"variables"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
response := h.Schema.Exec(r.Context(), params.Query, params.OperationName, params.Variables)
|
||||
responseJSON, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(responseJSON)
|
||||
}
|
36
vendor/github.com/neelance/graphql-go/relay/relay_test.go
generated
vendored
Normal file
36
vendor/github.com/neelance/graphql-go/relay/relay_test.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
package relay_test
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/neelance/graphql-go"
|
||||
"github.com/neelance/graphql-go/example/starwars"
|
||||
"github.com/neelance/graphql-go/relay"
|
||||
)
|
||||
|
||||
var starwarsSchema = graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{})
|
||||
|
||||
func TestServeHTTP(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("POST", "/some/path/here", strings.NewReader(`{"query":"{ hero { name } }", "operationName":"", "variables": null}`))
|
||||
h := relay.Handler{Schema: starwarsSchema}
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
|
||||
if w.Code != 200 {
|
||||
t.Fatalf("Expected status code 200, got %d.", w.Code)
|
||||
}
|
||||
|
||||
contentType := w.Header().Get("Content-Type")
|
||||
if contentType != "application/json" {
|
||||
t.Fatalf("Invalid content-type. Expected [application/json], but instead got [%s]", contentType)
|
||||
}
|
||||
|
||||
expectedResponse := `{"data":{"hero":{"name":"R2-D2"}}}`
|
||||
actualResponse := w.Body.String()
|
||||
if expectedResponse != actualResponse {
|
||||
t.Fatalf("Invalid response. Expected [%s], but instead got [%s]", expectedResponse, actualResponse)
|
||||
}
|
||||
}
|
36
vendor/github.com/neelance/graphql-go/time.go
generated
vendored
Normal file
36
vendor/github.com/neelance/graphql-go/time.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
package graphql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Time is a custom GraphQL type to represent an instant in time. It has to be added to a schema
|
||||
// via "scalar Time" since it is not a predeclared GraphQL type like "ID".
|
||||
type Time struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (_ Time) ImplementsGraphQLType(name string) bool {
|
||||
return name == "Time"
|
||||
}
|
||||
|
||||
func (t *Time) UnmarshalGraphQL(input interface{}) error {
|
||||
switch input := input.(type) {
|
||||
case time.Time:
|
||||
t.Time = input
|
||||
return nil
|
||||
case string:
|
||||
var err error
|
||||
t.Time, err = time.Parse(time.RFC3339, input)
|
||||
return err
|
||||
case int:
|
||||
t.Time = time.Unix(int64(input), 0)
|
||||
return nil
|
||||
case float64:
|
||||
t.Time = time.Unix(int64(input), 0)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("wrong type")
|
||||
}
|
||||
}
|
80
vendor/github.com/neelance/graphql-go/trace/trace.go
generated
vendored
Normal file
80
vendor/github.com/neelance/graphql-go/trace/trace.go
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/neelance/graphql-go/errors"
|
||||
"github.com/neelance/graphql-go/introspection"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/opentracing/opentracing-go/log"
|
||||
)
|
||||
|
||||
type TraceQueryFinishFunc func([]*errors.QueryError)
|
||||
type TraceFieldFinishFunc func(*errors.QueryError)
|
||||
|
||||
type Tracer interface {
|
||||
TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, TraceQueryFinishFunc)
|
||||
TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, TraceFieldFinishFunc)
|
||||
}
|
||||
|
||||
type OpenTracingTracer struct{}
|
||||
|
||||
func (OpenTracingTracer) TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, TraceQueryFinishFunc) {
|
||||
span, spanCtx := opentracing.StartSpanFromContext(ctx, "GraphQL request")
|
||||
span.SetTag("graphql.query", queryString)
|
||||
|
||||
if operationName != "" {
|
||||
span.SetTag("graphql.operationName", operationName)
|
||||
}
|
||||
|
||||
if len(variables) != 0 {
|
||||
span.LogFields(log.Object("graphql.variables", variables))
|
||||
}
|
||||
|
||||
return spanCtx, func(errs []*errors.QueryError) {
|
||||
if len(errs) > 0 {
|
||||
msg := errs[0].Error()
|
||||
if len(errs) > 1 {
|
||||
msg += fmt.Sprintf(" (and %d more errors)", len(errs)-1)
|
||||
}
|
||||
ext.Error.Set(span, true)
|
||||
span.SetTag("graphql.error", msg)
|
||||
}
|
||||
span.Finish()
|
||||
}
|
||||
}
|
||||
|
||||
func (OpenTracingTracer) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, TraceFieldFinishFunc) {
|
||||
if trivial {
|
||||
return ctx, noop
|
||||
}
|
||||
|
||||
span, spanCtx := opentracing.StartSpanFromContext(ctx, label)
|
||||
span.SetTag("graphql.type", typeName)
|
||||
span.SetTag("graphql.field", fieldName)
|
||||
for name, value := range args {
|
||||
span.SetTag("graphql.args."+name, value)
|
||||
}
|
||||
|
||||
return spanCtx, func(err *errors.QueryError) {
|
||||
if err != nil {
|
||||
ext.Error.Set(span, true)
|
||||
span.SetTag("graphql.error", err.Error())
|
||||
}
|
||||
span.Finish()
|
||||
}
|
||||
}
|
||||
|
||||
func noop(*errors.QueryError) {}
|
||||
|
||||
type NoopTracer struct{}
|
||||
|
||||
func (NoopTracer) TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, TraceQueryFinishFunc) {
|
||||
return ctx, func(errs []*errors.QueryError) {}
|
||||
}
|
||||
|
||||
func (NoopTracer) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, TraceFieldFinishFunc) {
|
||||
return ctx, func(err *errors.QueryError) {}
|
||||
}
|
13
vendor/github.com/opentracing/opentracing-go/.gitignore
generated
vendored
Normal file
13
vendor/github.com/opentracing/opentracing-go/.gitignore
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
# IntelliJ project files
|
||||
.idea/
|
||||
opentracing-go.iml
|
||||
opentracing-go.ipr
|
||||
opentracing-go.iws
|
||||
|
||||
# Test results
|
||||
*.cov
|
||||
*.html
|
||||
test.log
|
||||
|
||||
# Build dir
|
||||
build/
|
14
vendor/github.com/opentracing/opentracing-go/.travis.yml
generated
vendored
Normal file
14
vendor/github.com/opentracing/opentracing-go/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- tip
|
||||
|
||||
install:
|
||||
- go get -d -t github.com/opentracing/opentracing-go/...
|
||||
- go get -u github.com/golang/lint/...
|
||||
script:
|
||||
- make test lint
|
||||
- go build ./...
|
14
vendor/github.com/opentracing/opentracing-go/CHANGELOG.md
generated
vendored
Normal file
14
vendor/github.com/opentracing/opentracing-go/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
Changes by Version
|
||||
==================
|
||||
|
||||
1.1.0 (unreleased)
|
||||
-------------------
|
||||
|
||||
- Deprecate InitGlobalTracer() in favor of SetGlobalTracer()
|
||||
|
||||
|
||||
1.0.0 (2016-09-26)
|
||||
-------------------
|
||||
|
||||
- This release implements OpenTracing Specification 1.0 (http://opentracing.io/spec)
|
||||
|
21
vendor/github.com/opentracing/opentracing-go/LICENSE
generated
vendored
Normal file
21
vendor/github.com/opentracing/opentracing-go/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 The OpenTracing Authors
|
||||
|
||||
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.
|
32
vendor/github.com/opentracing/opentracing-go/Makefile
generated
vendored
Normal file
32
vendor/github.com/opentracing/opentracing-go/Makefile
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
PACKAGES := . ./mocktracer/... ./ext/...
|
||||
|
||||
.DEFAULT_GOAL := test-and-lint
|
||||
|
||||
.PHONE: test-and-lint
|
||||
|
||||
test-and-lint: test lint
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v -cover ./...
|
||||
|
||||
cover:
|
||||
@rm -rf cover-all.out
|
||||
$(foreach pkg, $(PACKAGES), $(MAKE) cover-pkg PKG=$(pkg) || true;)
|
||||
@grep mode: cover.out > coverage.out
|
||||
@cat cover-all.out >> coverage.out
|
||||
go tool cover -html=coverage.out -o cover.html
|
||||
@rm -rf cover.out cover-all.out coverage.out
|
||||
|
||||
cover-pkg:
|
||||
go test -coverprofile cover.out $(PKG)
|
||||
@grep -v mode: cover.out >> cover-all.out
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
go fmt ./...
|
||||
golint ./...
|
||||
@# Run again with magic to exit non-zero if golint outputs anything.
|
||||
@! (golint ./... | read dummy)
|
||||
go vet ./...
|
||||
|
147
vendor/github.com/opentracing/opentracing-go/README.md
generated
vendored
Normal file
147
vendor/github.com/opentracing/opentracing-go/README.md
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
[![Gitter chat](http://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/opentracing/public) [![Build Status](https://travis-ci.org/opentracing/opentracing-go.svg?branch=master)](https://travis-ci.org/opentracing/opentracing-go) [![GoDoc](https://godoc.org/github.com/opentracing/opentracing-go?status.svg)](http://godoc.org/github.com/opentracing/opentracing-go)
|
||||
|
||||
# OpenTracing API for Go
|
||||
|
||||
This package is a Go platform API for OpenTracing.
|
||||
|
||||
## Required Reading
|
||||
|
||||
In order to understand the Go platform API, one must first be familiar with the
|
||||
[OpenTracing project](http://opentracing.io) and
|
||||
[terminology](http://opentracing.io/documentation/pages/spec.html) more specifically.
|
||||
|
||||
## API overview for those adding instrumentation
|
||||
|
||||
Everyday consumers of this `opentracing` package really only need to worry
|
||||
about a couple of key abstractions: the `StartSpan` function, the `Span`
|
||||
interface, and binding a `Tracer` at `main()`-time. Here are code snippets
|
||||
demonstrating some important use cases.
|
||||
|
||||
#### Singleton initialization
|
||||
|
||||
The simplest starting point is `./default_tracer.go`. As early as possible, call
|
||||
|
||||
```go
|
||||
import "github.com/opentracing/opentracing-go"
|
||||
import ".../some_tracing_impl"
|
||||
|
||||
func main() {
|
||||
opentracing.InitGlobalTracer(
|
||||
// tracing impl specific:
|
||||
some_tracing_impl.New(...),
|
||||
)
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
##### Non-Singleton initialization
|
||||
|
||||
If you prefer direct control to singletons, manage ownership of the
|
||||
`opentracing.Tracer` implementation explicitly.
|
||||
|
||||
#### Creating a Span given an existing Go `context.Context`
|
||||
|
||||
If you use `context.Context` in your application, OpenTracing's Go library will
|
||||
happily rely on it for `Span` propagation. To start a new (blocking child)
|
||||
`Span`, you can use `StartSpanFromContext`.
|
||||
|
||||
```go
|
||||
func xyz(ctx context.Context, ...) {
|
||||
...
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "operation_name")
|
||||
defer span.Finish()
|
||||
span.LogFields(
|
||||
log.String("event", "soft error"),
|
||||
log.String("type", "cache timeout"),
|
||||
log.Int("waited.millis", 1500))
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Starting an empty trace by creating a "root span"
|
||||
|
||||
It's always possible to create a "root" `Span` with no parent or other causal
|
||||
reference.
|
||||
|
||||
```go
|
||||
func xyz() {
|
||||
...
|
||||
sp := opentracing.StartSpan("operation_name")
|
||||
defer sp.Finish()
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Creating a (child) Span given an existing (parent) Span
|
||||
|
||||
```go
|
||||
func xyz(parentSpan opentracing.Span, ...) {
|
||||
...
|
||||
sp := opentracing.StartSpan(
|
||||
"operation_name",
|
||||
opentracing.ChildOf(parentSpan.Context()))
|
||||
defer sp.Finish()
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Serializing to the wire
|
||||
|
||||
```go
|
||||
func makeSomeRequest(ctx context.Context) ... {
|
||||
if span := opentracing.SpanFromContext(ctx); span != nil {
|
||||
httpClient := &http.Client{}
|
||||
httpReq, _ := http.NewRequest("GET", "http://myservice/", nil)
|
||||
|
||||
// Transmit the span's TraceContext as HTTP headers on our
|
||||
// outbound request.
|
||||
opentracing.GlobalTracer().Inject(
|
||||
span.Context(),
|
||||
opentracing.HTTPHeaders,
|
||||
opentracing.HTTPHeadersCarrier(httpReq.Header))
|
||||
|
||||
resp, err := httpClient.Do(httpReq)
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Deserializing from the wire
|
||||
|
||||
```go
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||
var serverSpan opentracing.Span
|
||||
appSpecificOperationName := ...
|
||||
wireContext, err := opentracing.GlobalTracer().Extract(
|
||||
opentracing.HTTPHeaders,
|
||||
opentracing.HTTPHeadersCarrier(req.Header))
|
||||
if err != nil {
|
||||
// Optionally record something about err here
|
||||
}
|
||||
|
||||
// Create the span referring to the RPC client if available.
|
||||
// If wireContext == nil, a root span will be created.
|
||||
serverSpan = opentracing.StartSpan(
|
||||
appSpecificOperationName,
|
||||
ext.RPCServerOption(wireContext))
|
||||
|
||||
defer serverSpan.Finish()
|
||||
|
||||
ctx := opentracing.ContextWithSpan(context.Background(), serverSpan)
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Goroutine-safety
|
||||
|
||||
The entire public API is goroutine-safe and does not require external
|
||||
synchronization.
|
||||
|
||||
## API pointers for those implementing a tracing system
|
||||
|
||||
Tracing system implementors may be able to reuse or copy-paste-modify the `basictracer` package, found [here](https://github.com/opentracing/basictracer-go). In particular, see `basictracer.New(...)`.
|
||||
|
||||
## API compatibility
|
||||
|
||||
For the time being, "mild" backwards-incompatible changes may be made without changing the major version number. As OpenTracing and `opentracing-go` mature, backwards compatibility will become more of a priority.
|
198
vendor/github.com/opentracing/opentracing-go/ext/tags.go
generated
vendored
Normal file
198
vendor/github.com/opentracing/opentracing-go/ext/tags.go
generated
vendored
Normal file
@ -0,0 +1,198 @@
|
||||
package ext
|
||||
|
||||
import opentracing "github.com/opentracing/opentracing-go"
|
||||
|
||||
// These constants define common tag names recommended for better portability across
|
||||
// tracing systems and languages/platforms.
|
||||
//
|
||||
// The tag names are defined as typed strings, so that in addition to the usual use
|
||||
//
|
||||
// span.setTag(TagName, value)
|
||||
//
|
||||
// they also support value type validation via this additional syntax:
|
||||
//
|
||||
// TagName.Set(span, value)
|
||||
//
|
||||
var (
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// SpanKind (client/server or producer/consumer)
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
// SpanKind hints at relationship between spans, e.g. client/server
|
||||
SpanKind = spanKindTagName("span.kind")
|
||||
|
||||
// SpanKindRPCClient marks a span representing the client-side of an RPC
|
||||
// or other remote call
|
||||
SpanKindRPCClientEnum = SpanKindEnum("client")
|
||||
SpanKindRPCClient = opentracing.Tag{Key: string(SpanKind), Value: SpanKindRPCClientEnum}
|
||||
|
||||
// SpanKindRPCServer marks a span representing the server-side of an RPC
|
||||
// or other remote call
|
||||
SpanKindRPCServerEnum = SpanKindEnum("server")
|
||||
SpanKindRPCServer = opentracing.Tag{Key: string(SpanKind), Value: SpanKindRPCServerEnum}
|
||||
|
||||
// SpanKindProducer marks a span representing the producer-side of a
|
||||
// message bus
|
||||
SpanKindProducerEnum = SpanKindEnum("producer")
|
||||
SpanKindProducer = opentracing.Tag{Key: string(SpanKind), Value: SpanKindProducerEnum}
|
||||
|
||||
// SpanKindConsumer marks a span representing the consumer-side of a
|
||||
// message bus
|
||||
SpanKindConsumerEnum = SpanKindEnum("consumer")
|
||||
SpanKindConsumer = opentracing.Tag{Key: string(SpanKind), Value: SpanKindConsumerEnum}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Component name
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Component is a low-cardinality identifier of the module, library,
|
||||
// or package that is generating a span.
|
||||
Component = stringTagName("component")
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Sampling hint
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
// SamplingPriority determines the priority of sampling this Span.
|
||||
SamplingPriority = uint16TagName("sampling.priority")
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Peer tags. These tags can be emitted by either client-side of
|
||||
// server-side to describe the other side/service in a peer-to-peer
|
||||
// communications, like an RPC call.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
// PeerService records the service name of the peer.
|
||||
PeerService = stringTagName("peer.service")
|
||||
|
||||
// PeerAddress records the address name of the peer. This may be a "ip:port",
|
||||
// a bare "hostname", a FQDN or even a database DSN substring
|
||||
// like "mysql://username@127.0.0.1:3306/dbname"
|
||||
PeerAddress = stringTagName("peer.address")
|
||||
|
||||
// PeerHostname records the host name of the peer
|
||||
PeerHostname = stringTagName("peer.hostname")
|
||||
|
||||
// PeerHostIPv4 records IP v4 host address of the peer
|
||||
PeerHostIPv4 = uint32TagName("peer.ipv4")
|
||||
|
||||
// PeerHostIPv6 records IP v6 host address of the peer
|
||||
PeerHostIPv6 = stringTagName("peer.ipv6")
|
||||
|
||||
// PeerPort records port number of the peer
|
||||
PeerPort = uint16TagName("peer.port")
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// HTTP Tags
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
// HTTPUrl should be the URL of the request being handled in this segment
|
||||
// of the trace, in standard URI format. The protocol is optional.
|
||||
HTTPUrl = stringTagName("http.url")
|
||||
|
||||
// HTTPMethod is the HTTP method of the request, and is case-insensitive.
|
||||
HTTPMethod = stringTagName("http.method")
|
||||
|
||||
// HTTPStatusCode is the numeric HTTP status code (200, 404, etc) of the
|
||||
// HTTP response.
|
||||
HTTPStatusCode = uint16TagName("http.status_code")
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// DB Tags
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
// DBInstance is database instance name.
|
||||
DBInstance = stringTagName("db.instance")
|
||||
|
||||
// DBStatement is a database statement for the given database type.
|
||||
// It can be a query or a prepared statement (i.e., before substitution).
|
||||
DBStatement = stringTagName("db.statement")
|
||||
|
||||
// DBType is a database type. For any SQL database, "sql".
|
||||
// For others, the lower-case database category, e.g. "redis"
|
||||
DBType = stringTagName("db.type")
|
||||
|
||||
// DBUser is a username for accessing database.
|
||||
DBUser = stringTagName("db.user")
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Message Bus Tag
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
// MessageBusDestination is an address at which messages can be exchanged
|
||||
MessageBusDestination = stringTagName("message_bus.destination")
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Error Tag
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Error indicates that operation represented by the span resulted in an error.
|
||||
Error = boolTagName("error")
|
||||
)
|
||||
|
||||
// ---
|
||||
|
||||
// SpanKindEnum represents common span types
|
||||
type SpanKindEnum string
|
||||
|
||||
type spanKindTagName string
|
||||
|
||||
// Set adds a string tag to the `span`
|
||||
func (tag spanKindTagName) Set(span opentracing.Span, value SpanKindEnum) {
|
||||
span.SetTag(string(tag), value)
|
||||
}
|
||||
|
||||
type rpcServerOption struct {
|
||||
clientContext opentracing.SpanContext
|
||||
}
|
||||
|
||||
func (r rpcServerOption) Apply(o *opentracing.StartSpanOptions) {
|
||||
if r.clientContext != nil {
|
||||
opentracing.ChildOf(r.clientContext).Apply(o)
|
||||
}
|
||||
SpanKindRPCServer.Apply(o)
|
||||
}
|
||||
|
||||
// RPCServerOption returns a StartSpanOption appropriate for an RPC server span
|
||||
// with `client` representing the metadata for the remote peer Span if available.
|
||||
// In case client == nil, due to the client not being instrumented, this RPC
|
||||
// server span will be a root span.
|
||||
func RPCServerOption(client opentracing.SpanContext) opentracing.StartSpanOption {
|
||||
return rpcServerOption{client}
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
type stringTagName string
|
||||
|
||||
// Set adds a string tag to the `span`
|
||||
func (tag stringTagName) Set(span opentracing.Span, value string) {
|
||||
span.SetTag(string(tag), value)
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
type uint32TagName string
|
||||
|
||||
// Set adds a uint32 tag to the `span`
|
||||
func (tag uint32TagName) Set(span opentracing.Span, value uint32) {
|
||||
span.SetTag(string(tag), value)
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
type uint16TagName string
|
||||
|
||||
// Set adds a uint16 tag to the `span`
|
||||
func (tag uint16TagName) Set(span opentracing.Span, value uint16) {
|
||||
span.SetTag(string(tag), value)
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
type boolTagName string
|
||||
|
||||
// Add adds a bool tag to the `span`
|
||||
func (tag boolTagName) Set(span opentracing.Span, value bool) {
|
||||
span.SetTag(string(tag), value)
|
||||
}
|
148
vendor/github.com/opentracing/opentracing-go/ext/tags_test.go
generated
vendored
Normal file
148
vendor/github.com/opentracing/opentracing-go/ext/tags_test.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
package ext_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/opentracing/opentracing-go/mocktracer"
|
||||
)
|
||||
|
||||
func TestPeerTags(t *testing.T) {
|
||||
if ext.PeerService != "peer.service" {
|
||||
t.Fatalf("Invalid PeerService %v", ext.PeerService)
|
||||
}
|
||||
tracer := mocktracer.New()
|
||||
span := tracer.StartSpan("my-trace")
|
||||
ext.PeerService.Set(span, "my-service")
|
||||
ext.PeerAddress.Set(span, "my-hostname:8080")
|
||||
ext.PeerHostname.Set(span, "my-hostname")
|
||||
ext.PeerHostIPv4.Set(span, uint32(127<<24|1))
|
||||
ext.PeerHostIPv6.Set(span, "::")
|
||||
ext.PeerPort.Set(span, uint16(8080))
|
||||
ext.SamplingPriority.Set(span, uint16(1))
|
||||
ext.SpanKind.Set(span, ext.SpanKindRPCServerEnum)
|
||||
ext.SpanKindRPCClient.Set(span)
|
||||
span.Finish()
|
||||
|
||||
rawSpan := tracer.FinishedSpans()[0]
|
||||
assert.Equal(t, map[string]interface{}{
|
||||
"peer.service": "my-service",
|
||||
"peer.address": "my-hostname:8080",
|
||||
"peer.hostname": "my-hostname",
|
||||
"peer.ipv4": uint32(127<<24 | 1),
|
||||
"peer.ipv6": "::",
|
||||
"peer.port": uint16(8080),
|
||||
"span.kind": ext.SpanKindRPCClientEnum,
|
||||
}, rawSpan.Tags())
|
||||
assert.True(t, span.Context().(mocktracer.MockSpanContext).Sampled)
|
||||
ext.SamplingPriority.Set(span, uint16(0))
|
||||
assert.False(t, span.Context().(mocktracer.MockSpanContext).Sampled)
|
||||
}
|
||||
|
||||
func TestHTTPTags(t *testing.T) {
|
||||
tracer := mocktracer.New()
|
||||
span := tracer.StartSpan("my-trace", ext.SpanKindRPCServer)
|
||||
ext.HTTPUrl.Set(span, "test.biz/uri?protocol=false")
|
||||
ext.HTTPMethod.Set(span, "GET")
|
||||
ext.HTTPStatusCode.Set(span, 301)
|
||||
span.Finish()
|
||||
|
||||
rawSpan := tracer.FinishedSpans()[0]
|
||||
assert.Equal(t, map[string]interface{}{
|
||||
"http.url": "test.biz/uri?protocol=false",
|
||||
"http.method": "GET",
|
||||
"http.status_code": uint16(301),
|
||||
"span.kind": ext.SpanKindRPCServerEnum,
|
||||
}, rawSpan.Tags())
|
||||
}
|
||||
|
||||
func TestDBTags(t *testing.T) {
|
||||
tracer := mocktracer.New()
|
||||
span := tracer.StartSpan("my-trace", ext.SpanKindRPCClient)
|
||||
ext.DBInstance.Set(span, "127.0.0.1:3306/customers")
|
||||
ext.DBStatement.Set(span, "SELECT * FROM user_table")
|
||||
ext.DBType.Set(span, "sql")
|
||||
ext.DBUser.Set(span, "customer_user")
|
||||
span.Finish()
|
||||
|
||||
rawSpan := tracer.FinishedSpans()[0]
|
||||
assert.Equal(t, map[string]interface{}{
|
||||
"db.instance": "127.0.0.1:3306/customers",
|
||||
"db.statement": "SELECT * FROM user_table",
|
||||
"db.type": "sql",
|
||||
"db.user": "customer_user",
|
||||
"span.kind": ext.SpanKindRPCClientEnum,
|
||||
}, rawSpan.Tags())
|
||||
}
|
||||
|
||||
func TestMiscTags(t *testing.T) {
|
||||
tracer := mocktracer.New()
|
||||
span := tracer.StartSpan("my-trace")
|
||||
ext.Component.Set(span, "my-awesome-library")
|
||||
ext.SamplingPriority.Set(span, 1)
|
||||
ext.Error.Set(span, true)
|
||||
|
||||
span.Finish()
|
||||
|
||||
rawSpan := tracer.FinishedSpans()[0]
|
||||
assert.Equal(t, map[string]interface{}{
|
||||
"component": "my-awesome-library",
|
||||
"error": true,
|
||||
}, rawSpan.Tags())
|
||||
}
|
||||
|
||||
func TestRPCServerOption(t *testing.T) {
|
||||
tracer := mocktracer.New()
|
||||
parent := tracer.StartSpan("my-trace")
|
||||
parent.SetBaggageItem("bag", "gage")
|
||||
|
||||
carrier := opentracing.HTTPHeadersCarrier{}
|
||||
err := tracer.Inject(parent.Context(), opentracing.HTTPHeaders, carrier)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
parCtx, err := tracer.Extract(opentracing.HTTPHeaders, carrier)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tracer.StartSpan("my-child", ext.RPCServerOption(parCtx)).Finish()
|
||||
|
||||
rawSpan := tracer.FinishedSpans()[0]
|
||||
assert.Equal(t, map[string]interface{}{
|
||||
"span.kind": ext.SpanKindRPCServerEnum,
|
||||
}, rawSpan.Tags())
|
||||
assert.Equal(t, map[string]string{
|
||||
"bag": "gage",
|
||||
}, rawSpan.Context().(mocktracer.MockSpanContext).Baggage)
|
||||
}
|
||||
|
||||
func TestMessageBusProducerTags(t *testing.T) {
|
||||
tracer := mocktracer.New()
|
||||
span := tracer.StartSpan("my-trace", ext.SpanKindProducer)
|
||||
ext.MessageBusDestination.Set(span, "topic name")
|
||||
span.Finish()
|
||||
|
||||
rawSpan := tracer.FinishedSpans()[0]
|
||||
assert.Equal(t, map[string]interface{}{
|
||||
"message_bus.destination": "topic name",
|
||||
"span.kind": ext.SpanKindProducerEnum,
|
||||
}, rawSpan.Tags())
|
||||
}
|
||||
|
||||
func TestMessageBusConsumerTags(t *testing.T) {
|
||||
tracer := mocktracer.New()
|
||||
span := tracer.StartSpan("my-trace", ext.SpanKindConsumer)
|
||||
ext.MessageBusDestination.Set(span, "topic name")
|
||||
span.Finish()
|
||||
|
||||
rawSpan := tracer.FinishedSpans()[0]
|
||||
assert.Equal(t, map[string]interface{}{
|
||||
"message_bus.destination": "topic name",
|
||||
"span.kind": ext.SpanKindConsumerEnum,
|
||||
}, rawSpan.Tags())
|
||||
}
|
32
vendor/github.com/opentracing/opentracing-go/globaltracer.go
generated
vendored
Normal file
32
vendor/github.com/opentracing/opentracing-go/globaltracer.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
package opentracing
|
||||
|
||||
var (
|
||||
globalTracer Tracer = NoopTracer{}
|
||||
)
|
||||
|
||||
// SetGlobalTracer sets the [singleton] opentracing.Tracer returned by
|
||||
// GlobalTracer(). Those who use GlobalTracer (rather than directly manage an
|
||||
// opentracing.Tracer instance) should call SetGlobalTracer as early as
|
||||
// possible in main(), prior to calling the `StartSpan` global func below.
|
||||
// Prior to calling `SetGlobalTracer`, any Spans started via the `StartSpan`
|
||||
// (etc) globals are noops.
|
||||
func SetGlobalTracer(tracer Tracer) {
|
||||
globalTracer = tracer
|
||||
}
|
||||
|
||||
// GlobalTracer returns the global singleton `Tracer` implementation.
|
||||
// Before `SetGlobalTracer()` is called, the `GlobalTracer()` is a noop
|
||||
// implementation that drops all data handed to it.
|
||||
func GlobalTracer() Tracer {
|
||||
return globalTracer
|
||||
}
|
||||
|
||||
// StartSpan defers to `Tracer.StartSpan`. See `GlobalTracer()`.
|
||||
func StartSpan(operationName string, opts ...StartSpanOption) Span {
|
||||
return globalTracer.StartSpan(operationName, opts...)
|
||||
}
|
||||
|
||||
// InitGlobalTracer is deprecated. Please use SetGlobalTracer.
|
||||
func InitGlobalTracer(tracer Tracer) {
|
||||
SetGlobalTracer(tracer)
|
||||
}
|
57
vendor/github.com/opentracing/opentracing-go/gocontext.go
generated
vendored
Normal file
57
vendor/github.com/opentracing/opentracing-go/gocontext.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
package opentracing
|
||||
|
||||
import "golang.org/x/net/context"
|
||||
|
||||
type contextKey struct{}
|
||||
|
||||
var activeSpanKey = contextKey{}
|
||||
|
||||
// ContextWithSpan returns a new `context.Context` that holds a reference to
|
||||
// `span`'s SpanContext.
|
||||
func ContextWithSpan(ctx context.Context, span Span) context.Context {
|
||||
return context.WithValue(ctx, activeSpanKey, span)
|
||||
}
|
||||
|
||||
// SpanFromContext returns the `Span` previously associated with `ctx`, or
|
||||
// `nil` if no such `Span` could be found.
|
||||
//
|
||||
// NOTE: context.Context != SpanContext: the former is Go's intra-process
|
||||
// context propagation mechanism, and the latter houses OpenTracing's per-Span
|
||||
// identity and baggage information.
|
||||
func SpanFromContext(ctx context.Context) Span {
|
||||
val := ctx.Value(activeSpanKey)
|
||||
if sp, ok := val.(Span); ok {
|
||||
return sp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartSpanFromContext starts and returns a Span with `operationName`, using
|
||||
// any Span found within `ctx` as a ChildOfRef. If no such parent could be
|
||||
// found, StartSpanFromContext creates a root (parentless) Span.
|
||||
//
|
||||
// The second return value is a context.Context object built around the
|
||||
// returned Span.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// SomeFunction(ctx context.Context, ...) {
|
||||
// sp, ctx := opentracing.StartSpanFromContext(ctx, "SomeFunction")
|
||||
// defer sp.Finish()
|
||||
// ...
|
||||
// }
|
||||
func StartSpanFromContext(ctx context.Context, operationName string, opts ...StartSpanOption) (Span, context.Context) {
|
||||
return startSpanFromContextWithTracer(ctx, GlobalTracer(), operationName, opts...)
|
||||
}
|
||||
|
||||
// startSpanFromContextWithTracer is factored out for testing purposes.
|
||||
func startSpanFromContextWithTracer(ctx context.Context, tracer Tracer, operationName string, opts ...StartSpanOption) (Span, context.Context) {
|
||||
var span Span
|
||||
if parentSpan := SpanFromContext(ctx); parentSpan != nil {
|
||||
opts = append(opts, ChildOf(parentSpan.Context()))
|
||||
span = tracer.StartSpan(operationName, opts...)
|
||||
} else {
|
||||
span = tracer.StartSpan(operationName, opts...)
|
||||
}
|
||||
return span, ContextWithSpan(ctx, span)
|
||||
}
|
81
vendor/github.com/opentracing/opentracing-go/gocontext_test.go
generated
vendored
Normal file
81
vendor/github.com/opentracing/opentracing-go/gocontext_test.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
package opentracing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestContextWithSpan(t *testing.T) {
|
||||
span := &noopSpan{}
|
||||
ctx := ContextWithSpan(context.Background(), span)
|
||||
span2 := SpanFromContext(ctx)
|
||||
if span != span2 {
|
||||
t.Errorf("Not the same span returned from context, expected=%+v, actual=%+v", span, span2)
|
||||
}
|
||||
|
||||
ctx = context.Background()
|
||||
span2 = SpanFromContext(ctx)
|
||||
if span2 != nil {
|
||||
t.Errorf("Expected nil span, found %+v", span2)
|
||||
}
|
||||
|
||||
ctx = ContextWithSpan(ctx, span)
|
||||
span2 = SpanFromContext(ctx)
|
||||
if span != span2 {
|
||||
t.Errorf("Not the same span returned from context, expected=%+v, actual=%+v", span, span2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartSpanFromContext(t *testing.T) {
|
||||
testTracer := testTracer{}
|
||||
|
||||
// Test the case where there *is* a Span in the Context.
|
||||
{
|
||||
parentSpan := &testSpan{}
|
||||
parentCtx := ContextWithSpan(context.Background(), parentSpan)
|
||||
childSpan, childCtx := startSpanFromContextWithTracer(parentCtx, testTracer, "child")
|
||||
if !childSpan.Context().(testSpanContext).HasParent {
|
||||
t.Errorf("Failed to find parent: %v", childSpan)
|
||||
}
|
||||
if !childSpan.(testSpan).Equal(SpanFromContext(childCtx)) {
|
||||
t.Errorf("Unable to find child span in context: %v", childCtx)
|
||||
}
|
||||
}
|
||||
|
||||
// Test the case where there *is not* a Span in the Context.
|
||||
{
|
||||
emptyCtx := context.Background()
|
||||
childSpan, childCtx := startSpanFromContextWithTracer(emptyCtx, testTracer, "child")
|
||||
if childSpan.Context().(testSpanContext).HasParent {
|
||||
t.Errorf("Should not have found parent: %v", childSpan)
|
||||
}
|
||||
if !childSpan.(testSpan).Equal(SpanFromContext(childCtx)) {
|
||||
t.Errorf("Unable to find child span in context: %v", childCtx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartSpanFromContextOptions(t *testing.T) {
|
||||
testTracer := testTracer{}
|
||||
|
||||
// Test options are passed to tracer
|
||||
|
||||
startTime := time.Now().Add(-10 * time.Second) // ten seconds ago
|
||||
span, ctx := startSpanFromContextWithTracer(
|
||||
context.Background(), testTracer, "parent", StartTime(startTime), Tag{"component", "test"})
|
||||
|
||||
assert.Equal(t, "test", span.(testSpan).Tags["component"])
|
||||
assert.Equal(t, startTime, span.(testSpan).StartTime)
|
||||
|
||||
// Test it also works for a child span
|
||||
|
||||
childStartTime := startTime.Add(3 * time.Second)
|
||||
childSpan, _ := startSpanFromContextWithTracer(
|
||||
ctx, testTracer, "child", StartTime(childStartTime))
|
||||
|
||||
assert.Equal(t, childSpan.(testSpan).Tags["component"], nil)
|
||||
assert.Equal(t, childSpan.(testSpan).StartTime, childStartTime)
|
||||
}
|
245
vendor/github.com/opentracing/opentracing-go/log/field.go
generated
vendored
Normal file
245
vendor/github.com/opentracing/opentracing-go/log/field.go
generated
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type fieldType int
|
||||
|
||||
const (
|
||||
stringType fieldType = iota
|
||||
boolType
|
||||
intType
|
||||
int32Type
|
||||
uint32Type
|
||||
int64Type
|
||||
uint64Type
|
||||
float32Type
|
||||
float64Type
|
||||
errorType
|
||||
objectType
|
||||
lazyLoggerType
|
||||
)
|
||||
|
||||
// Field instances are constructed via LogBool, LogString, and so on.
|
||||
// Tracing implementations may then handle them via the Field.Marshal
|
||||
// method.
|
||||
//
|
||||
// "heavily influenced by" (i.e., partially stolen from)
|
||||
// https://github.com/uber-go/zap
|
||||
type Field struct {
|
||||
key string
|
||||
fieldType fieldType
|
||||
numericVal int64
|
||||
stringVal string
|
||||
interfaceVal interface{}
|
||||
}
|
||||
|
||||
// String adds a string-valued key:value pair to a Span.LogFields() record
|
||||
func String(key, val string) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: stringType,
|
||||
stringVal: val,
|
||||
}
|
||||
}
|
||||
|
||||
// Bool adds a bool-valued key:value pair to a Span.LogFields() record
|
||||
func Bool(key string, val bool) Field {
|
||||
var numericVal int64
|
||||
if val {
|
||||
numericVal = 1
|
||||
}
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: boolType,
|
||||
numericVal: numericVal,
|
||||
}
|
||||
}
|
||||
|
||||
// Int adds an int-valued key:value pair to a Span.LogFields() record
|
||||
func Int(key string, val int) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: intType,
|
||||
numericVal: int64(val),
|
||||
}
|
||||
}
|
||||
|
||||
// Int32 adds an int32-valued key:value pair to a Span.LogFields() record
|
||||
func Int32(key string, val int32) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: int32Type,
|
||||
numericVal: int64(val),
|
||||
}
|
||||
}
|
||||
|
||||
// Int64 adds an int64-valued key:value pair to a Span.LogFields() record
|
||||
func Int64(key string, val int64) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: int64Type,
|
||||
numericVal: val,
|
||||
}
|
||||
}
|
||||
|
||||
// Uint32 adds a uint32-valued key:value pair to a Span.LogFields() record
|
||||
func Uint32(key string, val uint32) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: uint32Type,
|
||||
numericVal: int64(val),
|
||||
}
|
||||
}
|
||||
|
||||
// Uint64 adds a uint64-valued key:value pair to a Span.LogFields() record
|
||||
func Uint64(key string, val uint64) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: uint64Type,
|
||||
numericVal: int64(val),
|
||||
}
|
||||
}
|
||||
|
||||
// Float32 adds a float32-valued key:value pair to a Span.LogFields() record
|
||||
func Float32(key string, val float32) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: float32Type,
|
||||
numericVal: int64(math.Float32bits(val)),
|
||||
}
|
||||
}
|
||||
|
||||
// Float64 adds a float64-valued key:value pair to a Span.LogFields() record
|
||||
func Float64(key string, val float64) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: float64Type,
|
||||
numericVal: int64(math.Float64bits(val)),
|
||||
}
|
||||
}
|
||||
|
||||
// Error adds an error with the key "error" to a Span.LogFields() record
|
||||
func Error(err error) Field {
|
||||
return Field{
|
||||
key: "error",
|
||||
fieldType: errorType,
|
||||
interfaceVal: err,
|
||||
}
|
||||
}
|
||||
|
||||
// Object adds an object-valued key:value pair to a Span.LogFields() record
|
||||
func Object(key string, obj interface{}) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: objectType,
|
||||
interfaceVal: obj,
|
||||
}
|
||||
}
|
||||
|
||||
// LazyLogger allows for user-defined, late-bound logging of arbitrary data
|
||||
type LazyLogger func(fv Encoder)
|
||||
|
||||
// Lazy adds a LazyLogger to a Span.LogFields() record; the tracing
|
||||
// implementation will call the LazyLogger function at an indefinite time in
|
||||
// the future (after Lazy() returns).
|
||||
func Lazy(ll LazyLogger) Field {
|
||||
return Field{
|
||||
fieldType: lazyLoggerType,
|
||||
interfaceVal: ll,
|
||||
}
|
||||
}
|
||||
|
||||
// Encoder allows access to the contents of a Field (via a call to
|
||||
// Field.Marshal).
|
||||
//
|
||||
// Tracer implementations typically provide an implementation of Encoder;
|
||||
// OpenTracing callers typically do not need to concern themselves with it.
|
||||
type Encoder interface {
|
||||
EmitString(key, value string)
|
||||
EmitBool(key string, value bool)
|
||||
EmitInt(key string, value int)
|
||||
EmitInt32(key string, value int32)
|
||||
EmitInt64(key string, value int64)
|
||||
EmitUint32(key string, value uint32)
|
||||
EmitUint64(key string, value uint64)
|
||||
EmitFloat32(key string, value float32)
|
||||
EmitFloat64(key string, value float64)
|
||||
EmitObject(key string, value interface{})
|
||||
EmitLazyLogger(value LazyLogger)
|
||||
}
|
||||
|
||||
// Marshal passes a Field instance through to the appropriate
|
||||
// field-type-specific method of an Encoder.
|
||||
func (lf Field) Marshal(visitor Encoder) {
|
||||
switch lf.fieldType {
|
||||
case stringType:
|
||||
visitor.EmitString(lf.key, lf.stringVal)
|
||||
case boolType:
|
||||
visitor.EmitBool(lf.key, lf.numericVal != 0)
|
||||
case intType:
|
||||
visitor.EmitInt(lf.key, int(lf.numericVal))
|
||||
case int32Type:
|
||||
visitor.EmitInt32(lf.key, int32(lf.numericVal))
|
||||
case int64Type:
|
||||
visitor.EmitInt64(lf.key, int64(lf.numericVal))
|
||||
case uint32Type:
|
||||
visitor.EmitUint32(lf.key, uint32(lf.numericVal))
|
||||
case uint64Type:
|
||||
visitor.EmitUint64(lf.key, uint64(lf.numericVal))
|
||||
case float32Type:
|
||||
visitor.EmitFloat32(lf.key, math.Float32frombits(uint32(lf.numericVal)))
|
||||
case float64Type:
|
||||
visitor.EmitFloat64(lf.key, math.Float64frombits(uint64(lf.numericVal)))
|
||||
case errorType:
|
||||
if err, ok := lf.interfaceVal.(error); ok {
|
||||
visitor.EmitString(lf.key, err.Error())
|
||||
} else {
|
||||
visitor.EmitString(lf.key, "<nil>")
|
||||
}
|
||||
case objectType:
|
||||
visitor.EmitObject(lf.key, lf.interfaceVal)
|
||||
case lazyLoggerType:
|
||||
visitor.EmitLazyLogger(lf.interfaceVal.(LazyLogger))
|
||||
}
|
||||
}
|
||||
|
||||
// Key returns the field's key.
|
||||
func (lf Field) Key() string {
|
||||
return lf.key
|
||||
}
|
||||
|
||||
// Value returns the field's value as interface{}.
|
||||
func (lf Field) Value() interface{} {
|
||||
switch lf.fieldType {
|
||||
case stringType:
|
||||
return lf.stringVal
|
||||
case boolType:
|
||||
return lf.numericVal != 0
|
||||
case intType:
|
||||
return int(lf.numericVal)
|
||||
case int32Type:
|
||||
return int32(lf.numericVal)
|
||||
case int64Type:
|
||||
return int64(lf.numericVal)
|
||||
case uint32Type:
|
||||
return uint32(lf.numericVal)
|
||||
case uint64Type:
|
||||
return uint64(lf.numericVal)
|
||||
case float32Type:
|
||||
return math.Float32frombits(uint32(lf.numericVal))
|
||||
case float64Type:
|
||||
return math.Float64frombits(uint64(lf.numericVal))
|
||||
case errorType, objectType, lazyLoggerType:
|
||||
return lf.interfaceVal
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of the key and value.
|
||||
func (lf Field) String() string {
|
||||
return fmt.Sprint(lf.key, ":", lf.Value())
|
||||
}
|
39
vendor/github.com/opentracing/opentracing-go/log/field_test.go
generated
vendored
Normal file
39
vendor/github.com/opentracing/opentracing-go/log/field_test.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFieldString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
field Field
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
field: String("key", "value"),
|
||||
expected: "key:value",
|
||||
},
|
||||
{
|
||||
field: Bool("key", true),
|
||||
expected: "key:true",
|
||||
},
|
||||
{
|
||||
field: Int("key", 5),
|
||||
expected: "key:5",
|
||||
},
|
||||
{
|
||||
field: Error(fmt.Errorf("err msg")),
|
||||
expected: "error:err msg",
|
||||
},
|
||||
{
|
||||
field: Error(nil),
|
||||
expected: "error:<nil>",
|
||||
},
|
||||
}
|
||||
for i, tc := range testCases {
|
||||
if str := tc.field.String(); str != tc.expected {
|
||||
t.Errorf("%d: expected '%s', got '%s'", i, tc.expected, str)
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user