GraphQL (part 2): Model changes and resolvers (#19486)
This PR adds the necessary resolvers to support the graphQL front-end. Some additional methods need to be added to the model structs to support this. Comments have been made to clarify their purpose. There is a slight duplication of code in the api layer. But eventually that's going to go away when we move away from the REST API entirely. The schema is in api4/schema.graphqls and gets compiled into the binary as an asset. The GraphiQL editor url is at GET /api/v5/graphql. Everything is behind the feature flag MM_FEATUREFLAGS_GRAPHQL. ```release-note NONE ```
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
88968f9e17
Коммит
3f52bd197a
15
api4/api.go
15
api4/api.go
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
graphql "github.com/graph-gophers/graphql-go"
|
||||||
_ "github.com/mattermost/go-i18n/i18n"
|
_ "github.com/mattermost/go-i18n/i18n"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/app"
|
"github.com/mattermost/mattermost-server/v6/app"
|
||||||
@@ -15,8 +16,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Routes struct {
|
type Routes struct {
|
||||||
Root *mux.Router // ''
|
Root *mux.Router // ''
|
||||||
APIRoot *mux.Router // 'api/v4'
|
APIRoot *mux.Router // 'api/v4'
|
||||||
|
APIRoot5 *mux.Router // 'api/v5'
|
||||||
|
|
||||||
Users *mux.Router // 'api/v4/users'
|
Users *mux.Router // 'api/v4/users'
|
||||||
User *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}'
|
User *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}'
|
||||||
@@ -136,10 +138,11 @@ type Routes struct {
|
|||||||
|
|
||||||
type API struct {
|
type API struct {
|
||||||
srv *app.Server
|
srv *app.Server
|
||||||
|
schema *graphql.Schema
|
||||||
BaseRoutes *Routes
|
BaseRoutes *Routes
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init(srv *app.Server) *API {
|
func Init(srv *app.Server) (*API, error) {
|
||||||
api := &API{
|
api := &API{
|
||||||
srv: srv,
|
srv: srv,
|
||||||
BaseRoutes: &Routes{},
|
BaseRoutes: &Routes{},
|
||||||
@@ -147,6 +150,7 @@ func Init(srv *app.Server) *API {
|
|||||||
|
|
||||||
api.BaseRoutes.Root = srv.Router
|
api.BaseRoutes.Root = srv.Router
|
||||||
api.BaseRoutes.APIRoot = srv.Router.PathPrefix(model.APIURLSuffix).Subrouter()
|
api.BaseRoutes.APIRoot = srv.Router.PathPrefix(model.APIURLSuffix).Subrouter()
|
||||||
|
api.BaseRoutes.APIRoot5 = srv.Router.PathPrefix(model.APIURLSuffixV5).Subrouter()
|
||||||
|
|
||||||
api.BaseRoutes.Users = api.BaseRoutes.APIRoot.PathPrefix("/users").Subrouter()
|
api.BaseRoutes.Users = api.BaseRoutes.APIRoot.PathPrefix("/users").Subrouter()
|
||||||
api.BaseRoutes.User = api.BaseRoutes.APIRoot.PathPrefix("/users/{user_id:[A-Za-z0-9]+}").Subrouter()
|
api.BaseRoutes.User = api.BaseRoutes.APIRoot.PathPrefix("/users/{user_id:[A-Za-z0-9]+}").Subrouter()
|
||||||
@@ -292,12 +296,15 @@ func Init(srv *app.Server) *API {
|
|||||||
api.InitSharedChannels()
|
api.InitSharedChannels()
|
||||||
api.InitPermissions()
|
api.InitPermissions()
|
||||||
api.InitExport()
|
api.InitExport()
|
||||||
|
if err := api.InitGraphQL(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404))
|
srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404))
|
||||||
|
|
||||||
InitLocal(srv)
|
InitLocal(srv)
|
||||||
|
|
||||||
return api
|
return api, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitLocal(srv *app.Server) *API {
|
func InitLocal(srv *app.Server) *API {
|
||||||
|
|||||||
@@ -4,9 +4,12 @@
|
|||||||
package api4
|
package api4
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
@@ -19,6 +22,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
graphql "github.com/graph-gophers/graphql-go"
|
||||||
s3 "github.com/minio/minio-go/v7"
|
s3 "github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -45,6 +49,7 @@ type TestHelper struct {
|
|||||||
|
|
||||||
Context *request.Context
|
Context *request.Context
|
||||||
Client *model.Client4
|
Client *model.Client4
|
||||||
|
GraphQLClient *graphQLClient
|
||||||
BasicUser *model.User
|
BasicUser *model.User
|
||||||
BasicUser2 *model.User
|
BasicUser2 *model.User
|
||||||
TeamAdminUser *model.User
|
TeamAdminUser *model.User
|
||||||
@@ -191,6 +196,7 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
|
|||||||
}
|
}
|
||||||
|
|
||||||
th.Client = th.CreateClient()
|
th.Client = th.CreateClient()
|
||||||
|
th.GraphQLClient = newGraphQLClient(fmt.Sprintf("http://localhost:%v", th.App.Srv().ListenAddr.Port))
|
||||||
th.SystemAdminClient = th.CreateClient()
|
th.SystemAdminClient = th.CreateClient()
|
||||||
th.SystemManagerClient = th.CreateClient()
|
th.SystemManagerClient = th.CreateClient()
|
||||||
|
|
||||||
@@ -374,6 +380,13 @@ func (th *TestHelper) TearDown() {
|
|||||||
th.ShutdownApp()
|
th.ShutdownApp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func closeBody(r *http.Response) {
|
||||||
|
if r.Body != nil {
|
||||||
|
_, _ = io.Copy(ioutil.Discard, r.Body)
|
||||||
|
_ = r.Body.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var initBasicOnce sync.Once
|
var initBasicOnce sync.Once
|
||||||
var userCache struct {
|
var userCache struct {
|
||||||
SystemAdminUser *model.User
|
SystemAdminUser *model.User
|
||||||
@@ -768,10 +781,16 @@ func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel {
|
|||||||
|
|
||||||
func (th *TestHelper) LoginBasic() {
|
func (th *TestHelper) LoginBasic() {
|
||||||
th.LoginBasicWithClient(th.Client)
|
th.LoginBasicWithClient(th.Client)
|
||||||
|
if os.Getenv("MM_FEATUREFLAGS_GRAPHQL") == "true" {
|
||||||
|
th.LoginBasicWithGraphQL()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (th *TestHelper) LoginBasic2() {
|
func (th *TestHelper) LoginBasic2() {
|
||||||
th.LoginBasic2WithClient(th.Client)
|
th.LoginBasic2WithClient(th.Client)
|
||||||
|
if os.Getenv("MM_FEATUREFLAGS_GRAPHQL") == "true" {
|
||||||
|
th.LoginBasicWithGraphQL()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (th *TestHelper) LoginTeamAdmin() {
|
func (th *TestHelper) LoginTeamAdmin() {
|
||||||
@@ -793,6 +812,13 @@ func (th *TestHelper) LoginBasicWithClient(client *model.Client4) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) LoginBasicWithGraphQL() {
|
||||||
|
_, _, err := th.GraphQLClient.login(th.BasicUser.Email, th.BasicUser.Password)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (th *TestHelper) LoginBasic2WithClient(client *model.Client4) {
|
func (th *TestHelper) LoginBasic2WithClient(client *model.Client4) {
|
||||||
_, _, err := client.Login(th.BasicUser2.Email, th.BasicUser2.Password)
|
_, _, err := client.Login(th.BasicUser2.Email, th.BasicUser2.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1251,3 +1277,22 @@ func (th *TestHelper) SetupScheme(scope string) *model.Scheme {
|
|||||||
}
|
}
|
||||||
return scheme
|
return scheme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (th *TestHelper) MakeGraphQLRequest(input *graphQLInput) (*graphql.Response, error) {
|
||||||
|
url := fmt.Sprintf("http://localhost:%v", th.App.Srv().ListenAddr.Port) + model.APIURLSuffixV5 + "/graphql"
|
||||||
|
|
||||||
|
buf, err := json.Marshal(input)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.GraphQLClient.doAPIRequest("POST", url, bytes.NewReader(buf), map[string]string{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer closeBody(resp)
|
||||||
|
|
||||||
|
var gqlResp *graphql.Response
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&gqlResp)
|
||||||
|
return gqlResp, err
|
||||||
|
}
|
||||||
|
|||||||
165
api4/graphql.go
Обычный файл
165
api4/graphql.go
Обычный файл
@@ -0,0 +1,165 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
graphql "github.com/graph-gophers/graphql-go"
|
||||||
|
gqlerrors "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type graphQLInput struct {
|
||||||
|
Query string `json:"query"`
|
||||||
|
OperationName string `json:"operationName"`
|
||||||
|
Variables map[string]interface{} `json:"variables"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed schema.graphqls
|
||||||
|
var schemaRaw string
|
||||||
|
|
||||||
|
func (api *API) InitGraphQL() error {
|
||||||
|
// Guard with a feature flag.
|
||||||
|
if !api.srv.Config().FeatureFlags.GraphQL {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
opts := []graphql.SchemaOpt{
|
||||||
|
graphql.UseFieldResolvers(),
|
||||||
|
graphql.Logger(mlog.NewGraphQLLogger(api.srv.Log)),
|
||||||
|
graphql.MaxParallelism(5),
|
||||||
|
}
|
||||||
|
|
||||||
|
if isProd() {
|
||||||
|
opts = append(opts,
|
||||||
|
// MaxDepth cannot be moved as a general param
|
||||||
|
// because otherwise introspection also doesn't work
|
||||||
|
// with just a depth of 4.
|
||||||
|
graphql.MaxDepth(4),
|
||||||
|
graphql.DisableIntrospection(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
api.schema, err = graphql.ParseSchema(schemaRaw, &resolver{}, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
api.BaseRoutes.APIRoot5.Handle("/graphql", api.APIHandlerTrustRequester(graphiQL)).Methods("GET")
|
||||||
|
api.BaseRoutes.APIRoot5.Handle("/graphql", api.APISessionRequired(api.graphQL)).Methods("POST")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unique type to hold our context.
|
||||||
|
type ctxKey struct{}
|
||||||
|
|
||||||
|
func (api *API) graphQL(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
var response *graphql.Response
|
||||||
|
defer func() {
|
||||||
|
if response != nil {
|
||||||
|
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||||
|
mlog.Warn("Error while writing response", mlog.Err(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Limit bodies to 100KiB.
|
||||||
|
// We need to enforce a lower limit than the file upload size,
|
||||||
|
// to prevent the library doing unnecessary parsing.
|
||||||
|
r.Body = http.MaxBytesReader(w, r.Body, 102400)
|
||||||
|
|
||||||
|
var params graphQLInput
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
|
||||||
|
err2 := gqlerrors.Errorf("invalid request body: %v", err)
|
||||||
|
response = &graphql.Response{Errors: []*gqlerrors.QueryError{err2}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isProd() && params.OperationName == "" {
|
||||||
|
err2 := gqlerrors.Errorf("operation name not passed")
|
||||||
|
response = &graphql.Response{Errors: []*gqlerrors.QueryError{err2}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the context with required info.
|
||||||
|
reqCtx := r.Context()
|
||||||
|
reqCtx = context.WithValue(reqCtx, ctxKey{}, c)
|
||||||
|
|
||||||
|
response = api.schema.Exec(reqCtx,
|
||||||
|
params.Query,
|
||||||
|
params.OperationName,
|
||||||
|
params.Variables)
|
||||||
|
|
||||||
|
if len(response.Errors) > 0 {
|
||||||
|
logFunc := mlog.Error
|
||||||
|
for _, gqlErr := range response.Errors {
|
||||||
|
if gqlErr.Err != nil {
|
||||||
|
if appErr, ok := gqlErr.Err.(*model.AppError); ok && appErr.StatusCode < http.StatusInternalServerError {
|
||||||
|
logFunc = mlog.Debug
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logFunc("Error executing request", mlog.String("operation", params.OperationName),
|
||||||
|
mlog.Array("errors", response.Errors))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func graphiQL(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
w.Write(graphiqlPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var graphiqlPage = []byte(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>GraphiQL editor | Mattermost</title>
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.min.css" rel="stylesheet" />
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.production.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.min.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("/api/v5/graphql", {
|
||||||
|
method: "post",
|
||||||
|
body: JSON.stringify(graphQLParams),
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
}).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>
|
||||||
|
`)
|
||||||
|
|
||||||
|
// isProd is a helper function to apply prod-specific graphQL validations.
|
||||||
|
func isProd() bool {
|
||||||
|
return model.BuildNumber != "dev"
|
||||||
|
}
|
||||||
87
api4/graphql_client.go
Обычный файл
87
api4/graphql_client.go
Обычный файл
@@ -0,0 +1,87 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// graphQLClient is an internal test client to run the tests.
|
||||||
|
// When the API matures, we will expose it to the model package.
|
||||||
|
type graphQLClient struct {
|
||||||
|
URL string // The location of the server, for example "http://localhost:8065"
|
||||||
|
APIURL string // The api location of the server, for example "http://localhost:8065/api/v4"
|
||||||
|
httpClient *http.Client // The http client
|
||||||
|
authToken string
|
||||||
|
authType string
|
||||||
|
httpHeader map[string]string // Headers to be copied over for each request
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGraphQLClient(url string) *graphQLClient {
|
||||||
|
url = strings.TrimRight(url, "/")
|
||||||
|
return &graphQLClient{url, url + model.APIURLSuffix, &http.Client{}, "", "", map[string]string{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *graphQLClient) login(loginId string, password string) (*model.User, *model.Response, error) {
|
||||||
|
m := make(map[string]string)
|
||||||
|
m["login_id"] = loginId
|
||||||
|
m["password"] = password
|
||||||
|
|
||||||
|
r, err := c.doAPIRequest(http.MethodPost, c.APIURL+"/users/login", strings.NewReader(model.MapToJSON(m)), map[string]string{model.HeaderEtagClient: ""})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.BuildResponse(r), err
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
c.authToken = r.Header.Get(model.HeaderToken)
|
||||||
|
c.authType = model.HeaderBearer
|
||||||
|
|
||||||
|
var user model.User
|
||||||
|
if jsonErr := json.NewDecoder(r.Body).Decode(&user); jsonErr != nil {
|
||||||
|
return nil, nil, model.NewAppError("login", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return &user, model.BuildResponse(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *graphQLClient) doAPIRequest(method, url string, data io.Reader, headers map[string]string) (*http.Response, error) {
|
||||||
|
rq, err := c.prepareRequest(method, url, data, headers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rp, err := c.httpClient.Do(rq)
|
||||||
|
if err != nil {
|
||||||
|
return rp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *graphQLClient) prepareRequest(method, url string, data io.Reader, headers map[string]string) (*http.Request, error) {
|
||||||
|
rq, err := http.NewRequest(method, url, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range headers {
|
||||||
|
rq.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.authToken != "" {
|
||||||
|
rq.Header.Set(model.HeaderAuth, c.authType+" "+c.authToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.httpHeader != nil && len(c.httpHeader) > 0 {
|
||||||
|
for k, v := range c.httpHeader {
|
||||||
|
rq.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rq, nil
|
||||||
|
}
|
||||||
34
api4/graphql_test.go
Обычный файл
34
api4/graphql_test.go
Обычный файл
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGraphQLPayload(t *testing.T) {
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
|
||||||
|
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
largeString := strings.Repeat("hello", 204800)
|
||||||
|
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "config",
|
||||||
|
Query: largeString,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 1)
|
||||||
|
// The actual error isn't exposed. We compare the string
|
||||||
|
// to not confuse with other errors.
|
||||||
|
require.Contains(t, resp.Errors[0].Message, "request body too large")
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v6/model"
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
)
|
)
|
||||||
@@ -68,7 +69,8 @@ func TestAPIHandlersWithGzip(t *testing.T) {
|
|||||||
th := Setup(t)
|
th := Setup(t)
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
api := Init(th.Server)
|
api, err := Init(th.Server)
|
||||||
|
require.NoError(t, err)
|
||||||
session, _ := th.App.GetSession(th.Client.AuthToken)
|
session, _ := th.App.GetSession(th.Client.AuthToken)
|
||||||
|
|
||||||
t.Run("with WebserverMode == \"gzip\"", func(t *testing.T) {
|
t.Run("with WebserverMode == \"gzip\"", func(t *testing.T) {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package api4
|
package api4
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -608,13 +607,6 @@ func TestGetAuthorizedOAuthAppsForUser(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeBody(r *http.Response) {
|
|
||||||
if r != nil && r.Body != nil {
|
|
||||||
ioutil.ReadAll(r.Body)
|
|
||||||
r.Body.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNilAuthorizeOAuthApp(t *testing.T) {
|
func TestNilAuthorizeOAuthApp(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := Setup(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|||||||
@@ -602,11 +602,12 @@ func TestCreatePostCheckOnlineStatus(t *testing.T) {
|
|||||||
th := Setup(t).InitBasic()
|
th := Setup(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
api := Init(th.Server)
|
api, err := Init(th.Server)
|
||||||
|
require.NoError(t, err)
|
||||||
session, _ := th.App.GetSession(th.Client.AuthToken)
|
session, _ := th.App.GetSession(th.Client.AuthToken)
|
||||||
|
|
||||||
cli := th.CreateClient()
|
cli := th.CreateClient()
|
||||||
_, _, err := cli.Login(th.BasicUser2.Username, th.BasicUser2.Password)
|
_, _, err = cli.Login(th.BasicUser2.Username, th.BasicUser2.Password)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
wsClient, err := th.CreateWebSocketClientWithClient(cli)
|
wsClient, err := th.CreateWebSocketClientWithClient(cli)
|
||||||
|
|||||||
286
api4/resolver.go
Обычный файл
286
api4/resolver.go
Обычный файл
@@ -0,0 +1,286 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/app"
|
||||||
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v6/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cursorPrefix is used to categorize objects
|
||||||
|
// sent in a cursor. The type is prepended
|
||||||
|
// to the string with a - to find which
|
||||||
|
// object the id belongs to.
|
||||||
|
//
|
||||||
|
// And after the type is extracted, object
|
||||||
|
// specific logic can be applied to extract the id.
|
||||||
|
type cursorPrefix string
|
||||||
|
|
||||||
|
const (
|
||||||
|
channelMemberCursorPrefix cursorPrefix = "channelMember"
|
||||||
|
channelCursorPrefix cursorPrefix = "channel"
|
||||||
|
)
|
||||||
|
|
||||||
|
type resolver struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getChannelsForTeamForUser
|
||||||
|
func (r *resolver) Channels(ctx context.Context, args struct {
|
||||||
|
TeamID string
|
||||||
|
UserID string
|
||||||
|
IncludeDeleted bool
|
||||||
|
LastDeleteAt float64
|
||||||
|
LastUpdateAt float64
|
||||||
|
First int32
|
||||||
|
After string
|
||||||
|
}) ([]*channel, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.UserID == model.Me {
|
||||||
|
args.UserID = c.AppContext.Session().UserId
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), args.UserID) {
|
||||||
|
c.SetPermissionError(model.PermissionEditOtherUsers)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.TeamID != "" && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), args.TeamID, model.PermissionViewTeam) {
|
||||||
|
c.SetPermissionError(model.PermissionViewTeam)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := int(args.First)
|
||||||
|
// ensure args.First limit
|
||||||
|
if limit == 0 {
|
||||||
|
limit = web.PerPageDefault
|
||||||
|
} else if limit > web.PerPageMaximum {
|
||||||
|
return nil, fmt.Errorf("first parameter %d higher than allowed maximum of %d", limit, web.PerPageMaximum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure args.After format
|
||||||
|
var afterChannel string
|
||||||
|
var ok bool
|
||||||
|
if args.After != "" {
|
||||||
|
afterChannel, ok = parseChannelCursor(args.After)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("after cursor not in the correct format: %s", args.After)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: convert this to a streaming API.
|
||||||
|
channels, appErr := c.App.GetChannelsForTeamForUserWithCursor(args.TeamID, args.UserID, &model.ChannelSearchOpts{
|
||||||
|
IncludeDeleted: args.IncludeDeleted,
|
||||||
|
LastDeleteAt: int(args.LastDeleteAt),
|
||||||
|
LastUpdateAt: int(args.LastUpdateAt),
|
||||||
|
PerPage: model.NewInt(limit),
|
||||||
|
}, afterChannel)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
appErr = c.App.FillInChannelsProps(channels)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return postProcessChannels(c, channels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getUser
|
||||||
|
func (r *resolver) User(ctx context.Context, args struct{ ID string }) (*user, error) {
|
||||||
|
return getGraphQLUser(ctx, args.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getClientConfig
|
||||||
|
func (r *resolver) Config(ctx context.Context) (model.StringMap, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.AppContext.Session().UserId == "" {
|
||||||
|
return c.App.LimitedClientConfigWithComputed(), nil
|
||||||
|
}
|
||||||
|
return c.App.ClientConfigWithComputed(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getClientLicense
|
||||||
|
func (r *resolver) License(ctx context.Context) (model.StringMap, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionReadLicenseInformation) {
|
||||||
|
return c.App.Srv().ClientLicense(), nil
|
||||||
|
}
|
||||||
|
return c.App.Srv().GetSanitizedClientLicense(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getTeamMembersForUser for teamID=""
|
||||||
|
// and api4.getTeamMember for teamID != ""
|
||||||
|
func (r *resolver) TeamMembers(ctx context.Context, args struct {
|
||||||
|
UserID string
|
||||||
|
TeamID string
|
||||||
|
}) ([]*teamMember, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.UserID == model.Me {
|
||||||
|
args.UserID = c.AppContext.Session().UserId
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), args.UserID) && !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionReadOtherUsersTeams) {
|
||||||
|
c.SetPermissionError(model.PermissionReadOtherUsersTeams)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
canSee, appErr := c.App.UserCanSeeOtherUser(c.AppContext.Session().UserId, args.UserID)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if !canSee {
|
||||||
|
c.SetPermissionError(model.PermissionViewMembers)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.TeamID != "" {
|
||||||
|
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), args.TeamID, model.PermissionViewTeam) {
|
||||||
|
c.SetPermissionError(model.PermissionViewTeam)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
tm, appErr2 := c.App.GetTeamMember(args.TeamID, args.UserID)
|
||||||
|
if appErr2 != nil {
|
||||||
|
return nil, appErr2
|
||||||
|
}
|
||||||
|
|
||||||
|
return []*teamMember{{*tm}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
members, appErr := c.App.GetTeamMembersForUser(args.UserID)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to the wrapper format.
|
||||||
|
res := make([]*teamMember, 0, len(members))
|
||||||
|
for _, tm := range members {
|
||||||
|
res = append(res, &teamMember{*tm})
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*resolver) ChannelsLeft(ctx context.Context, args struct {
|
||||||
|
UserID string
|
||||||
|
Since float64
|
||||||
|
}) ([]string, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.UserID == model.Me {
|
||||||
|
args.UserID = c.AppContext.Session().UserId
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), args.UserID) {
|
||||||
|
c.SetPermissionError(model.PermissionEditOtherUsers)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.App.Srv().Store.ChannelMemberHistory().GetChannelsLeftSince(args.UserID, int64(args.Since))
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getChannelMember
|
||||||
|
func (*resolver) ChannelMembers(ctx context.Context, args struct {
|
||||||
|
UserID string
|
||||||
|
ChannelID string
|
||||||
|
First int32
|
||||||
|
After string
|
||||||
|
LastUpdateAt float64
|
||||||
|
}) ([]*channelMember, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.UserID == model.Me {
|
||||||
|
args.UserID = c.AppContext.Session().UserId
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a single channel
|
||||||
|
if args.ChannelID != "" {
|
||||||
|
if !c.App.SessionHasPermissionToChannel(*c.AppContext.Session(), args.ChannelID, model.PermissionReadChannel) {
|
||||||
|
c.SetPermissionError(model.PermissionReadChannel)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
member, appErr := c.App.GetChannelMember(app.WithMaster(context.Background()), args.ChannelID, args.UserID)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return []*channelMember{{*member}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), args.UserID) {
|
||||||
|
c.SetPermissionError(model.PermissionEditOtherUsers)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := int(args.First)
|
||||||
|
// ensure args.First limit
|
||||||
|
if limit == 0 {
|
||||||
|
limit = web.PerPageDefault
|
||||||
|
} else if limit > web.PerPageMaximum {
|
||||||
|
return nil, fmt.Errorf("first parameter %d higher than allowed maximum of %d", limit, web.PerPageMaximum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure args.After format
|
||||||
|
var afterChannel, afterUser string
|
||||||
|
var ok bool
|
||||||
|
if args.After != "" {
|
||||||
|
afterChannel, afterUser, ok = parseChannelMemberCursor(args.After)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("after cursor not in the correct format: %s", args.After)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
members, err := c.App.Srv().Store.Channel().GetMembersForUserWithCursor(args.UserID, afterChannel, afterUser, limit, int(args.LastUpdateAt))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]*channelMember, 0, len(members))
|
||||||
|
for _, cm := range members {
|
||||||
|
res = append(res, &channelMember{cm})
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCtx extracts web.Context out of the usual request context.
|
||||||
|
// Kind of an anti-pattern, but there are lots of methods attached to *web.Context
|
||||||
|
// so we use it for now.
|
||||||
|
func getCtx(ctx context.Context) (*web.Context, error) {
|
||||||
|
c, ok := ctx.Value(ctxKey{}).(*web.Context)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no web.Context found in context")
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
140
api4/resolver_channel.go
Обычный файл
140
api4/resolver_channel.go
Обычный файл
@@ -0,0 +1,140 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v6/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
// channel is an internal graphQL wrapper struct to add resolver methods.
|
||||||
|
type channel struct {
|
||||||
|
model.Channel
|
||||||
|
PrettyDisplayName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getTeam
|
||||||
|
func (ch *channel) Team(ctx context.Context) (*model.Team, error) {
|
||||||
|
if ch.TeamId == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return getGraphQLTeam(ctx, ch.TeamId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *channel) Cursor() *string {
|
||||||
|
cursor := string(channelCursorPrefix) + "-" + ch.Id
|
||||||
|
encoded := base64.StdEncoding.EncodeToString([]byte(cursor))
|
||||||
|
return model.NewString(encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseChannelCursor(cursor string) (channelID string, ok bool) {
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(cursor)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(string(decoded), "-")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
if cursorPrefix(parts[0]) != channelCursorPrefix {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts[1], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func postProcessChannels(c *web.Context, channels []*model.Channel) ([]*channel, error) {
|
||||||
|
// This approach becomes effectively similar to a dataloader if the displayName computation
|
||||||
|
// were to be done at the field level per channel.
|
||||||
|
|
||||||
|
// Get DM/GM channelIDs
|
||||||
|
var channelIDs []string
|
||||||
|
for _, ch := range channels {
|
||||||
|
if ch.IsGroupOrDirect() {
|
||||||
|
channelIDs = append(channelIDs, ch.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pref *model.Preference
|
||||||
|
var userInfo map[string][]*model.User
|
||||||
|
var err error
|
||||||
|
var appErr *model.AppError
|
||||||
|
|
||||||
|
// Avoiding unnecessary queries unless necessary.
|
||||||
|
if len(channelIDs) > 0 {
|
||||||
|
userInfo, err = c.App.Srv().Store.Channel().GetMembersInfoByChannelIds(channelIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pref, appErr = c.App.GetPreferenceByCategoryAndNameForUser(c.AppContext.Session().UserId, "display_settings", "name_format")
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to the wrapper format.
|
||||||
|
res := make([]*channel, 0, len(channels))
|
||||||
|
for _, ch := range channels {
|
||||||
|
prettyName := ch.DisplayName
|
||||||
|
|
||||||
|
if ch.IsGroupOrDirect() {
|
||||||
|
// get users slice for channel id
|
||||||
|
users := userInfo[ch.Id]
|
||||||
|
if users == nil {
|
||||||
|
return nil, fmt.Errorf("user info not found for channel id: %s", ch.Id)
|
||||||
|
}
|
||||||
|
prettyName = getPrettyDNForUsers(pref.Value, users)
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, &channel{Channel: *ch, PrettyDisplayName: prettyName})
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrettyDNForUsers(displaySetting string, users []*model.User) string {
|
||||||
|
displayNames := make([]string, 0, len(users))
|
||||||
|
// TODO: optimize this logic.
|
||||||
|
// Name computation happens repeatedly for the same user from
|
||||||
|
// multiple channels.
|
||||||
|
for _, u := range users {
|
||||||
|
displayNames = append(displayNames, getPrettyDNForUser(displaySetting, u))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(displayNames)
|
||||||
|
return strings.Join(displayNames, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrettyDNForUser(displaySetting string, user *model.User) string {
|
||||||
|
var displayName string
|
||||||
|
switch displaySetting {
|
||||||
|
case "nickname_full_name":
|
||||||
|
displayName = user.Nickname
|
||||||
|
if displayName == "" {
|
||||||
|
displayName = user.GetFullName()
|
||||||
|
}
|
||||||
|
if displayName == "" {
|
||||||
|
displayName = user.Username
|
||||||
|
}
|
||||||
|
case "full_name":
|
||||||
|
displayName = user.GetFullName()
|
||||||
|
if displayName == "" {
|
||||||
|
displayName = user.Username
|
||||||
|
}
|
||||||
|
default: // the "username" case also falls under this one.
|
||||||
|
displayName = user.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
return displayName
|
||||||
|
}
|
||||||
113
api4/resolver_channel_member.go
Обычный файл
113
api4/resolver_channel_member.go
Обычный файл
@@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v6/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
// channelMember is an internal graphQL wrapper struct to add resolver methods.
|
||||||
|
type channelMember struct {
|
||||||
|
model.ChannelMember
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getUser
|
||||||
|
func (cm *channelMember) User(ctx context.Context) (*user, error) {
|
||||||
|
return getGraphQLUser(ctx, cm.UserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.Channel
|
||||||
|
func (cm *channelMember) Channel(ctx context.Context) (*channel, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, appErr := c.App.GetChannel(cm.ChannelId)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if channel.Type == model.ChannelTypeOpen {
|
||||||
|
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionReadPublicChannel) &&
|
||||||
|
!c.App.SessionHasPermissionToChannel(*c.AppContext.Session(), cm.ChannelId, model.PermissionReadChannel) {
|
||||||
|
c.SetPermissionError(model.PermissionReadPublicChannel)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !c.App.SessionHasPermissionToChannel(*c.AppContext.Session(), cm.ChannelId, model.PermissionReadChannel) {
|
||||||
|
c.SetPermissionError(model.PermissionReadChannel)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appErr = c.App.FillInChannelProps(channel)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := postProcessChannels(c, []*model.Channel{channel})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// A bit of defence-in-depth; can probably be removed after a deeper look.
|
||||||
|
if len(res) != 1 {
|
||||||
|
return nil, fmt.Errorf("postProcessChannels: incorrect number of channels returned %d", len(res))
|
||||||
|
}
|
||||||
|
return res[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *channelMember) Roles_(ctx context.Context) ([]*model.Role, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return getGraphQLRoles(c, strings.Fields(cm.Roles))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *channelMember) Cursor() *string {
|
||||||
|
cursor := string(channelMemberCursorPrefix) + "-" + cm.ChannelId + "-" + cm.UserId
|
||||||
|
encoded := base64.StdEncoding.EncodeToString([]byte(cursor))
|
||||||
|
return model.NewString(encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGraphQLRoles(c *web.Context, roleNames []string) ([]*model.Role, error) {
|
||||||
|
cleanedRoleNames, valid := model.CleanRoleNames(roleNames)
|
||||||
|
if !valid {
|
||||||
|
c.SetInvalidParam("rolename")
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
roles, appErr := c.App.GetRolesByNames(cleanedRoleNames)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return roles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseChannelMemberCursor(cursor string) (channelID, userID string, ok bool) {
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(cursor)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(string(decoded), "-")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
if cursorPrefix(parts[0]) != channelMemberCursorPrefix {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts[1], parts[2], true
|
||||||
|
}
|
||||||
355
api4/resolver_channel_member_test.go
Обычный файл
355
api4/resolver_channel_member_test.go
Обычный файл
@@ -0,0 +1,355 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGraphQLChannelMembers(t *testing.T) {
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
// Adding another team with more channels (public and private)
|
||||||
|
myTeam := th.CreateTeam()
|
||||||
|
ch1 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, myTeam.Id)
|
||||||
|
ch2 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypePrivate, myTeam.Id)
|
||||||
|
th.LinkUserToTeam(th.BasicUser, myTeam)
|
||||||
|
th.App.AddUserToChannel(th.BasicUser, ch1, false)
|
||||||
|
th.App.AddUserToChannel(th.BasicUser, ch2, false)
|
||||||
|
|
||||||
|
// Creating some msgcount
|
||||||
|
th.CreateMessagePostWithClient(th.Client, th.BasicChannel, "basic post")
|
||||||
|
th.CreateMessagePostWithClient(th.Client, ch1, "ch1 post")
|
||||||
|
|
||||||
|
var q struct {
|
||||||
|
ChannelMembers []struct {
|
||||||
|
Channel struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
CreateAt float64 `json:"createAt"`
|
||||||
|
UpdateAt float64 `json:"updateAt"`
|
||||||
|
Type model.ChannelType `json:"type"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Header string `json:"header"`
|
||||||
|
Purpose string `json:"purpose"`
|
||||||
|
Team struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
} `json:"team"`
|
||||||
|
} `json:"channel"`
|
||||||
|
User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
FirstName string `json:"firstName"`
|
||||||
|
LastName string `json:"lastName"`
|
||||||
|
NickName string `json:"nickname"`
|
||||||
|
} `json:"user"`
|
||||||
|
Roles []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"Name"`
|
||||||
|
Permissions []string `json:"permissions"`
|
||||||
|
SchemeManaged bool `json:"schemeManaged"`
|
||||||
|
BuiltIn bool `json:"builtIn"`
|
||||||
|
} `json:"roles"`
|
||||||
|
LastViewedAt float64 `json:"lastViewedAt"`
|
||||||
|
LastUpdateAt float64 `json:"lastUpdateAt"`
|
||||||
|
MsgCount float64 `json:"msgCount"`
|
||||||
|
MentionCount float64 `json:"mentionCount"`
|
||||||
|
MentionCountRoot float64 `json:"mentionCountRoot"`
|
||||||
|
NotifyProps model.StringMap `json:"notifyProps"`
|
||||||
|
SchemeGuest bool `json:"schemeGuest"`
|
||||||
|
SchemeUser bool `json:"schemeUser"`
|
||||||
|
SchemeAdmin bool `json:"schemeAdmin"`
|
||||||
|
Cursor string `json:"cursor"`
|
||||||
|
} `json:"channelMembers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("all", func(t *testing.T) {
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channelMembers",
|
||||||
|
Query: `
|
||||||
|
query channelMembers {
|
||||||
|
channelMembers(userId: "me") {
|
||||||
|
channel {
|
||||||
|
id
|
||||||
|
createAt
|
||||||
|
updateAt
|
||||||
|
type
|
||||||
|
displayName
|
||||||
|
name
|
||||||
|
header
|
||||||
|
team {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
email
|
||||||
|
}
|
||||||
|
msgCount
|
||||||
|
mentionCount
|
||||||
|
mentionCountRoot
|
||||||
|
schemeGuest
|
||||||
|
schemeUser
|
||||||
|
schemeAdmin
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.ChannelMembers, 9)
|
||||||
|
|
||||||
|
numPrivate := 0
|
||||||
|
numPublic := 0
|
||||||
|
numOffTopic := 0
|
||||||
|
numTownSquare := 0
|
||||||
|
for _, ch := range q.ChannelMembers {
|
||||||
|
assert.NotEmpty(t, ch.Channel.ID)
|
||||||
|
assert.NotEmpty(t, ch.Channel.Name)
|
||||||
|
assert.NotEmpty(t, ch.Channel.CreateAt)
|
||||||
|
assert.NotEmpty(t, ch.Channel.UpdateAt)
|
||||||
|
if ch.Channel.Type == model.ChannelTypeOpen {
|
||||||
|
numPublic++
|
||||||
|
} else if ch.Channel.Type == model.ChannelTypePrivate {
|
||||||
|
numPrivate++
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.Channel.DisplayName == "Off-Topic" {
|
||||||
|
numOffTopic++
|
||||||
|
} else if ch.Channel.DisplayName == "Town Square" {
|
||||||
|
numTownSquare++
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, th.BasicUser.Id, ch.User.ID)
|
||||||
|
assert.Equal(t, th.BasicUser.Username, ch.User.Username)
|
||||||
|
assert.Equal(t, th.BasicUser.Email, ch.User.Email)
|
||||||
|
|
||||||
|
assert.False(t, ch.SchemeGuest)
|
||||||
|
|
||||||
|
if ch.Channel.Team.ID == myTeam.Id {
|
||||||
|
assert.True(t, ch.SchemeAdmin)
|
||||||
|
} else {
|
||||||
|
assert.False(t, ch.SchemeAdmin)
|
||||||
|
}
|
||||||
|
assert.True(t, ch.SchemeUser)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, ch.Cursor)
|
||||||
|
|
||||||
|
switch ch.Channel.ID {
|
||||||
|
case th.BasicChannel.Id:
|
||||||
|
assert.Equal(t, float64(2), ch.MsgCount)
|
||||||
|
case ch1.Id:
|
||||||
|
assert.Equal(t, float64(1), ch.MsgCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, 2, numPrivate)
|
||||||
|
assert.Equal(t, 7, numPublic)
|
||||||
|
assert.Equal(t, 2, numOffTopic)
|
||||||
|
assert.Equal(t, 2, numTownSquare)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("user_perms", func(t *testing.T) {
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channelMembers",
|
||||||
|
Query: `
|
||||||
|
query channelMembers($user: String!) {
|
||||||
|
channelMembers(userId: $user) {
|
||||||
|
channel {
|
||||||
|
id
|
||||||
|
createAt
|
||||||
|
updateAt
|
||||||
|
}
|
||||||
|
msgCount
|
||||||
|
mentionCount
|
||||||
|
mentionCountRoot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"user": model.NewId(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pagination", func(t *testing.T) {
|
||||||
|
query := `query channelMembers($first: Int, $after: String = "") {
|
||||||
|
channelMembers(userId: "me", first: $first, after: $after) {
|
||||||
|
channel {
|
||||||
|
id
|
||||||
|
createAt
|
||||||
|
updateAt
|
||||||
|
type
|
||||||
|
displayName
|
||||||
|
name
|
||||||
|
header
|
||||||
|
}
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channelMembers",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"first": 4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.ChannelMembers, 4)
|
||||||
|
|
||||||
|
input = graphQLInput{
|
||||||
|
OperationName: "channelMembers",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"first": 4,
|
||||||
|
"after": q.ChannelMembers[3].Cursor,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.ChannelMembers, 4)
|
||||||
|
|
||||||
|
input = graphQLInput{
|
||||||
|
OperationName: "channelMembers",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"first": 4,
|
||||||
|
"after": q.ChannelMembers[3].Cursor,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.ChannelMembers, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("channel_filter", func(t *testing.T) {
|
||||||
|
query := `query channelMembers($channelId: String, $first: Int, $after: String = "") {
|
||||||
|
channelMembers(userId: "me", channelId: $channelId, first: $first, after: $after) {
|
||||||
|
channel {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channelMembers",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"channelId": ch1.Id,
|
||||||
|
"first": 4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.ChannelMembers, 1)
|
||||||
|
assert.Equal(t, q.ChannelMembers[0].Channel.ID, ch1.Id)
|
||||||
|
|
||||||
|
input = graphQLInput{
|
||||||
|
OperationName: "channelMembers",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"channelId": model.NewId(),
|
||||||
|
"first": 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UpdateAt", func(t *testing.T) {
|
||||||
|
query := `query channelMembers($first: Int, $after: String = "", $lastUpdateAt: Float) {
|
||||||
|
channelMembers(userId: "me", first: $first, after: $after, lastUpdateAt: $lastUpdateAt) {
|
||||||
|
channel {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
lastUpdateAt
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
now := model.GetMillis()
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channelMembers",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"first": 4,
|
||||||
|
"lastUpdateAt": float64(now),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
require.Len(t, q.ChannelMembers, 0)
|
||||||
|
|
||||||
|
// Create post to update the lastUpdateAt for the channel member.
|
||||||
|
th.CreateMessagePostWithClient(th.Client, th.BasicChannel, "another post")
|
||||||
|
|
||||||
|
input = graphQLInput{
|
||||||
|
OperationName: "channelMembers",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"first": 4,
|
||||||
|
"lastUpdateAt": float64(now),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
require.Len(t, q.ChannelMembers, 1)
|
||||||
|
assert.Equal(t, th.BasicChannel.Id, q.ChannelMembers[0].Channel.ID)
|
||||||
|
assert.GreaterOrEqual(t, q.ChannelMembers[0].LastUpdateAt, float64(now))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelMemberCursor(t *testing.T) {
|
||||||
|
ch := channelMember{
|
||||||
|
ChannelMember: model.ChannelMember{ChannelId: "testid", UserId: "userid"},
|
||||||
|
}
|
||||||
|
cur := ch.Cursor()
|
||||||
|
|
||||||
|
chId, userId, ok := parseChannelMemberCursor(*cur)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, ch.ChannelId, chId)
|
||||||
|
assert.Equal(t, ch.UserId, userId)
|
||||||
|
}
|
||||||
462
api4/resolver_channel_test.go
Обычный файл
462
api4/resolver_channel_test.go
Обычный файл
@@ -0,0 +1,462 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGraphQLChannels(t *testing.T) {
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
// Adding another team with more channels (public and private)
|
||||||
|
myTeam := th.CreateTeam()
|
||||||
|
ch1 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, myTeam.Id)
|
||||||
|
ch2 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypePrivate, myTeam.Id)
|
||||||
|
th.LinkUserToTeam(th.BasicUser, myTeam)
|
||||||
|
th.App.AddUserToChannel(th.BasicUser, ch1, false)
|
||||||
|
th.App.AddUserToChannel(th.BasicUser, ch2, false)
|
||||||
|
|
||||||
|
var q struct {
|
||||||
|
Channels []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
CreateAt float64 `json:"createAt"`
|
||||||
|
UpdateAt float64 `json:"updateAt"`
|
||||||
|
Type model.ChannelType `json:"type"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Header string `json:"header"`
|
||||||
|
Purpose string `json:"purpose"`
|
||||||
|
SchemeId string `json:"schemeId"`
|
||||||
|
Cursor string `json:"cursor"`
|
||||||
|
Team struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
} `json:"team"`
|
||||||
|
} `json:"channels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("all", func(t *testing.T) {
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: `
|
||||||
|
query channels {
|
||||||
|
channels(userId: "me") {
|
||||||
|
id
|
||||||
|
createAt
|
||||||
|
updateAt
|
||||||
|
type
|
||||||
|
displayName
|
||||||
|
name
|
||||||
|
header
|
||||||
|
purpose
|
||||||
|
schemeId
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.Channels, 9)
|
||||||
|
|
||||||
|
numPrivate := 0
|
||||||
|
numPublic := 0
|
||||||
|
numOffTopic := 0
|
||||||
|
numTownSquare := 0
|
||||||
|
for _, ch := range q.Channels {
|
||||||
|
assert.NotEmpty(t, ch.ID)
|
||||||
|
assert.NotEmpty(t, ch.Name)
|
||||||
|
assert.NotEmpty(t, ch.Cursor)
|
||||||
|
assert.NotEmpty(t, ch.CreateAt)
|
||||||
|
assert.NotEmpty(t, ch.UpdateAt)
|
||||||
|
if ch.Type == model.ChannelTypeOpen {
|
||||||
|
numPublic++
|
||||||
|
} else if ch.Type == model.ChannelTypePrivate {
|
||||||
|
numPrivate++
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.DisplayName == "Off-Topic" {
|
||||||
|
numOffTopic++
|
||||||
|
} else if ch.DisplayName == "Town Square" {
|
||||||
|
numTownSquare++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, 2, numPrivate)
|
||||||
|
assert.Equal(t, 7, numPublic)
|
||||||
|
assert.Equal(t, 2, numOffTopic)
|
||||||
|
assert.Equal(t, 2, numTownSquare)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("user_perms", func(t *testing.T) {
|
||||||
|
query := `query channels($userId: String = "") {
|
||||||
|
channels(userId: $userId) {
|
||||||
|
id
|
||||||
|
createAt
|
||||||
|
updateAt
|
||||||
|
type
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
u1 := th.CreateUser()
|
||||||
|
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"userId": u1.Id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pagination", func(t *testing.T) {
|
||||||
|
query := `query channels($first: Int, $after: String = "") {
|
||||||
|
channels(userId: "me", first: $first, after: $after) {
|
||||||
|
id
|
||||||
|
createAt
|
||||||
|
updateAt
|
||||||
|
type
|
||||||
|
displayName
|
||||||
|
name
|
||||||
|
header
|
||||||
|
purpose
|
||||||
|
schemeId
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"first": 4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.Channels, 4)
|
||||||
|
|
||||||
|
input = graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"first": 4,
|
||||||
|
"after": q.Channels[3].Cursor,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.Channels, 4)
|
||||||
|
|
||||||
|
input = graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"first": 4,
|
||||||
|
"after": q.Channels[3].Cursor,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.Channels, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("team_filter", func(t *testing.T) {
|
||||||
|
query := `query channels($teamId: String, $first: Int) {
|
||||||
|
channels(userId: "me", teamId: $teamId, first: $first) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"first": 10,
|
||||||
|
"teamId": myTeam.Id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.Channels, 4)
|
||||||
|
|
||||||
|
input = graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"first": 2,
|
||||||
|
"teamId": myTeam.Id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.Channels, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("team_data", func(t *testing.T) {
|
||||||
|
query := `query channels($teamId: String, $first: Int) {
|
||||||
|
channels(userId: "me", teamId: $teamId, first: $first) {
|
||||||
|
id
|
||||||
|
team {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"first": 1,
|
||||||
|
"teamId": myTeam.Id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.Channels, 1)
|
||||||
|
|
||||||
|
gotTeam := q.Channels[0].Team
|
||||||
|
assert.Equal(t, myTeam.Id, gotTeam.ID)
|
||||||
|
assert.Equal(t, myTeam.DisplayName, gotTeam.DisplayName)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Delete+Update", func(t *testing.T) {
|
||||||
|
query := `query channels($lastDeleteAt: Float = 0,
|
||||||
|
$lastUpdateAt: Float = 0,
|
||||||
|
$first: Int = 60,
|
||||||
|
$includeDeleted: Boolean) {
|
||||||
|
channels(userId: "me", lastDeleteAt: $lastDeleteAt, lastUpdateAt: $lastUpdateAt, first: $first, includeDeleted: $includeDeleted) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"includeDeleted": false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.Channels, 9)
|
||||||
|
|
||||||
|
now := model.GetMillis()
|
||||||
|
input = graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"includeDeleted": true,
|
||||||
|
"lastUpdateAt": float64(now),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 1) // no channels found
|
||||||
|
|
||||||
|
th.BasicChannel.Purpose = "newpurpose"
|
||||||
|
_, _, err = th.Client.UpdateChannel(th.BasicChannel)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
input = graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"includeDeleted": true,
|
||||||
|
"lastUpdateAt": float64(now),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.Channels, 1)
|
||||||
|
|
||||||
|
_, err = th.Client.DeleteChannel(ch1.Id)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = th.Client.DeleteChannel(ch2.Id)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
input = graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"includeDeleted": false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.Channels, 7)
|
||||||
|
|
||||||
|
input = graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"includeDeleted": true,
|
||||||
|
"lastDeleteAt": float64(model.GetMillis()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.Channels, 7)
|
||||||
|
|
||||||
|
input = graphQLInput{
|
||||||
|
OperationName: "channels",
|
||||||
|
Query: query,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"includeDeleted": true,
|
||||||
|
"lastDeleteAt": float64(model.GetMillis()),
|
||||||
|
"first": 5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.Channels, 5)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPrettyDNForUsers(t *testing.T) {
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
|
||||||
|
t.Run("nickname_full_name", func(t *testing.T) {
|
||||||
|
users := []*model.User{
|
||||||
|
{
|
||||||
|
Nickname: "nick1",
|
||||||
|
Username: "user1",
|
||||||
|
FirstName: "first1",
|
||||||
|
LastName: "last1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Nickname: "nick2",
|
||||||
|
Username: "user2",
|
||||||
|
FirstName: "first2",
|
||||||
|
LastName: "last2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, "nick1, nick2", getPrettyDNForUsers("nickname_full_name", users))
|
||||||
|
|
||||||
|
users = []*model.User{
|
||||||
|
{
|
||||||
|
Username: "user1",
|
||||||
|
FirstName: "first1",
|
||||||
|
LastName: "last1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Username: "user2",
|
||||||
|
FirstName: "first2",
|
||||||
|
LastName: "last2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, "first1 last1, first2 last2", getPrettyDNForUsers("nickname_full_name", users))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("full_name", func(t *testing.T) {
|
||||||
|
users := []*model.User{
|
||||||
|
{
|
||||||
|
Nickname: "nick1",
|
||||||
|
Username: "user1",
|
||||||
|
FirstName: "first1",
|
||||||
|
LastName: "last1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Nickname: "nick2",
|
||||||
|
Username: "user2",
|
||||||
|
FirstName: "first2",
|
||||||
|
LastName: "last2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, "first1 last1, first2 last2", getPrettyDNForUsers("full_name", users))
|
||||||
|
|
||||||
|
users = []*model.User{
|
||||||
|
{
|
||||||
|
Username: "user1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Username: "user2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, "user1, user2", getPrettyDNForUsers("full_name", users))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("username", func(t *testing.T) {
|
||||||
|
users := []*model.User{
|
||||||
|
{
|
||||||
|
Nickname: "nick1",
|
||||||
|
Username: "user1",
|
||||||
|
FirstName: "first1",
|
||||||
|
LastName: "last1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Nickname: "nick2",
|
||||||
|
Username: "user2",
|
||||||
|
FirstName: "first2",
|
||||||
|
LastName: "last2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, "user1, user2", getPrettyDNForUsers("username", users))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChannelCursor(t *testing.T) {
|
||||||
|
ch := channel{
|
||||||
|
Channel: model.Channel{Id: "testid"},
|
||||||
|
}
|
||||||
|
cur := ch.Cursor()
|
||||||
|
|
||||||
|
id, ok := parseChannelCursor(*cur)
|
||||||
|
require.True(t, ok)
|
||||||
|
assert.Equal(t, ch.Id, id)
|
||||||
|
}
|
||||||
31
api4/resolver_team.go
Обычный файл
31
api4/resolver_team.go
Обычный файл
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getGraphQLTeam(ctx context.Context, id string) (*model.Team, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
team, appErr := c.App.GetTeam(id)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!team.AllowOpenInvite || team.Type != model.TeamOpen) &&
|
||||||
|
!c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), team.Id, model.PermissionViewTeam) {
|
||||||
|
c.SetPermissionError(model.PermissionViewTeam)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
team = c.App.SanitizeTeam(*c.AppContext.Session(), team)
|
||||||
|
return team, nil
|
||||||
|
}
|
||||||
69
api4/resolver_team_member.go
Обычный файл
69
api4/resolver_team_member.go
Обычный файл
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// teamMember is an internal graphQL wrapper struct to add resolver methods.
|
||||||
|
type teamMember struct {
|
||||||
|
model.TeamMember
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getTeam
|
||||||
|
func (tm *teamMember) Team(ctx context.Context) (*model.Team, error) {
|
||||||
|
return getGraphQLTeam(ctx, tm.TeamId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getUser
|
||||||
|
func (tm *teamMember) User(ctx context.Context) (*user, error) {
|
||||||
|
return getGraphQLUser(ctx, tm.UserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getCategoriesForTeamForUser
|
||||||
|
func (tm *teamMember) SidebarCategories(ctx context.Context) ([]*model.SidebarCategoryWithChannels, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), tm.UserId) {
|
||||||
|
c.SetPermissionError(model.PermissionEditOtherUsers)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
categories, appErr := c.App.GetSidebarCategories(tm.UserId, tm.TeamId)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: look into optimizing this.
|
||||||
|
// create map
|
||||||
|
orderMap := make(map[string]*model.SidebarCategoryWithChannels, len(categories.Categories))
|
||||||
|
for _, category := range categories.Categories {
|
||||||
|
orderMap[category.Id] = category
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new slice based on the order
|
||||||
|
res := make([]*model.SidebarCategoryWithChannels, 0, len(categories.Categories))
|
||||||
|
for _, categoryId := range categories.Order {
|
||||||
|
res = append(res, orderMap[categoryId])
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getRolesByNames
|
||||||
|
func (tm *teamMember) Roles_(ctx context.Context) ([]*model.Role, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return getGraphQLRoles(c, strings.Fields(tm.Roles))
|
||||||
|
}
|
||||||
252
api4/resolver_team_member_test.go
Обычный файл
252
api4/resolver_team_member_test.go
Обычный файл
@@ -0,0 +1,252 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGraphQLTeamMembers(t *testing.T) {
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
var q struct {
|
||||||
|
TeamMembers []struct {
|
||||||
|
User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
FirstName string `json:"firstName"`
|
||||||
|
LastName string `json:"lastName"`
|
||||||
|
NickName string `json:"nickname"`
|
||||||
|
} `json:"user"`
|
||||||
|
Team struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
} `json:"team"`
|
||||||
|
Roles []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"Name"`
|
||||||
|
Permissions []string `json:"permissions"`
|
||||||
|
SchemeManaged bool `json:"schemeManaged"`
|
||||||
|
BuiltIn bool `json:"builtIn"`
|
||||||
|
} `json:"roles"`
|
||||||
|
DeleteAt float64 `json:"deleteAt"`
|
||||||
|
SchemeGuest bool `json:"schemeGuest"`
|
||||||
|
SchemeUser bool `json:"schemeUser"`
|
||||||
|
SchemeAdmin bool `json:"schemeAdmin"`
|
||||||
|
SidebarCategories []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
Sorting model.SidebarCategorySorting `json:"sorting"`
|
||||||
|
ChannelIDs []string `json:"channelIds"`
|
||||||
|
} `json:"sidebarCategories"`
|
||||||
|
} `json:"teamMembers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("User", func(t *testing.T) {
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "teamMembers",
|
||||||
|
Query: `
|
||||||
|
query teamMembers($userId: String = "", $teamId: String = "") {
|
||||||
|
teamMembers(userId: $userId, teamId: $teamId) {
|
||||||
|
team {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
}
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
email
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
roles {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
schemeGuest
|
||||||
|
schemeUser
|
||||||
|
schemeAdmin
|
||||||
|
sidebarCategories {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
sorting
|
||||||
|
channelIds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"userId": "me",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.TeamMembers, 1)
|
||||||
|
|
||||||
|
tm := q.TeamMembers[0]
|
||||||
|
assert.Equal(t, th.BasicTeam.Id, tm.Team.ID)
|
||||||
|
assert.Equal(t, th.BasicTeam.DisplayName, tm.Team.DisplayName)
|
||||||
|
|
||||||
|
assert.Equal(t, th.BasicUser.Id, tm.User.ID)
|
||||||
|
assert.Equal(t, th.BasicUser.Username, tm.User.Username)
|
||||||
|
assert.Equal(t, th.BasicUser.Email, tm.User.Email)
|
||||||
|
assert.Equal(t, th.BasicUser.FirstName, tm.User.FirstName)
|
||||||
|
assert.Equal(t, th.BasicUser.LastName, tm.User.LastName)
|
||||||
|
|
||||||
|
require.Len(t, tm.Roles, 1)
|
||||||
|
assert.NotEmpty(t, tm.Roles[0].ID)
|
||||||
|
assert.Equal(t, "team_user", tm.Roles[0].Name)
|
||||||
|
assert.False(t, tm.SchemeGuest)
|
||||||
|
assert.True(t, tm.SchemeUser)
|
||||||
|
assert.False(t, tm.SchemeAdmin)
|
||||||
|
|
||||||
|
categories, _, err := th.Client.GetSidebarCategoriesForTeamForUser(th.BasicUser.Id, th.BasicTeam.Id, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
sort.Slice(tm.SidebarCategories, func(i, j int) bool {
|
||||||
|
return tm.SidebarCategories[i].ID < tm.SidebarCategories[j].ID
|
||||||
|
})
|
||||||
|
sort.Slice(categories.Categories, func(i, j int) bool {
|
||||||
|
return categories.Categories[i].Id < categories.Categories[j].Id
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := range categories.Categories {
|
||||||
|
assert.Equal(t, categories.Categories[i].Id, tm.SidebarCategories[i].ID)
|
||||||
|
assert.Equal(t, categories.Categories[i].DisplayName, tm.SidebarCategories[i].DisplayName)
|
||||||
|
assert.Equal(t, categories.Categories[i].Sorting, tm.SidebarCategories[i].Sorting)
|
||||||
|
assert.Equal(t, categories.Categories[i].ChannelIds(), tm.SidebarCategories[i].ChannelIDs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("User+Team", func(t *testing.T) {
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "teamMembers",
|
||||||
|
Query: `
|
||||||
|
query teamMembers($userId: String = "", $teamId: String = "") {
|
||||||
|
teamMembers(userId: $userId, teamId: $teamId) {
|
||||||
|
team {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
}
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
email
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
roles {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"userId": "me",
|
||||||
|
"teamId": th.BasicTeam.Id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.TeamMembers, 1)
|
||||||
|
|
||||||
|
tm := q.TeamMembers[0]
|
||||||
|
assert.Equal(t, th.BasicTeam.Id, tm.Team.ID)
|
||||||
|
assert.Equal(t, th.BasicTeam.DisplayName, tm.Team.DisplayName)
|
||||||
|
|
||||||
|
assert.Equal(t, th.BasicUser.Id, tm.User.ID)
|
||||||
|
assert.Equal(t, th.BasicUser.Username, tm.User.Username)
|
||||||
|
assert.Equal(t, th.BasicUser.Email, tm.User.Email)
|
||||||
|
assert.Equal(t, th.BasicUser.FirstName, tm.User.FirstName)
|
||||||
|
assert.Equal(t, th.BasicUser.LastName, tm.User.LastName)
|
||||||
|
|
||||||
|
require.Len(t, tm.Roles, 1)
|
||||||
|
assert.NotEmpty(t, tm.Roles[0].ID)
|
||||||
|
assert.Equal(t, "team_user", tm.Roles[0].Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NewTeam", func(t *testing.T) {
|
||||||
|
// Adding another team with more channels (public and private)
|
||||||
|
myTeam := th.CreateTeam()
|
||||||
|
ch1 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, myTeam.Id)
|
||||||
|
ch2 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypePrivate, myTeam.Id)
|
||||||
|
th.LinkUserToTeam(th.BasicUser, myTeam)
|
||||||
|
th.App.AddUserToChannel(th.BasicUser, ch1, false)
|
||||||
|
th.App.AddUserToChannel(th.BasicUser, ch2, false)
|
||||||
|
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "teamMembers",
|
||||||
|
Query: `
|
||||||
|
query teamMembers($userId: String = "", $teamId: String = "") {
|
||||||
|
teamMembers(userId: $userId, teamId: $teamId) {
|
||||||
|
team {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
}
|
||||||
|
roles {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"userId": "me",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.TeamMembers, 2)
|
||||||
|
|
||||||
|
sort.Slice(q.TeamMembers, func(i, j int) bool {
|
||||||
|
return q.TeamMembers[i].Team.ID < q.TeamMembers[j].Team.ID
|
||||||
|
})
|
||||||
|
|
||||||
|
expectedTeams := []*model.Team{th.BasicTeam, myTeam}
|
||||||
|
sort.Slice(expectedTeams, func(i, j int) bool {
|
||||||
|
return expectedTeams[i].Id < expectedTeams[j].Id
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := range q.TeamMembers {
|
||||||
|
tm := q.TeamMembers[i]
|
||||||
|
|
||||||
|
if tm.Team.ID == myTeam.Id {
|
||||||
|
require.Len(t, tm.Roles, 2)
|
||||||
|
sort.Slice(tm.Roles, func(i, j int) bool {
|
||||||
|
return tm.Roles[i].Name < tm.Roles[j].Name
|
||||||
|
})
|
||||||
|
assert.Equal(t, "team_admin", tm.Roles[0].Name)
|
||||||
|
assert.Equal(t, "team_user", tm.Roles[1].Name)
|
||||||
|
} else {
|
||||||
|
require.Len(t, tm.Roles, 1)
|
||||||
|
assert.NotEmpty(t, tm.Roles[0].ID)
|
||||||
|
assert.Equal(t, "team_user", tm.Roles[0].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedTeams[i].Id = tm.Team.ID
|
||||||
|
expectedTeams[i].DisplayName = tm.Team.DisplayName
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
145
api4/resolver_test.go
Обычный файл
145
api4/resolver_test.go
Обычный файл
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGraphQLConfig(t *testing.T) {
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
|
||||||
|
|
||||||
|
th := Setup(t)
|
||||||
|
th.LoginBasicWithGraphQL()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
var q struct {
|
||||||
|
Config map[string]string `json:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "config",
|
||||||
|
Query: `
|
||||||
|
query config {
|
||||||
|
config
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, _, err := th.Client.GetOldClientConfig("")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Equal(t, cfg, q.Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGraphQLLicense(t *testing.T) {
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
|
||||||
|
|
||||||
|
th := Setup(t)
|
||||||
|
th.LoginBasicWithGraphQL()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
var q struct {
|
||||||
|
License map[string]string `json:"license"`
|
||||||
|
}
|
||||||
|
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "license",
|
||||||
|
Query: `
|
||||||
|
query license {
|
||||||
|
license
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, _, err := th.Client.GetOldClientLicense("")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Equal(t, cfg, q.License)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGraphQLChannelsLeft(t *testing.T) {
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
|
||||||
|
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
var q struct {
|
||||||
|
ChannelsLeft []string `json:"channelsLeft"`
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("NotLeft", func(t *testing.T) {
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channelsLeft",
|
||||||
|
Query: `
|
||||||
|
query channelsLeft($userId: String = "me", $since: Float = 0.0) {
|
||||||
|
channelsLeft(userId: $userId, since: $since)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.ChannelsLeft, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Left", func(t *testing.T) {
|
||||||
|
_, err := th.Client.RemoveUserFromChannel(th.BasicChannel.Id, th.BasicUser.Id)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channelsLeft",
|
||||||
|
Query: `
|
||||||
|
query channelsLeft($userId: String = "me", $since: Float = 0.0) {
|
||||||
|
channelsLeft(userId: $userId, since: $since)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.ChannelsLeft, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("LeftAfterTime", func(t *testing.T) {
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "channelsLeft",
|
||||||
|
Query: `
|
||||||
|
query channelsLeft($userId: String = "me", $since: Float = 0.0) {
|
||||||
|
channelsLeft(userId: $userId, since: $since)
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"since": model.GetMillis(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Len(t, q.ChannelsLeft, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
118
api4/resolver_user.go
Обычный файл
118
api4/resolver_user.go
Обычный файл
@@ -0,0 +1,118 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v6/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
// user is an internal graphQL wrapper struct to add resolver methods.
|
||||||
|
type user struct {
|
||||||
|
model.User
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getUser
|
||||||
|
func getGraphQLUser(ctx context.Context, id string) (*user, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if id == model.Me {
|
||||||
|
id = c.AppContext.Session().UserId
|
||||||
|
}
|
||||||
|
|
||||||
|
if !model.IsValidId(id) {
|
||||||
|
return nil, web.NewInvalidParamError("user_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
canSee, appErr := c.App.UserCanSeeOtherUser(c.AppContext.Session().UserId, id)
|
||||||
|
if appErr != nil || !canSee {
|
||||||
|
c.SetPermissionError(model.PermissionViewMembers)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
usr, appErr := c.App.GetUser(id)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IsSystemAdmin() || c.AppContext.Session().UserId == usr.Id {
|
||||||
|
userTermsOfService, appErr := c.App.GetUserTermsOfService(usr.Id)
|
||||||
|
if appErr != nil && appErr.StatusCode != http.StatusNotFound {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if userTermsOfService != nil {
|
||||||
|
usr.TermsOfServiceId = userTermsOfService.TermsOfServiceId
|
||||||
|
usr.TermsOfServiceCreateAt = userTermsOfService.CreateAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.AppContext.Session().UserId == usr.Id {
|
||||||
|
usr.Sanitize(map[string]bool{})
|
||||||
|
} else {
|
||||||
|
c.App.SanitizeProfile(usr, c.IsSystemAdmin())
|
||||||
|
}
|
||||||
|
c.App.UpdateLastActivityAtIfNeeded(*c.AppContext.Session())
|
||||||
|
|
||||||
|
return &user{*usr}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getRolesByNames
|
||||||
|
func (u *user) Roles(ctx context.Context) ([]*model.Role, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
roleNames := u.GetRoles()
|
||||||
|
if len(roleNames) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return getGraphQLRoles(c, roleNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getPreferences
|
||||||
|
func (u *user) Preferences(ctx context.Context) ([]model.Preference, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), u.Id) {
|
||||||
|
c.SetPermissionError(model.PermissionEditOtherUsers)
|
||||||
|
return nil, c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences, appErr := c.App.GetPreferencesForUser(u.Id)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
return preferences, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// match with api4.getUserStatus
|
||||||
|
func (u *user) Status(ctx context.Context) (*model.Status, error) {
|
||||||
|
c, err := getCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
statuses, appErr := c.App.GetUserStatusesByIds([]string{u.Id})
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(statuses) == 0 {
|
||||||
|
return nil, model.NewAppError("UserStatus", "api.status.user_not_found.app_error", nil, "", http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
return statuses[0], nil
|
||||||
|
}
|
||||||
200
api4/resolver_user_test.go
Обычный файл
200
api4/resolver_user_test.go
Обычный файл
@@ -0,0 +1,200 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGraphQLUser(t *testing.T) {
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_GRAPHQL", "true")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_GRAPHQL")
|
||||||
|
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
var q struct {
|
||||||
|
User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
FirstName string `json:"firstName"`
|
||||||
|
LastName string `json:"lastName"`
|
||||||
|
NickName string `json:"nickname"`
|
||||||
|
IsBot bool `json:"isBot"`
|
||||||
|
IsSystemAdmin bool `json:"isSystemAdmin"`
|
||||||
|
CreateAt float64 `json:"createAt"`
|
||||||
|
CustomStatus struct {
|
||||||
|
Emoji string `json:"emoji"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Duration string `json:"duration"`
|
||||||
|
ExpiresAt time.Time `json:"expiresAt"`
|
||||||
|
} `json:"customStatus"`
|
||||||
|
Timezone model.StringMap `json:"timezone"`
|
||||||
|
Props model.StringMap `json:"props"`
|
||||||
|
NotifyProps model.StringMap `json:"notifyProps"`
|
||||||
|
Position string `json:"position"`
|
||||||
|
Roles []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"Name"`
|
||||||
|
Permissions []string `json:"permissions"`
|
||||||
|
SchemeManaged bool `json:"schemeManaged"`
|
||||||
|
BuiltIn bool `json:"builtIn"`
|
||||||
|
} `json:"roles"`
|
||||||
|
Preferences []struct {
|
||||||
|
UserID string `json:"userId"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
} `json:"preferences"`
|
||||||
|
} `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Basic", func(t *testing.T) {
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "user",
|
||||||
|
Query: `
|
||||||
|
query user($id: String = "me") {
|
||||||
|
user(id: $id) {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
email
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
isBot
|
||||||
|
isGuest
|
||||||
|
isSystemAdmin
|
||||||
|
timezone
|
||||||
|
props
|
||||||
|
notifyProps
|
||||||
|
roles {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
preferences {
|
||||||
|
name
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
assert.Equal(t, th.BasicUser.Id, q.User.ID)
|
||||||
|
assert.Equal(t, th.BasicUser.Username, q.User.Username)
|
||||||
|
assert.Equal(t, th.BasicUser.Email, q.User.Email)
|
||||||
|
assert.Equal(t, th.BasicUser.FirstName, q.User.FirstName)
|
||||||
|
assert.Equal(t, th.BasicUser.IsBot, q.User.IsBot)
|
||||||
|
assert.Equal(t, th.BasicUser.IsSystemAdmin(), q.User.IsSystemAdmin)
|
||||||
|
assert.Equal(t, th.BasicUser.Timezone, q.User.Timezone)
|
||||||
|
assert.Equal(t, th.BasicUser.Props, q.User.Props)
|
||||||
|
assert.Equal(t, th.BasicUser.NotifyProps, q.User.NotifyProps)
|
||||||
|
|
||||||
|
roles, _, err := th.Client.GetRolesByNames(th.BasicUser.GetRoles())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, q.User.Roles, 1)
|
||||||
|
assert.Len(t, roles, 1)
|
||||||
|
assert.Equal(t, roles[0].Id, q.User.Roles[0].ID)
|
||||||
|
assert.Equal(t, roles[0].Name, q.User.Roles[0].Name)
|
||||||
|
|
||||||
|
prefs, _, err := th.Client.GetPreferences(th.BasicUser.Id)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
sort.Slice(prefs, func(i, j int) bool {
|
||||||
|
return prefs[i].Name < prefs[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
sort.Slice(q.User.Preferences, func(i, j int) bool {
|
||||||
|
return q.User.Preferences[i].Name < q.User.Preferences[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := range prefs {
|
||||||
|
assert.Equal(t, q.User.Preferences[i].Name, prefs[i].Name)
|
||||||
|
assert.Equal(t, q.User.Preferences[i].Value, prefs[i].Value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Update", func(t *testing.T) {
|
||||||
|
th.BasicUser.Props = map[string]string{"testpropkey": "testpropvalue"}
|
||||||
|
th.App.UpdateUser(th.BasicUser, false)
|
||||||
|
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "user",
|
||||||
|
Query: `
|
||||||
|
query user($id: String = "me") {
|
||||||
|
user(id: $id) {
|
||||||
|
id
|
||||||
|
props
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
|
||||||
|
assert.Equal(t, th.BasicUser.Props, q.User.Props)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DifferentUser", func(t *testing.T) {
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "user",
|
||||||
|
Query: `
|
||||||
|
query user($id: String = "me") {
|
||||||
|
user(id: $id) {
|
||||||
|
id
|
||||||
|
props
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"id": th.BasicUser2.Id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 0)
|
||||||
|
require.NoError(t, json.Unmarshal(resp.Data, &q))
|
||||||
|
|
||||||
|
assert.Equal(t, q.User.ID, th.BasicUser2.Id)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("BadUser", func(t *testing.T) {
|
||||||
|
id := model.NewId()
|
||||||
|
input := graphQLInput{
|
||||||
|
OperationName: "user",
|
||||||
|
Query: `
|
||||||
|
query user($id: String = "me") {
|
||||||
|
user(id: $id) {
|
||||||
|
id
|
||||||
|
props
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := th.MakeGraphQLRequest(&input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resp.Errors, 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
157
api4/schema.graphqls
Обычный файл
157
api4/schema.graphqls
Обычный файл
@@ -0,0 +1,157 @@
|
|||||||
|
schema {
|
||||||
|
query: Query
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
user(id: String!): User
|
||||||
|
config(): StringMap!
|
||||||
|
license(): StringMap!
|
||||||
|
teamMembers(userId: String!,
|
||||||
|
teamId: String = ""): [TeamMember]!
|
||||||
|
channels(userId: String!,
|
||||||
|
teamId: String = "",
|
||||||
|
includeDeleted: Boolean = false,
|
||||||
|
lastDeleteAt: Float = 0,
|
||||||
|
lastUpdateAt: Float = 0,
|
||||||
|
first: Int = 60,
|
||||||
|
after: String = ""): [Channel]!
|
||||||
|
channelsLeft(userId: String!,
|
||||||
|
since: Float!): [String!]!
|
||||||
|
channelMembers(userId: String!,
|
||||||
|
channelId: String = "",
|
||||||
|
first: Int = 60,
|
||||||
|
after: String = "",
|
||||||
|
lastUpdateAt: Float = 0): [ChannelMember]!
|
||||||
|
}
|
||||||
|
|
||||||
|
scalar ChannelType
|
||||||
|
|
||||||
|
scalar SidebarCategoryType
|
||||||
|
|
||||||
|
scalar SidebarCategorySorting
|
||||||
|
|
||||||
|
scalar StringMap
|
||||||
|
|
||||||
|
scalar Time
|
||||||
|
|
||||||
|
type Channel {
|
||||||
|
id : String!
|
||||||
|
createAt : Float!
|
||||||
|
updateAt : Float!
|
||||||
|
deleteAt : Float!
|
||||||
|
type : ChannelType!
|
||||||
|
displayName: String!
|
||||||
|
prettyDisplayName: String!
|
||||||
|
name: String!
|
||||||
|
header: String!
|
||||||
|
purpose: String!
|
||||||
|
creatorId: String!
|
||||||
|
schemeId: String
|
||||||
|
team: Team
|
||||||
|
cursor: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChannelMember {
|
||||||
|
channel : Channel
|
||||||
|
user : User
|
||||||
|
roles : [Role]!
|
||||||
|
lastViewedAt : Float!
|
||||||
|
msgCount : Float!
|
||||||
|
mentionCount : Float!
|
||||||
|
mentionCountRoot : Float!
|
||||||
|
notifyProps : StringMap!
|
||||||
|
lastUpdateAt : Float!
|
||||||
|
schemeGuest : Boolean!
|
||||||
|
schemeUser : Boolean!
|
||||||
|
schemeAdmin : Boolean!
|
||||||
|
explicitRoles : String!
|
||||||
|
cursor: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type User {
|
||||||
|
id: String!
|
||||||
|
username: String!
|
||||||
|
email: String!
|
||||||
|
firstName: String!
|
||||||
|
lastName: String!
|
||||||
|
nickname: String!
|
||||||
|
isBot: Boolean!
|
||||||
|
isGuest: Boolean!
|
||||||
|
isSystemAdmin: Boolean!
|
||||||
|
createAt: Float!
|
||||||
|
deleteAt: Float!
|
||||||
|
authService: String!
|
||||||
|
customStatus: CustomStatus
|
||||||
|
status: Status
|
||||||
|
props: StringMap!
|
||||||
|
notifyProps: StringMap!
|
||||||
|
lastPictureUpdateAt: Float!
|
||||||
|
locale: String!
|
||||||
|
timezone: StringMap!
|
||||||
|
position: String!
|
||||||
|
roles: [Role]!
|
||||||
|
preferences: [Preference!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomStatus {
|
||||||
|
emoji: String!
|
||||||
|
text: String!
|
||||||
|
duration: String!
|
||||||
|
expiresAt: Time!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Status {
|
||||||
|
status: String!
|
||||||
|
manual: Boolean!
|
||||||
|
lastActivityAt: Float!
|
||||||
|
activeChannel: String!
|
||||||
|
dndEndTime: Float!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Role {
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
permissions: [String!]!
|
||||||
|
schemeManaged: Boolean!
|
||||||
|
builtIn: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Preference {
|
||||||
|
userId: String!
|
||||||
|
category: String!
|
||||||
|
name: String!
|
||||||
|
value: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Team {
|
||||||
|
id: String!
|
||||||
|
displayName : String!
|
||||||
|
name : String!
|
||||||
|
description : String!
|
||||||
|
email : String!
|
||||||
|
type : String!
|
||||||
|
companyName : String!
|
||||||
|
allowedDomains : String!
|
||||||
|
inviteId : String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TeamMember {
|
||||||
|
team: Team
|
||||||
|
user: User
|
||||||
|
roles: [Role]!
|
||||||
|
deleteAt: Float!
|
||||||
|
schemeGuest: Boolean!
|
||||||
|
schemeUser: Boolean!
|
||||||
|
schemeAdmin: Boolean!
|
||||||
|
sidebarCategories: [SidebarCategory]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type SidebarCategory {
|
||||||
|
id: String!
|
||||||
|
sorting: SidebarCategorySorting!
|
||||||
|
type: SidebarCategoryType!
|
||||||
|
displayName: String!
|
||||||
|
muted: Boolean!
|
||||||
|
collapsed: Boolean!
|
||||||
|
channelIds: [String!]!
|
||||||
|
}
|
||||||
@@ -752,7 +752,8 @@ func TestServerBusy503(t *testing.T) {
|
|||||||
|
|
||||||
func TestPushNotificationAck(t *testing.T) {
|
func TestPushNotificationAck(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := Setup(t).InitBasic()
|
||||||
api := Init(th.Server)
|
api, err := Init(th.Server)
|
||||||
|
require.NoError(t, err)
|
||||||
session, _ := th.App.GetSession(th.Client.AuthToken)
|
session, _ := th.App.GetSession(th.Client.AuthToken)
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
max_connections = 300
|
||||||
listen_addresses = '*'
|
listen_addresses = '*'
|
||||||
fsync = off
|
fsync = off
|
||||||
full_page_writes = off
|
full_page_writes = off
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func runServer(configStore *config.Store, interruptChan chan os.Signal) error {
|
|||||||
}
|
}
|
||||||
server, err := app.NewServer(options...)
|
server, err := app.NewServer(options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mlog.Critical(err.Error())
|
mlog.Error(err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer server.Shutdown()
|
defer server.Shutdown()
|
||||||
@@ -86,20 +86,24 @@ func runServer(configStore *config.Store, interruptChan chan os.Signal) error {
|
|||||||
if x := recover(); x != nil {
|
if x := recover(); x != nil {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
pprof.Lookup("goroutine").WriteTo(&buf, 2)
|
pprof.Lookup("goroutine").WriteTo(&buf, 2)
|
||||||
mlog.Critical("A panic occurred",
|
mlog.Error("A panic occurred",
|
||||||
mlog.Any("error", x),
|
mlog.Any("error", x),
|
||||||
mlog.String("stack", buf.String()))
|
mlog.String("stack", buf.String()))
|
||||||
panic(x)
|
panic(x)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
api := api4.Init(server)
|
api, err := api4.Init(server)
|
||||||
|
if err != nil {
|
||||||
|
mlog.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
wsapi.Init(server)
|
wsapi.Init(server)
|
||||||
web.New(server)
|
web.New(server)
|
||||||
|
|
||||||
err = server.Start()
|
err = server.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mlog.Critical(err.Error())
|
mlog.Error(err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,10 @@ func webClientTestsCmdF(command *cobra.Command, args []string) error {
|
|||||||
return serverErr
|
return serverErr
|
||||||
}
|
}
|
||||||
|
|
||||||
api4.Init(a.Srv())
|
_, err = api4.Init(a.Srv())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
wsapi.Init(a.Srv())
|
wsapi.Init(a.Srv())
|
||||||
a.UpdateConfig(setupClientTests)
|
a.UpdateConfig(setupClientTests)
|
||||||
runWebClientTests()
|
runWebClientTests()
|
||||||
@@ -79,7 +82,10 @@ func serverForWebClientTestsCmdF(command *cobra.Command, args []string) error {
|
|||||||
return serverErr
|
return serverErr
|
||||||
}
|
}
|
||||||
|
|
||||||
api4.Init(a.Srv())
|
_, err = api4.Init(a.Srv())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
wsapi.Init(a.Srv())
|
wsapi.Init(a.Srv())
|
||||||
a.UpdateConfig(setupClientTests)
|
a.UpdateConfig(setupClientTests)
|
||||||
|
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -39,6 +39,7 @@ require (
|
|||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/gorilla/schema v1.2.0
|
github.com/gorilla/schema v1.2.0
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
|
github.com/graph-gophers/graphql-go v1.3.0
|
||||||
github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c
|
github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c
|
||||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
|
|||||||
3
go.sum
3
go.sum
@@ -735,6 +735,8 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
|
|||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0=
|
||||||
|
github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
|
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
|
||||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
@@ -1202,6 +1204,7 @@ github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mo
|
|||||||
github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
|
github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
|
||||||
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
|
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
|
||||||
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
|
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
|
||||||
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ package model
|
|||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -159,6 +161,23 @@ func WithID(ID string) ChannelOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The following are some GraphQL methods necessary to return the
|
||||||
|
// data in float64 type. The spec doesn't support 64 bit integers,
|
||||||
|
// so we have to pass the data in float64. The _ at the end is
|
||||||
|
// a hack to keep the attribute name same in GraphQL schema.
|
||||||
|
|
||||||
|
func (o *Channel) CreateAt_() float64 {
|
||||||
|
return float64(o.CreateAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Channel) UpdateAt_() float64 {
|
||||||
|
return float64(o.UpdateAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Channel) DeleteAt_() float64 {
|
||||||
|
return float64(o.DeleteAt)
|
||||||
|
}
|
||||||
|
|
||||||
func (o *Channel) DeepCopy() *Channel {
|
func (o *Channel) DeepCopy() *Channel {
|
||||||
copy := *o
|
copy := *o
|
||||||
if copy.SchemeId != nil {
|
if copy.SchemeId != nil {
|
||||||
@@ -305,6 +324,24 @@ func (o *Channel) GetOtherUserIdForDM(userId string) string {
|
|||||||
return otherUserId
|
return otherUserId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ChannelType) ImplementsGraphQLType(name string) bool {
|
||||||
|
return name == "ChannelType"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ChannelType) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(string(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ChannelType) UnmarshalGraphQL(input interface{}) error {
|
||||||
|
chType, ok := input.(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("wrong type")
|
||||||
|
}
|
||||||
|
|
||||||
|
*t = ChannelType(chType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetDMNameFromIds(userId1, userId2 string) string {
|
func GetDMNameFromIds(userId1, userId2 string) string {
|
||||||
if userId1 > userId2 {
|
if userId1 > userId2 {
|
||||||
return userId2 + "__" + userId1
|
return userId2 + "__" + userId1
|
||||||
|
|||||||
@@ -60,6 +60,31 @@ type ChannelMember struct {
|
|||||||
ExplicitRoles string `json:"explicit_roles"`
|
ExplicitRoles string `json:"explicit_roles"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The following are some GraphQL methods necessary to return the
|
||||||
|
// data in float64 type. The spec doesn't support 64 bit integers,
|
||||||
|
// so we have to pass the data in float64. The _ at the end is
|
||||||
|
// a hack to keep the attribute name same in GraphQL schema.
|
||||||
|
|
||||||
|
func (o *ChannelMember) LastViewedAt_() float64 {
|
||||||
|
return float64(o.LastViewedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ChannelMember) MsgCount_() float64 {
|
||||||
|
return float64(o.MsgCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ChannelMember) MentionCount_() float64 {
|
||||||
|
return float64(o.MentionCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ChannelMember) MentionCountRoot_() float64 {
|
||||||
|
return float64(o.MentionCountRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ChannelMember) LastUpdateAt_() float64 {
|
||||||
|
return float64(o.LastUpdateAt)
|
||||||
|
}
|
||||||
|
|
||||||
// ChannelMemberWithTeamData contains ChannelMember appended with extra team information
|
// ChannelMemberWithTeamData contains ChannelMember appended with extra team information
|
||||||
// as well.
|
// as well.
|
||||||
type ChannelMemberWithTeamData struct {
|
type ChannelMemberWithTeamData struct {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,6 +56,10 @@ type SidebarCategoryWithChannels struct {
|
|||||||
Channels []string `json:"channel_ids"`
|
Channels []string `json:"channel_ids"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sc SidebarCategoryWithChannels) ChannelIds() []string {
|
||||||
|
return sc.Channels
|
||||||
|
}
|
||||||
|
|
||||||
type SidebarCategoryOrder []string
|
type SidebarCategoryOrder []string
|
||||||
|
|
||||||
// OrderedSidebarCategories combines categories, their channel IDs and an array of Category IDs, sorted
|
// OrderedSidebarCategories combines categories, their channel IDs and an array of Category IDs, sorted
|
||||||
@@ -83,3 +89,39 @@ func IsValidCategoryId(s string) bool {
|
|||||||
// Or default categories can follow the pattern {type}_{userID}_{teamID}
|
// Or default categories can follow the pattern {type}_{userID}_{teamID}
|
||||||
return categoryIdPattern.MatchString(s)
|
return categoryIdPattern.MatchString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (SidebarCategoryType) ImplementsGraphQLType(name string) bool {
|
||||||
|
return name == "SidebarCategoryType"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t SidebarCategoryType) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(string(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SidebarCategoryType) UnmarshalGraphQL(input interface{}) error {
|
||||||
|
chType, ok := input.(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("wrong type")
|
||||||
|
}
|
||||||
|
|
||||||
|
*t = SidebarCategoryType(chType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SidebarCategorySorting) ImplementsGraphQLType(name string) bool {
|
||||||
|
return name == "SidebarCategorySorting"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t SidebarCategorySorting) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(string(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SidebarCategorySorting) UnmarshalGraphQL(input interface{}) error {
|
||||||
|
chType, ok := input.(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("wrong type")
|
||||||
|
}
|
||||||
|
|
||||||
|
*t = SidebarCategorySorting(chType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const (
|
|||||||
|
|
||||||
APIURLSuffixV1 = "/api/v1"
|
APIURLSuffixV1 = "/api/v1"
|
||||||
APIURLSuffixV4 = "/api/v4"
|
APIURLSuffixV4 = "/api/v4"
|
||||||
|
APIURLSuffixV5 = "/api/v5"
|
||||||
APIURLSuffix = APIURLSuffixV4
|
APIURLSuffix = APIURLSuffixV4
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -61,6 +63,12 @@ func (cs *CustomStatus) AreDurationAndExpirationTimeValid() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExpiresAt_ returns the time in a type that has the marshal/unmarshal methods
|
||||||
|
// attached to it.
|
||||||
|
func (cs *CustomStatus) ExpiresAt_() graphql.Time {
|
||||||
|
return graphql.Time{Time: cs.ExpiresAt}
|
||||||
|
}
|
||||||
|
|
||||||
func RuneToHexadecimalString(r rune) string {
|
func RuneToHexadecimalString(r rune) string {
|
||||||
return fmt.Sprintf("%04x", r)
|
return fmt.Sprintf("%04x", r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ type FeatureFlags struct {
|
|||||||
|
|
||||||
// Enable Workspace optimization dashboard
|
// Enable Workspace optimization dashboard
|
||||||
WorkspaceOptimizationDashboard bool
|
WorkspaceOptimizationDashboard bool
|
||||||
|
|
||||||
|
// Enable GraphQL feature
|
||||||
|
GraphQL bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FeatureFlags) SetDefaults() {
|
func (f *FeatureFlags) SetDefaults() {
|
||||||
@@ -93,6 +96,7 @@ func (f *FeatureFlags) SetDefaults() {
|
|||||||
f.BoardsDataRetention = false
|
f.BoardsDataRetention = false
|
||||||
f.NormalizeLdapDNs = false
|
f.NormalizeLdapDNs = false
|
||||||
f.WorkspaceOptimizationDashboard = false
|
f.WorkspaceOptimizationDashboard = false
|
||||||
|
f.GraphQL = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FeatureFlags) Plugins() map[string]string {
|
func (f *FeatureFlags) Plugins() map[string]string {
|
||||||
|
|||||||
@@ -34,6 +34,19 @@ func (s *Status) ToJSON() ([]byte, error) {
|
|||||||
return json.Marshal(sCopy)
|
return json.Marshal(sCopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The following are some GraphQL methods necessary to return the
|
||||||
|
// data in float64 type. The spec doesn't support 64 bit integers,
|
||||||
|
// so we have to pass the data in float64. The _ at the end is
|
||||||
|
// a hack to keep the attribute name same in GraphQL schema.
|
||||||
|
|
||||||
|
func (s *Status) LastActivityAt_() float64 {
|
||||||
|
return float64(s.LastActivityAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Status) DNDEndTime_() float64 {
|
||||||
|
return float64(s.DNDEndTime)
|
||||||
|
}
|
||||||
|
|
||||||
func StatusListToJSON(u []*Status) ([]byte, error) {
|
func StatusListToJSON(u []*Status) ([]byte, error) {
|
||||||
list := make([]Status, len(u))
|
list := make([]Status, len(u))
|
||||||
for i, s := range u {
|
for i, s := range u {
|
||||||
|
|||||||
@@ -125,3 +125,9 @@ func (o *TeamMember) PreUpdate() {
|
|||||||
func (o *TeamMember) GetRoles() []string {
|
func (o *TeamMember) GetRoles() []string {
|
||||||
return strings.Fields(o.Roles)
|
return strings.Fields(o.Roles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteAt_ returns the deleteAt value in float64. This is necessary to work
|
||||||
|
// with GraphQL since it doesn't support 64 bit integers.
|
||||||
|
func (o *TeamMember) DeleteAt_() float64 {
|
||||||
|
return float64(o.DeleteAt)
|
||||||
|
}
|
||||||
|
|||||||
@@ -409,6 +409,23 @@ func (u *User) PreSave() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The following are some GraphQL methods necessary to return the
|
||||||
|
// data in float64 type. The spec doesn't support 64 bit integers,
|
||||||
|
// so we have to pass the data in float64. The _ at the end is
|
||||||
|
// a hack to keep the attribute name same in GraphQL schema.
|
||||||
|
|
||||||
|
func (u *User) CreateAt_() float64 {
|
||||||
|
return float64(u.CreateAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) DeleteAt_() float64 {
|
||||||
|
return float64(u.DeleteAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) LastPictureUpdateAt() float64 {
|
||||||
|
return float64(u.LastPictureUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
// PreUpdate should be run before updating the user in the db.
|
// PreUpdate should be run before updating the user in the db.
|
||||||
func (u *User) PreUpdate() {
|
func (u *User) PreUpdate() {
|
||||||
u.Username = SanitizeUnicode(u.Username)
|
u.Username = SanitizeUnicode(u.Username)
|
||||||
@@ -630,6 +647,15 @@ func (u *User) GetCustomStatus() *CustomStatus {
|
|||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) CustomStatus() *CustomStatus {
|
||||||
|
var o *CustomStatus
|
||||||
|
|
||||||
|
data := u.Props[UserPropsKeyCustomStatus]
|
||||||
|
_ = json.Unmarshal([]byte(data), &o)
|
||||||
|
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) ClearCustomStatus() {
|
func (u *User) ClearCustomStatus() {
|
||||||
u.MakeNonNil()
|
u.MakeNonNil()
|
||||||
u.Props[UserPropsKeyCustomStatus] = ""
|
u.Props[UserPropsKeyCustomStatus] = ""
|
||||||
|
|||||||
@@ -132,6 +132,24 @@ func (m StringMap) Value() (driver.Value, error) {
|
|||||||
return string(j), err
|
return string(j), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (StringMap) ImplementsGraphQLType(name string) bool {
|
||||||
|
return name == "StringMap"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m StringMap) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal((map[string]string)(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *StringMap) UnmarshalGraphQL(input interface{}) error {
|
||||||
|
json, ok := input.(map[string]string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("wrong type")
|
||||||
|
}
|
||||||
|
|
||||||
|
*m = json
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (si *StringInterface) Scan(value interface{}) error {
|
func (si *StringInterface) Scan(value interface{}) error {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
23
shared/mlog/graphql.go
Обычный файл
23
shared/mlog/graphql.go
Обычный файл
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package mlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GraphQLLogger is used to log panics that occur during query execution.
|
||||||
|
type GraphQLLogger struct {
|
||||||
|
logger *Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGraphQLLogger(logger *Logger) *GraphQLLogger {
|
||||||
|
return &GraphQLLogger{logger: logger}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogPanic satisfies the graphql/log.Logger interface.
|
||||||
|
// It converts the panic into an error.
|
||||||
|
func (l *GraphQLLogger) LogPanic(_ context.Context, value interface{}) {
|
||||||
|
l.logger.Error("Error while executing GraphQL query", Any("error", value))
|
||||||
|
}
|
||||||
5
vendor/github.com/graph-gophers/graphql-go/.gitignore
сгенерированный
поставляемый
Обычный файл
5
vendor/github.com/graph-gophers/graphql-go/.gitignore
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,5 @@
|
|||||||
|
/.idea
|
||||||
|
/.vscode
|
||||||
|
/internal/validation/testdata/graphql-js
|
||||||
|
/internal/validation/testdata/node_modules
|
||||||
|
/vendor
|
||||||
35
vendor/github.com/graph-gophers/graphql-go/.golangci.yml
сгенерированный
поставляемый
Обычный файл
35
vendor/github.com/graph-gophers/graphql-go/.golangci.yml
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,35 @@
|
|||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gofmt:
|
||||||
|
simplify: true
|
||||||
|
govet:
|
||||||
|
check-shadowing: true
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- fieldalignment
|
||||||
|
- deepequalerrors # remove later
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- deadcode
|
||||||
|
- gofmt
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- exportloopref
|
||||||
|
- structcheck
|
||||||
|
- staticcheck
|
||||||
|
- unconvert
|
||||||
|
- unused
|
||||||
|
- varcheck
|
||||||
|
- misspell
|
||||||
|
- goimports
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- linters:
|
||||||
|
- unused
|
||||||
|
path: "graphql_test.go"
|
||||||
10
vendor/github.com/graph-gophers/graphql-go/CHANGELOG.md
сгенерированный
поставляемый
Обычный файл
10
vendor/github.com/graph-gophers/graphql-go/CHANGELOG.md
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,10 @@
|
|||||||
|
CHANGELOG
|
||||||
|
|
||||||
|
[v1.1.0](https://github.com/graph-gophers/graphql-go/releases/tag/v1.1.0) Release v1.1.0
|
||||||
|
* [FEATURE] Add types package #437
|
||||||
|
* [FEATURE] Expose `packer.Unmarshaler` as `decode.Unmarshaler` to the public #450
|
||||||
|
* [FEATURE] Add location fields to type definitions #454
|
||||||
|
* [FEATURE] `errors.Errorf` preserves original error similar to `fmt.Errorf` #456
|
||||||
|
* [BUGFIX] Fix duplicated __typename in response (fixes #369) #443
|
||||||
|
|
||||||
|
[v1.0.0](https://github.com/graph-gophers/graphql-go/releases/tag/v1.0.0) Initial release
|
||||||
13
vendor/github.com/graph-gophers/graphql-go/CONTRIBUTING.md
сгенерированный
поставляемый
Обычный файл
13
vendor/github.com/graph-gophers/graphql-go/CONTRIBUTING.md
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,13 @@
|
|||||||
|
## Contributing
|
||||||
|
|
||||||
|
- With issues:
|
||||||
|
- Use the search tool before opening a new issue.
|
||||||
|
- Please provide source code and commit sha if you found a bug.
|
||||||
|
- Review existing issues and provide feedback or react to them.
|
||||||
|
|
||||||
|
- With pull requests:
|
||||||
|
- Open your pull request against `master`
|
||||||
|
- Your pull request should have no more than two commits, if not you should squash them.
|
||||||
|
- It should pass all tests in the available continuous integrations systems such as TravisCI.
|
||||||
|
- You should add/modify tests to cover your proposed code changes.
|
||||||
|
- If your pull request contains a new feature, please document it on the README.
|
||||||
24
vendor/github.com/graph-gophers/graphql-go/LICENSE
сгенерированный
поставляемый
Обычный файл
24
vendor/github.com/graph-gophers/graphql-go/LICENSE
сгенерированный
поставляемый
Обычный файл
@@ -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.
|
||||||
169
vendor/github.com/graph-gophers/graphql-go/README.md
сгенерированный
поставляемый
Обычный файл
169
vendor/github.com/graph-gophers/graphql-go/README.md
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,169 @@
|
|||||||
|
# graphql-go [](https://sourcegraph.com/github.com/graph-gophers/graphql-go?badge) [](https://graph-gophers.semaphoreci.com/projects/graphql-go) [](https://godoc.org/github.com/graph-gophers/graphql-go)
|
||||||
|
|
||||||
|
<p align="center"><img src="docs/img/logo.png" width="300"></p>
|
||||||
|
|
||||||
|
The goal of this project is to provide full support of the [GraphQL draft specification](https://facebook.github.io/graphql/draft) with a set of idiomatic, easy to use Go packages.
|
||||||
|
|
||||||
|
While still under heavy development (`internal` APIs are almost certainly subject to change), this library is
|
||||||
|
safe for production use.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- minimal API
|
||||||
|
- support for `context.Context`
|
||||||
|
- support for the `OpenTracing` standard
|
||||||
|
- schema type-checking against resolvers
|
||||||
|
- resolvers are matched to the schema based on method sets (can resolve a GraphQL schema with a Go interface or Go struct).
|
||||||
|
- handles panics in resolvers
|
||||||
|
- parallel execution of resolvers
|
||||||
|
- subscriptions
|
||||||
|
- [sample WS transport](https://github.com/graph-gophers/graphql-transport-ws)
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
We're trying out the GitHub Project feature to manage `graphql-go`'s [development roadmap](https://github.com/graph-gophers/graphql-go/projects/1).
|
||||||
|
Feedback is welcome and appreciated.
|
||||||
|
|
||||||
|
## (Some) Documentation
|
||||||
|
|
||||||
|
### Basic Sample
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
graphql "github.com/graph-gophers/graphql-go"
|
||||||
|
"github.com/graph-gophers/graphql-go/relay"
|
||||||
|
)
|
||||||
|
|
||||||
|
type query struct{}
|
||||||
|
|
||||||
|
func (_ *query) Hello() string { return "Hello, world!" }
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
s := `
|
||||||
|
type Query {
|
||||||
|
hello: String!
|
||||||
|
}
|
||||||
|
`
|
||||||
|
schema := graphql.MustParseSchema(s, &query{})
|
||||||
|
http.Handle("/query", &relay.Handler{Schema: schema})
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To test:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -XPOST -d '{"query": "{ hello }"}' localhost:8080/query
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resolvers
|
||||||
|
|
||||||
|
A resolver must have one method or field for each field of the GraphQL type it resolves. The method or field name has to be [exported](https://golang.org/ref/spec#Exported_identifiers) and match the schema's field's name in a non-case-sensitive way.
|
||||||
|
You can use struct fields as resolvers by using `SchemaOpt: UseFieldResolvers()`. For example,
|
||||||
|
```
|
||||||
|
opts := []graphql.SchemaOpt{graphql.UseFieldResolvers()}
|
||||||
|
schema := graphql.MustParseSchema(s, &query{}, opts...)
|
||||||
|
```
|
||||||
|
|
||||||
|
When using `UseFieldResolvers` schema option, a struct field will be used *only* when:
|
||||||
|
- there is no method for a struct field
|
||||||
|
- a struct field does not implement an interface method
|
||||||
|
- a struct field does not have arguments
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schema Options
|
||||||
|
|
||||||
|
- `UseStringDescriptions()` enables the usage of double quoted and triple quoted. When this is not enabled, comments are parsed as descriptions instead.
|
||||||
|
- `UseFieldResolvers()` specifies whether to use struct field resolvers.
|
||||||
|
- `MaxDepth(n int)` specifies the maximum field nesting depth in a query. The default is 0 which disables max depth checking.
|
||||||
|
- `MaxParallelism(n int)` specifies the maximum number of resolvers per request allowed to run in parallel. The default is 10.
|
||||||
|
- `Tracer(tracer trace.Tracer)` is used to trace queries and fields. It defaults to `trace.OpenTracingTracer`.
|
||||||
|
- `ValidationTracer(tracer trace.ValidationTracer)` is used to trace validation errors. It defaults to `trace.NoopValidationTracer`.
|
||||||
|
- `Logger(logger log.Logger)` is used to log panics during query execution. It defaults to `exec.DefaultLogger`.
|
||||||
|
- `PanicHandler(panicHandler errors.PanicHandler)` is used to transform panics into errors during query execution. It defaults to `errors.DefaultPanicHandler`.
|
||||||
|
- `DisableIntrospection()` disables introspection queries.
|
||||||
|
|
||||||
|
### Custom Errors
|
||||||
|
|
||||||
|
Errors returned by resolvers can include custom extensions by implementing the `ResolverError` interface:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type ResolverError interface {
|
||||||
|
error
|
||||||
|
Extensions() map[string]interface{}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of a simple custom error:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type droidNotFoundError struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e droidNotFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("error [%s]: %s", e.Code, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e droidNotFoundError) Extensions() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"code": e.Code,
|
||||||
|
"message": e.Message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Which could produce a GraphQL error such as:
|
||||||
|
|
||||||
|
```go
|
||||||
|
{
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"message": "error [NotFound]: This is not the droid you are looking for",
|
||||||
|
"path": [
|
||||||
|
"droid"
|
||||||
|
],
|
||||||
|
"extensions": {
|
||||||
|
"code": "NotFound",
|
||||||
|
"message": "This is not the droid you are looking for"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### [Examples](https://github.com/graph-gophers/graphql-go/wiki/Examples)
|
||||||
|
|
||||||
|
### [Companies that use this library](https://github.com/graph-gophers/graphql-go/wiki/Users)
|
||||||
13
vendor/github.com/graph-gophers/graphql-go/decode/decode.go
сгенерированный
поставляемый
Обычный файл
13
vendor/github.com/graph-gophers/graphql-go/decode/decode.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,13 @@
|
|||||||
|
package decode
|
||||||
|
|
||||||
|
// Unmarshaler defines the api of Go types mapped to custom GraphQL scalar types
|
||||||
|
type Unmarshaler interface {
|
||||||
|
// ImplementsGraphQLType maps the implementing custom Go type
|
||||||
|
// to the GraphQL scalar type in the schema.
|
||||||
|
ImplementsGraphQLType(name string) bool
|
||||||
|
// UnmarshalGraphQL is the custom unmarshaler for the implementing type
|
||||||
|
//
|
||||||
|
// This function will be called whenever you use the
|
||||||
|
// custom GraphQL scalar type as an input
|
||||||
|
UnmarshalGraphQL(input interface{}) error
|
||||||
|
}
|
||||||
59
vendor/github.com/graph-gophers/graphql-go/errors/errors.go
сгенерированный
поставляемый
Обычный файл
59
vendor/github.com/graph-gophers/graphql-go/errors/errors.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,59 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QueryError struct {
|
||||||
|
Err error `json:"-"` // Err holds underlying if available
|
||||||
|
Message string `json:"message"`
|
||||||
|
Locations []Location `json:"locations,omitempty"`
|
||||||
|
Path []interface{} `json:"path,omitempty"`
|
||||||
|
Rule string `json:"-"`
|
||||||
|
ResolverError error `json:"-"`
|
||||||
|
Extensions map[string]interface{} `json:"extensions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// similar to fmt.Errorf, Errorf will wrap the last argument if it is an instance of error
|
||||||
|
var err error
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
if v, ok := a[n-1].(error); ok {
|
||||||
|
err = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &QueryError{
|
||||||
|
Err: err,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *QueryError) Unwrap() error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ error = &QueryError{}
|
||||||
18
vendor/github.com/graph-gophers/graphql-go/errors/panic_handler.go
сгенерированный
поставляемый
Обычный файл
18
vendor/github.com/graph-gophers/graphql-go/errors/panic_handler.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,18 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PanicHandler is the interface used to create custom panic errors that occur during query execution
|
||||||
|
type PanicHandler interface {
|
||||||
|
MakePanicError(ctx context.Context, value interface{}) *QueryError
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultPanicHandler is the default PanicHandler
|
||||||
|
type DefaultPanicHandler struct{}
|
||||||
|
|
||||||
|
// MakePanicError creates a new QueryError from a panic that occurred during execution
|
||||||
|
func (h *DefaultPanicHandler) MakePanicError(ctx context.Context, value interface{}) *QueryError {
|
||||||
|
return Errorf("panic occurred: %v", value)
|
||||||
|
}
|
||||||
5
vendor/github.com/graph-gophers/graphql-go/go.mod
сгенерированный
поставляемый
Обычный файл
5
vendor/github.com/graph-gophers/graphql-go/go.mod
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,5 @@
|
|||||||
|
module github.com/graph-gophers/graphql-go
|
||||||
|
|
||||||
|
require github.com/opentracing/opentracing-go v1.1.0
|
||||||
|
|
||||||
|
go 1.13
|
||||||
2
vendor/github.com/graph-gophers/graphql-go/go.sum
сгенерированный
поставляемый
Обычный файл
2
vendor/github.com/graph-gophers/graphql-go/go.sum
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,2 @@
|
|||||||
|
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
|
||||||
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
339
vendor/github.com/graph-gophers/graphql-go/graphql.go
сгенерированный
поставляемый
Обычный файл
339
vendor/github.com/graph-gophers/graphql-go/graphql.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,339 @@
|
|||||||
|
package graphql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/common"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec/selected"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/query"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/schema"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/validation"
|
||||||
|
"github.com/graph-gophers/graphql-go/introspection"
|
||||||
|
"github.com/graph-gophers/graphql-go/log"
|
||||||
|
"github.com/graph-gophers/graphql-go/trace"
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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{},
|
||||||
|
panicHandler: &errors.DefaultPanicHandler{},
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.validationTracer == nil {
|
||||||
|
if tracer, ok := s.tracer.(trace.ValidationTracerContext); ok {
|
||||||
|
s.validationTracer = tracer
|
||||||
|
} else {
|
||||||
|
s.validationTracer = &validationBridgingTracer{tracer: trace.NoopValidationTracer{}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := schema.Parse(s.schema, schemaString, s.useStringDescriptions); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.validateSchema(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *types.Schema
|
||||||
|
res *resolvable.Schema
|
||||||
|
|
||||||
|
maxDepth int
|
||||||
|
maxParallelism int
|
||||||
|
tracer trace.Tracer
|
||||||
|
validationTracer trace.ValidationTracerContext
|
||||||
|
logger log.Logger
|
||||||
|
panicHandler errors.PanicHandler
|
||||||
|
useStringDescriptions bool
|
||||||
|
disableIntrospection bool
|
||||||
|
subscribeResolverTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Schema) ASTSchema() *types.Schema {
|
||||||
|
return s.schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchemaOpt is an option to pass to ParseSchema or MustParseSchema.
|
||||||
|
type SchemaOpt func(*Schema)
|
||||||
|
|
||||||
|
// UseStringDescriptions enables the usage of double quoted and triple quoted
|
||||||
|
// strings as descriptions as per the June 2018 spec
|
||||||
|
// https://facebook.github.io/graphql/June2018/. When this is not enabled,
|
||||||
|
// comments are parsed as descriptions instead.
|
||||||
|
func UseStringDescriptions() SchemaOpt {
|
||||||
|
return func(s *Schema) {
|
||||||
|
s.useStringDescriptions = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseFieldResolvers specifies whether to use struct field resolvers
|
||||||
|
func UseFieldResolvers() SchemaOpt {
|
||||||
|
return func(s *Schema) {
|
||||||
|
s.schema.UseFieldResolvers = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxDepth specifies the maximum field nesting depth in a query. The default is 0 which disables max depth checking.
|
||||||
|
func MaxDepth(n int) SchemaOpt {
|
||||||
|
return func(s *Schema) {
|
||||||
|
s.maxDepth = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidationTracer is used to trace validation errors. It defaults to trace.NoopValidationTracer.
|
||||||
|
// Deprecated: context is needed to support tracing correctly. Use a Tracer which implements trace.ValidationTracerContext.
|
||||||
|
func ValidationTracer(tracer trace.ValidationTracer) SchemaOpt { //nolint:staticcheck
|
||||||
|
return func(s *Schema) {
|
||||||
|
s.validationTracer = &validationBridgingTracer{tracer: tracer}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger is used to log panics during query execution. It defaults to exec.DefaultLogger.
|
||||||
|
func Logger(logger log.Logger) SchemaOpt {
|
||||||
|
return func(s *Schema) {
|
||||||
|
s.logger = logger
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PanicHandler is used to customize the panic errors during query execution.
|
||||||
|
// It defaults to errors.DefaultPanicHandler.
|
||||||
|
func PanicHandler(panicHandler errors.PanicHandler) SchemaOpt {
|
||||||
|
return func(s *Schema) {
|
||||||
|
s.panicHandler = panicHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableIntrospection disables introspection queries.
|
||||||
|
func DisableIntrospection() SchemaOpt {
|
||||||
|
return func(s *Schema) {
|
||||||
|
s.disableIntrospection = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeResolverTimeout is an option to control the amount of time
|
||||||
|
// we allow for a single subscribe message resolver to complete it's job
|
||||||
|
// before it times out and returns an error to the subscriber.
|
||||||
|
func SubscribeResolverTimeout(timeout time.Duration) SchemaOpt {
|
||||||
|
return func(s *Schema) {
|
||||||
|
s.subscribeResolverTimeout = timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// Errors are intentionally serialized first based on the advice in https://github.com/facebook/graphql/commit/7b40390d48680b15cb93e02d46ac5eb249689876#diff-757cea6edf0288677a9eea4cfc801d87R107
|
||||||
|
type Response struct {
|
||||||
|
Errors []*errors.QueryError `json:"errors,omitempty"`
|
||||||
|
Data json.RawMessage `json:"data,omitempty"`
|
||||||
|
Extensions map[string]interface{} `json:"extensions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the given query with the schema.
|
||||||
|
func (s *Schema) Validate(queryString string) []*errors.QueryError {
|
||||||
|
return s.ValidateWithVariables(queryString, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateWithVariables validates the given query with the schema and the input variables.
|
||||||
|
func (s *Schema) ValidateWithVariables(queryString string, variables map[string]interface{}) []*errors.QueryError {
|
||||||
|
doc, qErr := query.Parse(queryString)
|
||||||
|
if qErr != nil {
|
||||||
|
return []*errors.QueryError{qErr}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validation.Validate(s.schema, doc, variables, s.maxDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.Resolver.IsValid() {
|
||||||
|
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}}
|
||||||
|
}
|
||||||
|
|
||||||
|
validationFinish := s.validationTracer.TraceValidation(ctx)
|
||||||
|
errs := validation.Validate(s.schema, doc, variables, s.maxDepth)
|
||||||
|
validationFinish(errs)
|
||||||
|
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)}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the optional "operationName" POST parameter is not provided then
|
||||||
|
// use the query's operation name for improved tracing.
|
||||||
|
if operationName == "" {
|
||||||
|
operationName = op.Name.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscriptions are not valid in Exec. Use schema.Subscribe() instead.
|
||||||
|
if op.Type == query.Subscription {
|
||||||
|
return &Response{Errors: []*errors.QueryError{{Message: "graphql-ws protocol header is missing"}}}
|
||||||
|
}
|
||||||
|
if op.Type == query.Mutation {
|
||||||
|
if _, ok := s.schema.EntryPoints["mutation"]; !ok {
|
||||||
|
return &Response{Errors: []*errors.QueryError{{Message: "no mutations are offered by the schema"}}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in variables with the defaults from the operation
|
||||||
|
if variables == nil {
|
||||||
|
variables = make(map[string]interface{}, len(op.Vars))
|
||||||
|
}
|
||||||
|
for _, v := range op.Vars {
|
||||||
|
if _, ok := variables[v.Name.Name]; !ok && v.Default != nil {
|
||||||
|
variables[v.Name.Name] = v.Default.Deserialize(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &exec.Request{
|
||||||
|
Request: selected.Request{
|
||||||
|
Doc: doc,
|
||||||
|
Vars: variables,
|
||||||
|
Schema: s.schema,
|
||||||
|
DisableIntrospection: s.disableIntrospection,
|
||||||
|
},
|
||||||
|
Limiter: make(chan struct{}, s.maxParallelism),
|
||||||
|
Tracer: s.tracer,
|
||||||
|
Logger: s.logger,
|
||||||
|
PanicHandler: s.panicHandler,
|
||||||
|
}
|
||||||
|
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 (s *Schema) validateSchema() error {
|
||||||
|
// https://graphql.github.io/graphql-spec/June2018/#sec-Root-Operation-Types
|
||||||
|
// > The query root operation type must be provided and must be an Object type.
|
||||||
|
if err := validateRootOp(s.schema, "query", true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// > The mutation root operation type is optional; if it is not provided, the service does not support mutations.
|
||||||
|
// > If it is provided, it must be an Object type.
|
||||||
|
if err := validateRootOp(s.schema, "mutation", false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// > Similarly, the subscription root operation type is also optional; if it is not provided, the service does not
|
||||||
|
// > support subscriptions. If it is provided, it must be an Object type.
|
||||||
|
if err := validateRootOp(s.schema, "subscription", false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type validationBridgingTracer struct {
|
||||||
|
tracer trace.ValidationTracer //nolint:staticcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *validationBridgingTracer) TraceValidation(context.Context) trace.TraceValidationFinishFunc {
|
||||||
|
return t.tracer.TraceValidation()
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateRootOp(s *types.Schema, name string, mandatory bool) error {
|
||||||
|
t, ok := s.EntryPoints[name]
|
||||||
|
if !ok {
|
||||||
|
if mandatory {
|
||||||
|
return fmt.Errorf("root operation %q must be defined", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if t.Kind() != "OBJECT" {
|
||||||
|
return fmt.Errorf("root operation %q must be an OBJECT", name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOperation(document *types.ExecutableDefinition, operationName string) (*types.OperationDefinition, 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
|
||||||
|
}
|
||||||
30
vendor/github.com/graph-gophers/graphql-go/id.go
сгенерированный
поставляемый
Обычный файл
30
vendor/github.com/graph-gophers/graphql-go/id.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,30 @@
|
|||||||
|
package graphql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"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 = fmt.Errorf("wrong type for ID: %T", input)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (id ID) MarshalJSON() ([]byte, error) {
|
||||||
|
return strconv.AppendQuote(nil, string(id)), nil
|
||||||
|
}
|
||||||
103
vendor/github.com/graph-gophers/graphql-go/internal/common/blockstring.go
сгенерированный
поставляемый
Обычный файл
103
vendor/github.com/graph-gophers/graphql-go/internal/common/blockstring.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,103 @@
|
|||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2019 GraphQL Contributors
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// This implementation has been adapted from the graphql-js reference implementation
|
||||||
|
// https://github.com/graphql/graphql-js/blob/5eb7c4ded7ceb83ac742149cbe0dae07a8af9a30/src/language/blockString.js
|
||||||
|
// which is released under the MIT License above.
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Produces the value of a block string from its parsed raw value, similar to
|
||||||
|
// CoffeeScript's block string, Python's docstring trim or Ruby's strip_heredoc.
|
||||||
|
//
|
||||||
|
// This implements the GraphQL spec's BlockStringValue() static algorithm.
|
||||||
|
func blockString(raw string) string {
|
||||||
|
lines := strings.Split(raw, "\n")
|
||||||
|
|
||||||
|
// Remove common indentation from all lines except the first (which has none)
|
||||||
|
ind := blockStringIndentation(lines)
|
||||||
|
if ind > 0 {
|
||||||
|
for i := 1; i < len(lines); i++ {
|
||||||
|
l := lines[i]
|
||||||
|
if len(l) < ind {
|
||||||
|
lines[i] = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines[i] = l[ind:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading and trailing blank lines
|
||||||
|
trimStart := 0
|
||||||
|
for i := 0; i < len(lines) && isBlank(lines[i]); i++ {
|
||||||
|
trimStart++
|
||||||
|
}
|
||||||
|
lines = lines[trimStart:]
|
||||||
|
trimEnd := 0
|
||||||
|
for i := len(lines) - 1; i > 0 && isBlank(lines[i]); i-- {
|
||||||
|
trimEnd++
|
||||||
|
}
|
||||||
|
lines = lines[:len(lines)-trimEnd]
|
||||||
|
|
||||||
|
return strings.Join(lines, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockStringIndentation(lines []string) int {
|
||||||
|
var commonIndent *int
|
||||||
|
for i := 1; i < len(lines); i++ {
|
||||||
|
l := lines[i]
|
||||||
|
indent := leadingWhitespace(l)
|
||||||
|
if indent == len(l) {
|
||||||
|
// don't consider blank/empty lines
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if indent == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if commonIndent == nil || indent < *commonIndent {
|
||||||
|
commonIndent = &indent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if commonIndent == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *commonIndent
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBlank(s string) bool {
|
||||||
|
return len(s) == 0 || leadingWhitespace(s) == len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func leadingWhitespace(s string) int {
|
||||||
|
i := 0
|
||||||
|
for _, r := range s {
|
||||||
|
if r != '\t' && r != ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
18
vendor/github.com/graph-gophers/graphql-go/internal/common/directive.go
сгенерированный
поставляемый
Обычный файл
18
vendor/github.com/graph-gophers/graphql-go/internal/common/directive.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,18 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import "github.com/graph-gophers/graphql-go/types"
|
||||||
|
|
||||||
|
func ParseDirectives(l *Lexer) types.DirectiveList {
|
||||||
|
var directives types.DirectiveList
|
||||||
|
for l.Peek() == '@' {
|
||||||
|
l.ConsumeToken('@')
|
||||||
|
d := &types.Directive{}
|
||||||
|
d.Name = l.ConsumeIdentWithLoc()
|
||||||
|
d.Name.Loc.Column--
|
||||||
|
if l.Peek() == '(' {
|
||||||
|
d.Arguments = ParseArgumentList(l)
|
||||||
|
}
|
||||||
|
directives = append(directives, d)
|
||||||
|
}
|
||||||
|
return directives
|
||||||
|
}
|
||||||
229
vendor/github.com/graph-gophers/graphql-go/internal/common/lexer.go
сгенерированный
поставляемый
Обычный файл
229
vendor/github.com/graph-gophers/graphql-go/internal/common/lexer.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,229 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/scanner"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type syntaxError string
|
||||||
|
|
||||||
|
type Lexer struct {
|
||||||
|
sc *scanner.Scanner
|
||||||
|
next rune
|
||||||
|
comment bytes.Buffer
|
||||||
|
useStringDescriptions bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ident struct {
|
||||||
|
Name string
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLexer(s string, useStringDescriptions bool) *Lexer {
|
||||||
|
sc := &scanner.Scanner{
|
||||||
|
Mode: scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings,
|
||||||
|
}
|
||||||
|
sc.Init(strings.NewReader(s))
|
||||||
|
|
||||||
|
l := Lexer{sc: sc, useStringDescriptions: useStringDescriptions}
|
||||||
|
l.sc.Error = l.CatchScannerError
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsumeWhitespace consumes whitespace and tokens equivalent to whitespace (e.g. commas and comments).
|
||||||
|
//
|
||||||
|
// Consumed comment characters will build the description for the next type or field encountered.
|
||||||
|
// The description is available from `DescComment()`, and will be reset every time `ConsumeWhitespace()` is
|
||||||
|
// executed unless l.useStringDescriptions is set.
|
||||||
|
func (l *Lexer) ConsumeWhitespace() {
|
||||||
|
l.comment.Reset()
|
||||||
|
for {
|
||||||
|
l.next = l.sc.Scan()
|
||||||
|
|
||||||
|
if l.next == ',' {
|
||||||
|
// Similar to white space and line terminators, commas (',') are used to improve the
|
||||||
|
// legibility of source text and separate lexical tokens but are otherwise syntactically and
|
||||||
|
// semantically insignificant within GraphQL documents.
|
||||||
|
//
|
||||||
|
// http://facebook.github.io/graphql/draft/#sec-Insignificant-Commas
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.next == '#' {
|
||||||
|
// GraphQL source documents may contain single-line comments, starting with the '#' marker.
|
||||||
|
//
|
||||||
|
// A comment can contain any Unicode code point except `LineTerminator` so a comment always
|
||||||
|
// consists of all code points starting with the '#' character up to but not including the
|
||||||
|
// line terminator.
|
||||||
|
l.consumeComment()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// consumeDescription optionally consumes a description based on the June 2018 graphql spec if any are present.
|
||||||
|
//
|
||||||
|
// Single quote strings are also single line. Triple quote strings can be multi-line. Triple quote strings
|
||||||
|
// whitespace trimmed on both ends.
|
||||||
|
// If a description is found, consume any following comments as well
|
||||||
|
//
|
||||||
|
// http://facebook.github.io/graphql/June2018/#sec-Descriptions
|
||||||
|
func (l *Lexer) consumeDescription() string {
|
||||||
|
// If the next token is not a string, we don't consume it
|
||||||
|
if l.next != scanner.String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// Triple quote string is an empty "string" followed by an open quote due to the way the parser treats strings as one token
|
||||||
|
var desc string
|
||||||
|
if l.sc.Peek() == '"' {
|
||||||
|
desc = l.consumeTripleQuoteComment()
|
||||||
|
} else {
|
||||||
|
desc = l.consumeStringComment()
|
||||||
|
}
|
||||||
|
l.ConsumeWhitespace()
|
||||||
|
return desc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) ConsumeIdent() string {
|
||||||
|
name := l.sc.TokenText()
|
||||||
|
l.ConsumeToken(scanner.Ident)
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) ConsumeIdentWithLoc() types.Ident {
|
||||||
|
loc := l.Location()
|
||||||
|
name := l.sc.TokenText()
|
||||||
|
l.ConsumeToken(scanner.Ident)
|
||||||
|
return types.Ident{Name: name, Loc: 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.ConsumeWhitespace()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) ConsumeLiteral() *types.PrimitiveValue {
|
||||||
|
lit := &types.PrimitiveValue{Type: l.next, Text: l.sc.TokenText()}
|
||||||
|
l.ConsumeWhitespace()
|
||||||
|
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.ConsumeWhitespace()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) DescComment() string {
|
||||||
|
comment := l.comment.String()
|
||||||
|
desc := l.consumeDescription()
|
||||||
|
if l.useStringDescriptions {
|
||||||
|
return desc
|
||||||
|
}
|
||||||
|
return comment
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) consumeTripleQuoteComment() string {
|
||||||
|
l.next = l.sc.Next()
|
||||||
|
if l.next != '"' {
|
||||||
|
panic("consumeTripleQuoteComment used in wrong context: no third quote?")
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var numQuotes int
|
||||||
|
for {
|
||||||
|
l.next = l.sc.Next()
|
||||||
|
if l.next == '"' {
|
||||||
|
numQuotes++
|
||||||
|
} else {
|
||||||
|
numQuotes = 0
|
||||||
|
}
|
||||||
|
buf.WriteRune(l.next)
|
||||||
|
if numQuotes == 3 || l.next == scanner.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val := buf.String()
|
||||||
|
val = val[:len(val)-numQuotes]
|
||||||
|
return blockString(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) consumeStringComment() string {
|
||||||
|
val, err := strconv.Unquote(l.sc.TokenText())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// consumeComment consumes all characters from `#` to the first encountered line terminator.
|
||||||
|
// The characters are appended to `l.comment`.
|
||||||
|
func (l *Lexer) consumeComment() {
|
||||||
|
if l.next != '#' {
|
||||||
|
panic("consumeComment used in wrong context")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: count and trim whitespace so we can dedent any following lines.
|
||||||
|
if l.sc.Peek() == ' ' {
|
||||||
|
l.sc.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.comment.Len() > 0 {
|
||||||
|
l.comment.WriteRune('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
next := l.sc.Next()
|
||||||
|
if next == '\r' || next == '\n' || next == scanner.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
l.comment.WriteRune(next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lexer) CatchScannerError(s *scanner.Scanner, msg string) {
|
||||||
|
l.SyntaxError(msg)
|
||||||
|
}
|
||||||
58
vendor/github.com/graph-gophers/graphql-go/internal/common/literals.go
сгенерированный
поставляемый
Обычный файл
58
vendor/github.com/graph-gophers/graphql-go/internal/common/literals.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,58 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"text/scanner"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseLiteral(l *Lexer, constOnly bool) types.Value {
|
||||||
|
loc := l.Location()
|
||||||
|
switch l.Peek() {
|
||||||
|
case '$':
|
||||||
|
if constOnly {
|
||||||
|
l.SyntaxError("variable not allowed")
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
l.ConsumeToken('$')
|
||||||
|
return &types.Variable{Name: l.ConsumeIdent(), Loc: loc}
|
||||||
|
|
||||||
|
case scanner.Int, scanner.Float, scanner.String, scanner.Ident:
|
||||||
|
lit := l.ConsumeLiteral()
|
||||||
|
if lit.Type == scanner.Ident && lit.Text == "null" {
|
||||||
|
return &types.NullValue{Loc: 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 []types.Value
|
||||||
|
for l.Peek() != ']' {
|
||||||
|
list = append(list, ParseLiteral(l, constOnly))
|
||||||
|
}
|
||||||
|
l.ConsumeToken(']')
|
||||||
|
return &types.ListValue{Values: list, Loc: loc}
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
l.ConsumeToken('{')
|
||||||
|
var fields []*types.ObjectField
|
||||||
|
for l.Peek() != '}' {
|
||||||
|
name := l.ConsumeIdentWithLoc()
|
||||||
|
l.ConsumeToken(':')
|
||||||
|
value := ParseLiteral(l, constOnly)
|
||||||
|
fields = append(fields, &types.ObjectField{Name: name, Value: value})
|
||||||
|
}
|
||||||
|
l.ConsumeToken('}')
|
||||||
|
return &types.ObjectValue{Fields: fields, Loc: loc}
|
||||||
|
|
||||||
|
default:
|
||||||
|
l.SyntaxError("invalid value")
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
67
vendor/github.com/graph-gophers/graphql-go/internal/common/types.go
сгенерированный
поставляемый
Обычный файл
67
vendor/github.com/graph-gophers/graphql-go/internal/common/types.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,67 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseType(l *Lexer) types.Type {
|
||||||
|
t := parseNullType(l)
|
||||||
|
if l.Peek() == '!' {
|
||||||
|
l.ConsumeToken('!')
|
||||||
|
return &types.NonNull{OfType: t}
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNullType(l *Lexer) types.Type {
|
||||||
|
if l.Peek() == '[' {
|
||||||
|
l.ConsumeToken('[')
|
||||||
|
ofType := ParseType(l)
|
||||||
|
l.ConsumeToken(']')
|
||||||
|
return &types.List{OfType: ofType}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.TypeName{Ident: l.ConsumeIdentWithLoc()}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resolver func(name string) types.Type
|
||||||
|
|
||||||
|
// ResolveType attempts to resolve a type's name against a resolving function.
|
||||||
|
// This function is used when one needs to check if a TypeName exists in the resolver (typically a Schema).
|
||||||
|
//
|
||||||
|
// In the example below, ResolveType would be used to check if the resolving function
|
||||||
|
// returns a valid type for Dimension:
|
||||||
|
//
|
||||||
|
// type Profile {
|
||||||
|
// picture(dimensions: Dimension): Url
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ResolveType recursively unwraps List and NonNull types until a NamedType is reached.
|
||||||
|
func ResolveType(t types.Type, resolver Resolver) (types.Type, *errors.QueryError) {
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.List:
|
||||||
|
ofType, err := ResolveType(t.OfType, resolver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &types.List{OfType: ofType}, nil
|
||||||
|
case *types.NonNull:
|
||||||
|
ofType, err := ResolveType(t.OfType, resolver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &types.NonNull{OfType: ofType}, nil
|
||||||
|
case *types.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
|
||||||
|
}
|
||||||
|
}
|
||||||
37
vendor/github.com/graph-gophers/graphql-go/internal/common/values.go
сгенерированный
поставляемый
Обычный файл
37
vendor/github.com/graph-gophers/graphql-go/internal/common/values.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,37 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseInputValue(l *Lexer) *types.InputValueDefinition {
|
||||||
|
p := &types.InputValueDefinition{}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
p.Directives = ParseDirectives(l)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseArgumentList(l *Lexer) types.ArgumentList {
|
||||||
|
var args types.ArgumentList
|
||||||
|
l.ConsumeToken('(')
|
||||||
|
for l.Peek() != ')' {
|
||||||
|
name := l.ConsumeIdentWithLoc()
|
||||||
|
l.ConsumeToken(':')
|
||||||
|
value := ParseLiteral(l, false)
|
||||||
|
args = append(args, &types.Argument{
|
||||||
|
Name: name,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
l.ConsumeToken(')')
|
||||||
|
return args
|
||||||
|
}
|
||||||
381
vendor/github.com/graph-gophers/graphql-go/internal/exec/exec.go
сгенерированный
поставляемый
Обычный файл
381
vendor/github.com/graph-gophers/graphql-go/internal/exec/exec.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,381 @@
|
|||||||
|
package exec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec/selected"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/query"
|
||||||
|
"github.com/graph-gophers/graphql-go/log"
|
||||||
|
"github.com/graph-gophers/graphql-go/trace"
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
selected.Request
|
||||||
|
Limiter chan struct{}
|
||||||
|
Tracer trace.Tracer
|
||||||
|
Logger log.Logger
|
||||||
|
PanicHandler errors.PanicHandler
|
||||||
|
SubscribeResolverTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) handlePanic(ctx context.Context) {
|
||||||
|
if value := recover(); value != nil {
|
||||||
|
r.Logger.LogPanic(ctx, value)
|
||||||
|
r.AddError(r.PanicHandler.MakePanicError(ctx, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type extensionser interface {
|
||||||
|
Extensions() map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) Execute(ctx context.Context, s *resolvable.Schema, op *types.OperationDefinition) ([]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, 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 resolvedToNull(b *bytes.Buffer) bool {
|
||||||
|
return bytes.Equal(b.Bytes(), []byte("null"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) execSelections(ctx context.Context, sels []selected.Selection, path *pathSegment, s *resolvable.Schema, resolver reflect.Value, out *bytes.Buffer, serially bool) {
|
||||||
|
async := !serially && selected.HasAsyncSel(sels)
|
||||||
|
|
||||||
|
var fields []*fieldToExec
|
||||||
|
collectFieldsToResolve(sels, s, 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, s, f, &pathSegment{path, f.field.Alias}, true)
|
||||||
|
}(f)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
} else {
|
||||||
|
for _, f := range fields {
|
||||||
|
f.out = new(bytes.Buffer)
|
||||||
|
execFieldSelection(ctx, r, s, f, &pathSegment{path, f.field.Alias}, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte('{')
|
||||||
|
for i, f := range fields {
|
||||||
|
// If a non-nullable child resolved to null, an error was added to the
|
||||||
|
// "errors" list in the response, so this field resolves to null.
|
||||||
|
// If this field is non-nullable, the error is propagated to its parent.
|
||||||
|
if _, ok := f.field.Type.(*types.NonNull); ok && resolvedToNull(f.out) {
|
||||||
|
out.Reset()
|
||||||
|
out.Write([]byte("null"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 0 {
|
||||||
|
out.WriteByte(',')
|
||||||
|
}
|
||||||
|
out.WriteByte('"')
|
||||||
|
out.WriteString(f.field.Alias)
|
||||||
|
out.WriteByte('"')
|
||||||
|
out.WriteByte(':')
|
||||||
|
out.Write(f.out.Bytes())
|
||||||
|
}
|
||||||
|
out.WriteByte('}')
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectFieldsToResolve(sels []selected.Selection, s *resolvable.Schema, 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:
|
||||||
|
_, ok := fieldByAlias[sel.Alias]
|
||||||
|
if !ok {
|
||||||
|
res := reflect.ValueOf(typeOf(sel, resolver))
|
||||||
|
f := s.FieldTypename
|
||||||
|
f.TypeName = res.String()
|
||||||
|
|
||||||
|
sf := &selected.SchemaField{
|
||||||
|
Field: f,
|
||||||
|
Alias: sel.Alias,
|
||||||
|
FixedResult: res,
|
||||||
|
}
|
||||||
|
|
||||||
|
field := &fieldToExec{field: sf, resolver: resolver}
|
||||||
|
*fields = append(*fields, field)
|
||||||
|
fieldByAlias[sel.Alias] = field
|
||||||
|
}
|
||||||
|
|
||||||
|
case *selected.TypeAssertion:
|
||||||
|
out := resolver.Method(sel.MethodIndex).Call(nil)
|
||||||
|
if !out[1].Bool() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
collectFieldsToResolve(sel.Sels, s, 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, s *resolvable.Schema, 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 = r.PanicHandler.MakePanicError(ctx, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
res := f.resolver
|
||||||
|
if f.field.UseMethodResolver() {
|
||||||
|
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 := res.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
|
||||||
|
if ex, ok := callOut[1].Interface().(extensionser); ok {
|
||||||
|
err.Extensions = ex.Extensions()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO extract out unwrapping ptr logic to a common place
|
||||||
|
if res.Kind() == reflect.Ptr {
|
||||||
|
res = res.Elem()
|
||||||
|
}
|
||||||
|
result = res.FieldByIndex(f.field.FieldIndex)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
if applyLimiter {
|
||||||
|
<-r.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// If an error occurred while resolving a field, it should be treated as though the field
|
||||||
|
// returned null, and an error must be added to the "errors" list in the response.
|
||||||
|
r.AddError(err)
|
||||||
|
f.out.WriteString("null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.execSelectionSet(traceCtx, f.sels, f.field.Type, path, s, result, f.out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) execSelectionSet(ctx context.Context, sels []selected.Selection, typ types.Type, path *pathSegment, s *resolvable.Schema, resolver reflect.Value, out *bytes.Buffer) {
|
||||||
|
t, nonNull := unwrapNonNull(typ)
|
||||||
|
|
||||||
|
// a reflect.Value of a nil interface will show up as an Invalid value
|
||||||
|
if resolver.Kind() == reflect.Invalid || ((resolver.Kind() == reflect.Ptr || resolver.Kind() == reflect.Interface) && resolver.IsNil()) {
|
||||||
|
// If a field of a non-null type resolves to null (either because the
|
||||||
|
// function to resolve the field returned null or because an error occurred),
|
||||||
|
// add an error to the "errors" list in the response.
|
||||||
|
if nonNull {
|
||||||
|
err := errors.Errorf("graphql: got nil for non-null %q", t)
|
||||||
|
err.Path = path.toSlice()
|
||||||
|
r.AddError(err)
|
||||||
|
}
|
||||||
|
out.WriteString("null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t.(type) {
|
||||||
|
case *types.ObjectTypeDefinition, *types.InterfaceTypeDefinition, *types.Union:
|
||||||
|
r.execSelections(ctx, sels, path, s, resolver, out, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any pointers or interfaces at this point should be non-nil, so we can get the actual value of them
|
||||||
|
// for serialization
|
||||||
|
if resolver.Kind() == reflect.Ptr || resolver.Kind() == reflect.Interface {
|
||||||
|
resolver = resolver.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.List:
|
||||||
|
r.execList(ctx, sels, t, path, s, resolver, out)
|
||||||
|
|
||||||
|
case *types.ScalarTypeDefinition:
|
||||||
|
v := resolver.Interface()
|
||||||
|
data, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
panic(errors.Errorf("could not marshal %v: %s", v, err))
|
||||||
|
}
|
||||||
|
out.Write(data)
|
||||||
|
|
||||||
|
case *types.EnumTypeDefinition:
|
||||||
|
var stringer fmt.Stringer = resolver
|
||||||
|
if s, ok := resolver.Interface().(fmt.Stringer); ok {
|
||||||
|
stringer = s
|
||||||
|
}
|
||||||
|
name := stringer.String()
|
||||||
|
var valid bool
|
||||||
|
for _, v := range t.EnumValuesDefinition {
|
||||||
|
if v.EnumValue == name {
|
||||||
|
valid = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
err := errors.Errorf("Invalid value %s.\nExpected type %s, found %s.", name, t.Name, name)
|
||||||
|
err.Path = path.toSlice()
|
||||||
|
r.AddError(err)
|
||||||
|
out.WriteString("null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.WriteByte('"')
|
||||||
|
out.WriteString(name)
|
||||||
|
out.WriteByte('"')
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) execList(ctx context.Context, sels []selected.Selection, typ *types.List, path *pathSegment, s *resolvable.Schema, resolver reflect.Value, out *bytes.Buffer) {
|
||||||
|
l := resolver.Len()
|
||||||
|
entryouts := make([]bytes.Buffer, l)
|
||||||
|
|
||||||
|
if selected.HasAsyncSel(sels) {
|
||||||
|
// Limit the number of concurrent goroutines spawned as it can lead to large
|
||||||
|
// memory spikes for large lists.
|
||||||
|
concurrency := cap(r.Limiter)
|
||||||
|
sem := make(chan struct{}, concurrency)
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
sem <- struct{}{}
|
||||||
|
go func(i int) {
|
||||||
|
defer func() { <-sem }()
|
||||||
|
defer r.handlePanic(ctx)
|
||||||
|
r.execSelectionSet(ctx, sels, typ.OfType, &pathSegment{path, i}, s, resolver.Index(i), &entryouts[i])
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
for i := 0; i < concurrency; i++ {
|
||||||
|
sem <- struct{}{}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
r.execSelectionSet(ctx, sels, typ.OfType, &pathSegment{path, i}, s, resolver.Index(i), &entryouts[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, listOfNonNull := typ.OfType.(*types.NonNull)
|
||||||
|
|
||||||
|
out.WriteByte('[')
|
||||||
|
for i, entryout := range entryouts {
|
||||||
|
// If the list wraps a non-null type and one of the list elements
|
||||||
|
// resolves to null, then the entire list resolves to null.
|
||||||
|
if listOfNonNull && resolvedToNull(&entryout) {
|
||||||
|
out.Reset()
|
||||||
|
out.WriteString("null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 0 {
|
||||||
|
out.WriteByte(',')
|
||||||
|
}
|
||||||
|
out.Write(entryout.Bytes())
|
||||||
|
}
|
||||||
|
out.WriteByte(']')
|
||||||
|
}
|
||||||
|
|
||||||
|
func unwrapNonNull(t types.Type) (types.Type, bool) {
|
||||||
|
if nn, ok := t.(*types.NonNull); ok {
|
||||||
|
return nn.OfType, true
|
||||||
|
}
|
||||||
|
return t, false
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathSegment struct {
|
||||||
|
parent *pathSegment
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pathSegment) toSlice() []interface{} {
|
||||||
|
if p == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return append(p.parent.toSlice(), p.value)
|
||||||
|
}
|
||||||
390
vendor/github.com/graph-gophers/graphql-go/internal/exec/packer/packer.go
сгенерированный
поставляемый
Обычный файл
390
vendor/github.com/graph-gophers/graphql-go/internal/exec/packer/packer.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,390 @@
|
|||||||
|
package packer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/decode"
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type packer interface {
|
||||||
|
Pack(value interface{}) (reflect.Value, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
packerMap map[typePair]*packerMapEntry
|
||||||
|
structPackers []*StructPacker
|
||||||
|
}
|
||||||
|
|
||||||
|
type typePair struct {
|
||||||
|
graphQLType types.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.Deserialize(nil))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.defaultStruct.FieldByIndex(f.fieldIndex).Set(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) assignPacker(target *packer, schemaType types.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 types.Type, reflectType reflect.Type) (packer, error) {
|
||||||
|
t, nonNull := unwrapNonNull(schemaType)
|
||||||
|
if !nonNull {
|
||||||
|
if reflectType.Kind() == reflect.Ptr {
|
||||||
|
elemType := reflectType.Elem()
|
||||||
|
addPtr := true
|
||||||
|
if _, ok := t.(*types.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
|
||||||
|
} else if isNullable(reflectType) {
|
||||||
|
elemType := reflectType
|
||||||
|
addPtr := false
|
||||||
|
elem, err := b.makeNonNullPacker(t, elemType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &nullPacker{
|
||||||
|
elemPacker: elem,
|
||||||
|
valueType: reflectType,
|
||||||
|
addPtr: addPtr,
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("%s is not a pointer or a nullable type", reflectType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.makeNonNullPacker(t, reflectType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) makeNonNullPacker(schemaType types.Type, reflectType reflect.Type) (packer, error) {
|
||||||
|
if u, ok := reflect.New(reflectType).Interface().(decode.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 *types.ScalarTypeDefinition:
|
||||||
|
return &ValuePacker{
|
||||||
|
ValueType: reflectType,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case *types.EnumTypeDefinition:
|
||||||
|
if reflectType.Kind() != reflect.String {
|
||||||
|
return nil, fmt.Errorf("wrong type, expected %s", reflect.String)
|
||||||
|
}
|
||||||
|
return &ValuePacker{
|
||||||
|
ValueType: reflectType,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case *types.InputObject:
|
||||||
|
e, err := b.MakeStructPacker(t.Values, reflectType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
|
||||||
|
case *types.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 *types.ObjectTypeDefinition, *types.InterfaceTypeDefinition, *types.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 []*types.InputValueDefinition, 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 (hint: missing `args struct { ... }` wrapper for field arguments?)", 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("%s does not define field %q (hint: missing `args struct { ... }` wrapper for field arguments, or missing field on input struct)", typ, v.Name.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 = &types.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 *types.InputValueDefinition
|
||||||
|
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 && !isNullable(p.valueType) {
|
||||||
|
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 && !isNullable(p.ValueType) {
|
||||||
|
return reflect.Value{}, errors.Errorf("got null for non-null")
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.New(p.ValueType)
|
||||||
|
if err := v.Interface().(decode.Unmarshaler).UnmarshalGraphQL(value); err != nil {
|
||||||
|
return reflect.Value{}, err
|
||||||
|
}
|
||||||
|
return v.Elem(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
if reflect.TypeOf(input).ConvertibleTo(typ) {
|
||||||
|
return reflect.ValueOf(input).Convert(typ).Interface(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("incompatible type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func unwrapNonNull(t types.Type) (types.Type, bool) {
|
||||||
|
if nn, ok := t.(*types.NonNull); ok {
|
||||||
|
return nn.OfType, true
|
||||||
|
}
|
||||||
|
return t, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripUnderscore(s string) string {
|
||||||
|
return strings.Replace(s, "_", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NullUnmarshaller is an unmarshaller that can handle a nil input
|
||||||
|
type NullUnmarshaller interface {
|
||||||
|
decode.Unmarshaler
|
||||||
|
Nullable()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNullable(t reflect.Type) bool {
|
||||||
|
_, ok := reflect.New(t).Interface().(NullUnmarshaller)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
70
vendor/github.com/graph-gophers/graphql-go/internal/exec/resolvable/meta.go
сгенерированный
поставляемый
Обычный файл
70
vendor/github.com/graph-gophers/graphql-go/internal/exec/resolvable/meta.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,70 @@
|
|||||||
|
package resolvable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/introspection"
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Meta defines the details of the metadata schema for introspection.
|
||||||
|
type Meta struct {
|
||||||
|
FieldSchema Field
|
||||||
|
FieldType Field
|
||||||
|
FieldTypename Field
|
||||||
|
Schema *Object
|
||||||
|
Type *Object
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMeta(s *types.Schema) *Meta {
|
||||||
|
var err error
|
||||||
|
b := newBuilder(s)
|
||||||
|
|
||||||
|
metaSchema := s.Types["__Schema"].(*types.ObjectTypeDefinition)
|
||||||
|
so, err := b.makeObjectExec(metaSchema.Name, metaSchema.Fields, nil, false, reflect.TypeOf(&introspection.Schema{}))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
metaType := s.Types["__Type"].(*types.ObjectTypeDefinition)
|
||||||
|
t, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldTypename := Field{
|
||||||
|
FieldDefinition: types.FieldDefinition{
|
||||||
|
Name: "__typename",
|
||||||
|
Type: &types.NonNull{OfType: s.Types["String"]},
|
||||||
|
},
|
||||||
|
TraceLabel: "GraphQL field: __typename",
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldSchema := Field{
|
||||||
|
FieldDefinition: types.FieldDefinition{
|
||||||
|
Name: "__schema",
|
||||||
|
Type: s.Types["__Schema"],
|
||||||
|
},
|
||||||
|
TraceLabel: "GraphQL field: __schema",
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldType := Field{
|
||||||
|
FieldDefinition: types.FieldDefinition{
|
||||||
|
Name: "__type",
|
||||||
|
Type: s.Types["__Type"],
|
||||||
|
},
|
||||||
|
TraceLabel: "GraphQL field: __type",
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Meta{
|
||||||
|
FieldSchema: fieldSchema,
|
||||||
|
FieldTypename: fieldTypename,
|
||||||
|
FieldType: fieldType,
|
||||||
|
Schema: so,
|
||||||
|
Type: t,
|
||||||
|
}
|
||||||
|
}
|
||||||
453
vendor/github.com/graph-gophers/graphql-go/internal/exec/resolvable/resolvable.go
сгенерированный
поставляемый
Обычный файл
453
vendor/github.com/graph-gophers/graphql-go/internal/exec/resolvable/resolvable.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,453 @@
|
|||||||
|
package resolvable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/decode"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec/packer"
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Schema struct {
|
||||||
|
*Meta
|
||||||
|
types.Schema
|
||||||
|
Query Resolvable
|
||||||
|
Mutation Resolvable
|
||||||
|
Subscription Resolvable
|
||||||
|
Resolver reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resolvable interface {
|
||||||
|
isResolvable()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Object struct {
|
||||||
|
Name string
|
||||||
|
Fields map[string]*Field
|
||||||
|
TypeAssertions map[string]*TypeAssertion
|
||||||
|
}
|
||||||
|
|
||||||
|
type Field struct {
|
||||||
|
types.FieldDefinition
|
||||||
|
TypeName string
|
||||||
|
MethodIndex int
|
||||||
|
FieldIndex []int
|
||||||
|
HasContext bool
|
||||||
|
HasError bool
|
||||||
|
ArgsPacker *packer.StructPacker
|
||||||
|
ValueExec Resolvable
|
||||||
|
TraceLabel string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Field) UseMethodResolver() bool {
|
||||||
|
return len(f.FieldIndex) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *types.Schema, resolver interface{}) (*Schema, error) {
|
||||||
|
if resolver == nil {
|
||||||
|
return &Schema{Meta: newMeta(s), Schema: *s}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b := newBuilder(s)
|
||||||
|
|
||||||
|
var query, mutation, subscription 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 t, ok := s.EntryPoints["subscription"]; ok {
|
||||||
|
if err := b.assignExec(&subscription, t, reflect.TypeOf(resolver)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.finish(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Schema{
|
||||||
|
Meta: newMeta(s),
|
||||||
|
Schema: *s,
|
||||||
|
Resolver: reflect.ValueOf(resolver),
|
||||||
|
Query: query,
|
||||||
|
Mutation: mutation,
|
||||||
|
Subscription: subscription,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type execBuilder struct {
|
||||||
|
schema *types.Schema
|
||||||
|
resMap map[typePair]*resMapEntry
|
||||||
|
packerBuilder *packer.Builder
|
||||||
|
}
|
||||||
|
|
||||||
|
type typePair struct {
|
||||||
|
graphQLType types.Type
|
||||||
|
resolverType reflect.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
type resMapEntry struct {
|
||||||
|
exec Resolvable
|
||||||
|
targets []*Resolvable
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuilder(s *types.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 types.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 types.Type, resolverType reflect.Type) (Resolvable, error) {
|
||||||
|
var nonNull bool
|
||||||
|
t, nonNull = unwrapNonNull(t)
|
||||||
|
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.ObjectTypeDefinition:
|
||||||
|
return b.makeObjectExec(t.Name, t.Fields, nil, nonNull, resolverType)
|
||||||
|
|
||||||
|
case *types.InterfaceTypeDefinition:
|
||||||
|
return b.makeObjectExec(t.Name, t.Fields, t.PossibleTypes, nonNull, resolverType)
|
||||||
|
|
||||||
|
case *types.Union:
|
||||||
|
return b.makeObjectExec(t.Name, nil, t.UnionMemberTypes, 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 *types.ScalarTypeDefinition:
|
||||||
|
return makeScalarExec(t, resolverType)
|
||||||
|
|
||||||
|
case *types.EnumTypeDefinition:
|
||||||
|
return &Scalar{}, nil
|
||||||
|
|
||||||
|
case *types.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: " + t.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeScalarExec(t *types.ScalarTypeDefinition, 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 decode.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 types.FieldsDefinition, possibleTypes []*types.ObjectTypeDefinition,
|
||||||
|
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)
|
||||||
|
rt := unwrapPtr(resolverType)
|
||||||
|
fieldsCount := fieldCount(rt, map[string]int{})
|
||||||
|
for _, f := range fields {
|
||||||
|
var fieldIndex []int
|
||||||
|
methodIndex := findMethod(resolverType, f.Name)
|
||||||
|
if b.schema.UseFieldResolvers && methodIndex == -1 {
|
||||||
|
if fieldsCount[strings.ToLower(stripUnderscore(f.Name))] > 1 {
|
||||||
|
return nil, fmt.Errorf("%s does not resolve %q: ambiguous field %q", resolverType, typeName, f.Name)
|
||||||
|
}
|
||||||
|
fieldIndex = findField(rt, f.Name, []int{})
|
||||||
|
}
|
||||||
|
if methodIndex == -1 && len(fieldIndex) == 0 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
var m reflect.Method
|
||||||
|
var sf reflect.StructField
|
||||||
|
if methodIndex != -1 {
|
||||||
|
m = resolverType.Method(methodIndex)
|
||||||
|
} else {
|
||||||
|
sf = rt.FieldByIndex(fieldIndex)
|
||||||
|
}
|
||||||
|
fe, err := b.makeFieldExec(typeName, f, m, sf, methodIndex, fieldIndex, methodHasReceiver)
|
||||||
|
if err != nil {
|
||||||
|
var resolverName string
|
||||||
|
if methodIndex != -1 {
|
||||||
|
resolverName = m.Name
|
||||||
|
} else {
|
||||||
|
resolverName = sf.Name
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("%s\n\tused by (%s).%s", err, resolverType, resolverName)
|
||||||
|
}
|
||||||
|
Fields[f.Name] = fe
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check type assertions when
|
||||||
|
// 1) using method resolvers
|
||||||
|
// 2) Or resolver is not an interface type
|
||||||
|
typeAssertions := make(map[string]*TypeAssertion)
|
||||||
|
if !b.schema.UseFieldResolvers || resolverType.Kind() != reflect.Interface {
|
||||||
|
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 *types.FieldDefinition, m reflect.Method, sf reflect.StructField,
|
||||||
|
methodIndex int, fieldIndex []int, methodHasReceiver bool) (*Field, error) {
|
||||||
|
|
||||||
|
var argsPacker *packer.StructPacker
|
||||||
|
var hasError bool
|
||||||
|
var hasContext bool
|
||||||
|
|
||||||
|
// Validate resolver method only when there is one
|
||||||
|
if methodIndex != -1 {
|
||||||
|
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:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.Arguments) > 0 {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil, fmt.Errorf("must have parameter for field arguments")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
argsPacker, err = b.packerBuilder.MakeStructPacker(f.Arguments, in[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
in = in[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(in) > 0 {
|
||||||
|
return nil, fmt.Errorf("too many parameters")
|
||||||
|
}
|
||||||
|
|
||||||
|
maxNumOfReturns := 2
|
||||||
|
if m.Type.NumOut() < maxNumOfReturns-1 {
|
||||||
|
return nil, fmt.Errorf("too few return values")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Type.NumOut() > maxNumOfReturns {
|
||||||
|
return nil, fmt.Errorf("too many return values")
|
||||||
|
}
|
||||||
|
|
||||||
|
hasError = m.Type.NumOut() == maxNumOfReturns
|
||||||
|
if hasError {
|
||||||
|
if m.Type.Out(maxNumOfReturns-1) != errorType {
|
||||||
|
return nil, fmt.Errorf(`must have "error" as its last return value`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fe := &Field{
|
||||||
|
FieldDefinition: *f,
|
||||||
|
TypeName: typeName,
|
||||||
|
MethodIndex: methodIndex,
|
||||||
|
FieldIndex: fieldIndex,
|
||||||
|
HasContext: hasContext,
|
||||||
|
ArgsPacker: argsPacker,
|
||||||
|
HasError: hasError,
|
||||||
|
TraceLabel: fmt.Sprintf("GraphQL field: %s.%s", typeName, f.Name),
|
||||||
|
}
|
||||||
|
|
||||||
|
var out reflect.Type
|
||||||
|
if methodIndex != -1 {
|
||||||
|
out = m.Type.Out(0)
|
||||||
|
sub, ok := b.schema.EntryPoints["subscription"]
|
||||||
|
if ok && typeName == sub.TypeName() && out.Kind() == reflect.Chan {
|
||||||
|
out = m.Type.Out(0).Elem()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out = sf.Type
|
||||||
|
}
|
||||||
|
if err := b.assignExec(&fe.ValueExec, f.Type, out); 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 findField(t reflect.Type, name string, index []int) []int {
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
|
||||||
|
if field.Type.Kind() == reflect.Struct && field.Anonymous {
|
||||||
|
newIndex := findField(field.Type, name, []int{i})
|
||||||
|
if len(newIndex) > 1 {
|
||||||
|
return append(index, newIndex...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.EqualFold(stripUnderscore(name), stripUnderscore(field.Name)) {
|
||||||
|
return append(index, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldCount helps resolve ambiguity when more than one embedded struct contains fields with the same name.
|
||||||
|
func fieldCount(t reflect.Type, count map[string]int) map[string]int {
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
fieldName := strings.ToLower(stripUnderscore(field.Name))
|
||||||
|
|
||||||
|
if field.Type.Kind() == reflect.Struct && field.Anonymous {
|
||||||
|
count = fieldCount(field.Type, count)
|
||||||
|
} else {
|
||||||
|
if _, ok := count[fieldName]; !ok {
|
||||||
|
count[fieldName] = 0
|
||||||
|
}
|
||||||
|
count[fieldName]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func unwrapNonNull(t types.Type) (types.Type, bool) {
|
||||||
|
if nn, ok := t.(*types.NonNull); ok {
|
||||||
|
return nn.OfType, true
|
||||||
|
}
|
||||||
|
return t, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripUnderscore(s string) string {
|
||||||
|
return strings.Replace(s, "_", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unwrapPtr(t reflect.Type) reflect.Type {
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
return t.Elem()
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
269
vendor/github.com/graph-gophers/graphql-go/internal/exec/selected/selected.go
сгенерированный
поставляемый
Обычный файл
269
vendor/github.com/graph-gophers/graphql-go/internal/exec/selected/selected.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,269 @@
|
|||||||
|
package selected
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec/packer"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/query"
|
||||||
|
"github.com/graph-gophers/graphql-go/introspection"
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
Schema *types.Schema
|
||||||
|
Doc *types.ExecutableDefinition
|
||||||
|
Vars map[string]interface{}
|
||||||
|
Mu sync.Mutex
|
||||||
|
Errs []*errors.QueryError
|
||||||
|
DisableIntrospection bool
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *types.OperationDefinition) []Selection {
|
||||||
|
var obj *resolvable.Object
|
||||||
|
switch op.Type {
|
||||||
|
case query.Query:
|
||||||
|
obj = s.Query.(*resolvable.Object)
|
||||||
|
case query.Mutation:
|
||||||
|
obj = s.Mutation.(*resolvable.Object)
|
||||||
|
case query.Subscription:
|
||||||
|
obj = s.Subscription.(*resolvable.Object)
|
||||||
|
}
|
||||||
|
return applySelectionSet(r, s, 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, s *resolvable.Schema, e *resolvable.Object, sels []types.Selection) (flattenedSels []Selection) {
|
||||||
|
for _, sel := range sels {
|
||||||
|
switch sel := sel.(type) {
|
||||||
|
case *types.Field:
|
||||||
|
field := sel
|
||||||
|
if skipByDirective(r, field.Directives) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch field.Name.Name {
|
||||||
|
case "__typename":
|
||||||
|
// __typename is available even though r.DisableIntrospection == true
|
||||||
|
// because it is necessary when using union types and interfaces: https://graphql.org/learn/schema/#union-types
|
||||||
|
flattenedSels = append(flattenedSels, &TypenameField{
|
||||||
|
Object: *e,
|
||||||
|
Alias: field.Alias.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
case "__schema":
|
||||||
|
if !r.DisableIntrospection {
|
||||||
|
flattenedSels = append(flattenedSels, &SchemaField{
|
||||||
|
Field: s.Meta.FieldSchema,
|
||||||
|
Alias: field.Alias.Name,
|
||||||
|
Sels: applySelectionSet(r, s, s.Meta.Schema, field.SelectionSet),
|
||||||
|
Async: true,
|
||||||
|
FixedResult: reflect.ValueOf(introspection.WrapSchema(r.Schema)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
case "__type":
|
||||||
|
if !r.DisableIntrospection {
|
||||||
|
p := packer.ValuePacker{ValueType: reflect.TypeOf("")}
|
||||||
|
v, err := p.Pack(field.Arguments.MustGet("name").Deserialize(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: s.Meta.FieldType,
|
||||||
|
Alias: field.Alias.Name,
|
||||||
|
Sels: applySelectionSet(r, s, s.Meta.Type, field.SelectionSet),
|
||||||
|
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.Deserialize(r.Vars)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
packedArgs, err = fe.ArgsPacker.Pack(args)
|
||||||
|
if err != nil {
|
||||||
|
r.AddError(errors.Errorf("%s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldSels := applyField(r, s, fe.ValueExec, field.SelectionSet)
|
||||||
|
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 *types.InlineFragment:
|
||||||
|
frag := sel
|
||||||
|
if skipByDirective(r, frag.Directives) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
flattenedSels = append(flattenedSels, applyFragment(r, s, e, &frag.Fragment)...)
|
||||||
|
|
||||||
|
case *types.FragmentSpread:
|
||||||
|
spread := sel
|
||||||
|
if skipByDirective(r, spread.Directives) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
flattenedSels = append(flattenedSels, applyFragment(r, s, e, &r.Doc.Fragments.Get(spread.Name.Name).Fragment)...)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("invalid type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyFragment(r *Request, s *resolvable.Schema, e *resolvable.Object, frag *types.Fragment) []Selection {
|
||||||
|
if frag.On.Name != e.Name {
|
||||||
|
t := r.Schema.Resolve(frag.On.Name)
|
||||||
|
face, ok := t.(*types.InterfaceTypeDefinition)
|
||||||
|
if !ok && frag.On.Name != "" {
|
||||||
|
a, ok2 := e.TypeAssertions[frag.On.Name]
|
||||||
|
if !ok2 {
|
||||||
|
panic(fmt.Errorf("%q does not implement %q", frag.On, e.Name)) // TODO proper error handling
|
||||||
|
}
|
||||||
|
|
||||||
|
return []Selection{&TypeAssertion{
|
||||||
|
TypeAssertion: *a,
|
||||||
|
Sels: applySelectionSet(r, s, a.TypeExec.(*resolvable.Object), frag.Selections),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
if ok && len(face.PossibleTypes) > 0 {
|
||||||
|
sels := []Selection{}
|
||||||
|
for _, t := range face.PossibleTypes {
|
||||||
|
if t.Name == e.Name {
|
||||||
|
return applySelectionSet(r, s, e, frag.Selections)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a, ok := e.TypeAssertions[t.Name]; ok {
|
||||||
|
sels = append(sels, &TypeAssertion{
|
||||||
|
TypeAssertion: *a,
|
||||||
|
Sels: applySelectionSet(r, s, a.TypeExec.(*resolvable.Object), frag.Selections),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(sels) == 0 {
|
||||||
|
panic(fmt.Errorf("%q does not implement %q", e.Name, frag.On)) // TODO proper error handling
|
||||||
|
}
|
||||||
|
return sels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return applySelectionSet(r, s, e, frag.Selections)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyField(r *Request, s *resolvable.Schema, e resolvable.Resolvable, sels []types.Selection) []Selection {
|
||||||
|
switch e := e.(type) {
|
||||||
|
case *resolvable.Object:
|
||||||
|
return applySelectionSet(r, s, e, sels)
|
||||||
|
case *resolvable.List:
|
||||||
|
return applyField(r, s, e.Elem, sels)
|
||||||
|
case *resolvable.Scalar:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipByDirective(r *Request, directives types.DirectiveList) bool {
|
||||||
|
if d := directives.Get("skip"); d != nil {
|
||||||
|
p := packer.ValuePacker{ValueType: reflect.TypeOf(false)}
|
||||||
|
v, err := p.Pack(d.Arguments.MustGet("if").Deserialize(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.Arguments.MustGet("if").Deserialize(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
|
||||||
|
}
|
||||||
179
vendor/github.com/graph-gophers/graphql-go/internal/exec/subscribe.go
сгенерированный
поставляемый
Обычный файл
179
vendor/github.com/graph-gophers/graphql-go/internal/exec/subscribe.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,179 @@
|
|||||||
|
package exec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec/selected"
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Data json.RawMessage
|
||||||
|
Errors []*errors.QueryError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) Subscribe(ctx context.Context, s *resolvable.Schema, op *types.OperationDefinition) <-chan *Response {
|
||||||
|
var result reflect.Value
|
||||||
|
var f *fieldToExec
|
||||||
|
var err *errors.QueryError
|
||||||
|
func() {
|
||||||
|
defer r.handlePanic(ctx)
|
||||||
|
|
||||||
|
sels := selected.ApplyOperation(&r.Request, s, op)
|
||||||
|
var fields []*fieldToExec
|
||||||
|
collectFieldsToResolve(sels, s, s.Resolver, &fields, make(map[string]*fieldToExec))
|
||||||
|
|
||||||
|
// TODO: move this check into validation.Validate
|
||||||
|
if len(fields) != 1 {
|
||||||
|
err = errors.Errorf("%s", "can subscribe to at most one subscription at a time")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f = fields[0]
|
||||||
|
|
||||||
|
var in []reflect.Value
|
||||||
|
if f.field.HasContext {
|
||||||
|
in = append(in, reflect.ValueOf(ctx))
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
switch resolverErr := callOut[1].Interface().(type) {
|
||||||
|
case *errors.QueryError:
|
||||||
|
err = resolverErr
|
||||||
|
case error:
|
||||||
|
err = errors.Errorf("%s", resolverErr)
|
||||||
|
err.ResolverError = resolverErr
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("can only deal with *QueryError and error types, got %T", resolverErr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Handles the case where the locally executed func above panicked
|
||||||
|
if len(r.Request.Errs) > 0 {
|
||||||
|
return sendAndReturnClosed(&Response{Errors: r.Request.Errs})
|
||||||
|
}
|
||||||
|
|
||||||
|
if f == nil {
|
||||||
|
return sendAndReturnClosed(&Response{Errors: []*errors.QueryError{err}})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if _, nonNullChild := f.field.Type.(*types.NonNull); nonNullChild {
|
||||||
|
return sendAndReturnClosed(&Response{Errors: []*errors.QueryError{err}})
|
||||||
|
}
|
||||||
|
return sendAndReturnClosed(&Response{Data: []byte(fmt.Sprintf(`{"%s":null}`, f.field.Alias)), Errors: []*errors.QueryError{err}})
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||||
|
return sendAndReturnClosed(&Response{Errors: []*errors.QueryError{errors.Errorf("%s", ctxErr)}})
|
||||||
|
}
|
||||||
|
|
||||||
|
c := make(chan *Response)
|
||||||
|
// TODO: handle resolver nil channel better?
|
||||||
|
if result.IsZero() {
|
||||||
|
close(c)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
// Check subscription context
|
||||||
|
chosen, resp, ok := reflect.Select([]reflect.SelectCase{
|
||||||
|
{
|
||||||
|
Dir: reflect.SelectRecv,
|
||||||
|
Chan: reflect.ValueOf(ctx.Done()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Dir: reflect.SelectRecv,
|
||||||
|
Chan: result,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
switch chosen {
|
||||||
|
// subscription context done
|
||||||
|
case 0:
|
||||||
|
close(c)
|
||||||
|
return
|
||||||
|
// upstream received
|
||||||
|
case 1:
|
||||||
|
// upstream closed
|
||||||
|
if !ok {
|
||||||
|
close(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
subR := &Request{
|
||||||
|
Request: selected.Request{
|
||||||
|
Doc: r.Request.Doc,
|
||||||
|
Vars: r.Request.Vars,
|
||||||
|
Schema: r.Request.Schema,
|
||||||
|
},
|
||||||
|
Limiter: r.Limiter,
|
||||||
|
Tracer: r.Tracer,
|
||||||
|
Logger: r.Logger,
|
||||||
|
}
|
||||||
|
var out bytes.Buffer
|
||||||
|
func() {
|
||||||
|
timeout := r.SubscribeResolverTimeout
|
||||||
|
if timeout == 0 {
|
||||||
|
timeout = time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
subCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// resolve response
|
||||||
|
func() {
|
||||||
|
defer subR.handlePanic(subCtx)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
subR.execSelectionSet(subCtx, f.sels, f.field.Type, &pathSegment{nil, f.field.Alias}, s, resp, &buf)
|
||||||
|
|
||||||
|
propagateChildError := false
|
||||||
|
if _, nonNullChild := f.field.Type.(*types.NonNull); nonNullChild && resolvedToNull(&buf) {
|
||||||
|
propagateChildError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !propagateChildError {
|
||||||
|
out.WriteString(fmt.Sprintf(`{"%s":`, f.field.Alias))
|
||||||
|
out.Write(buf.Bytes())
|
||||||
|
out.WriteString(`}`)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := subCtx.Err(); err != nil {
|
||||||
|
c <- &Response{Errors: []*errors.QueryError{errors.Errorf("%s", err)}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send response within timeout
|
||||||
|
// TODO: maybe block until sent?
|
||||||
|
select {
|
||||||
|
case <-subCtx.Done():
|
||||||
|
case c <- &Response{Data: out.Bytes(), Errors: subR.Errs}:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendAndReturnClosed(resp *Response) chan *Response {
|
||||||
|
c := make(chan *Response, 1)
|
||||||
|
c <- resp
|
||||||
|
close(c)
|
||||||
|
return c
|
||||||
|
}
|
||||||
156
vendor/github.com/graph-gophers/graphql-go/internal/query/query.go
сгенерированный
поставляемый
Обычный файл
156
vendor/github.com/graph-gophers/graphql-go/internal/query/query.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,156 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"text/scanner"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/common"
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Query types.OperationType = "QUERY"
|
||||||
|
Mutation types.OperationType = "MUTATION"
|
||||||
|
Subscription types.OperationType = "SUBSCRIPTION"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Parse(queryString string) (*types.ExecutableDefinition, *errors.QueryError) {
|
||||||
|
l := common.NewLexer(queryString, false)
|
||||||
|
|
||||||
|
var execDef *types.ExecutableDefinition
|
||||||
|
err := l.CatchSyntaxError(func() { execDef = parseExecutableDefinition(l) })
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return execDef, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseExecutableDefinition(l *common.Lexer) *types.ExecutableDefinition {
|
||||||
|
ed := &types.ExecutableDefinition{}
|
||||||
|
l.ConsumeWhitespace()
|
||||||
|
for l.Peek() != scanner.EOF {
|
||||||
|
if l.Peek() == '{' {
|
||||||
|
op := &types.OperationDefinition{Type: Query, Loc: l.Location()}
|
||||||
|
op.Selections = parseSelectionSet(l)
|
||||||
|
ed.Operations = append(ed.Operations, op)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
loc := l.Location()
|
||||||
|
switch x := l.ConsumeIdent(); x {
|
||||||
|
case "query":
|
||||||
|
op := parseOperation(l, Query)
|
||||||
|
op.Loc = loc
|
||||||
|
ed.Operations = append(ed.Operations, op)
|
||||||
|
|
||||||
|
case "mutation":
|
||||||
|
ed.Operations = append(ed.Operations, parseOperation(l, Mutation))
|
||||||
|
|
||||||
|
case "subscription":
|
||||||
|
ed.Operations = append(ed.Operations, parseOperation(l, Subscription))
|
||||||
|
|
||||||
|
case "fragment":
|
||||||
|
frag := parseFragment(l)
|
||||||
|
frag.Loc = loc
|
||||||
|
ed.Fragments = append(ed.Fragments, frag)
|
||||||
|
|
||||||
|
default:
|
||||||
|
l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "fragment"`, x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ed
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOperation(l *common.Lexer, opType types.OperationType) *types.OperationDefinition {
|
||||||
|
op := &types.OperationDefinition{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) *types.FragmentDefinition {
|
||||||
|
f := &types.FragmentDefinition{}
|
||||||
|
f.Name = l.ConsumeIdentWithLoc()
|
||||||
|
l.ConsumeKeyword("on")
|
||||||
|
f.On = types.TypeName{Ident: l.ConsumeIdentWithLoc()}
|
||||||
|
f.Directives = common.ParseDirectives(l)
|
||||||
|
f.Selections = parseSelectionSet(l)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSelectionSet(l *common.Lexer) []types.Selection {
|
||||||
|
var sels []types.Selection
|
||||||
|
l.ConsumeToken('{')
|
||||||
|
for l.Peek() != '}' {
|
||||||
|
sels = append(sels, parseSelection(l))
|
||||||
|
}
|
||||||
|
l.ConsumeToken('}')
|
||||||
|
return sels
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSelection(l *common.Lexer) types.Selection {
|
||||||
|
if l.Peek() == '.' {
|
||||||
|
return parseSpread(l)
|
||||||
|
}
|
||||||
|
return parseFieldDef(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFieldDef(l *common.Lexer) *types.Field {
|
||||||
|
f := &types.Field{}
|
||||||
|
f.Alias = l.ConsumeIdentWithLoc()
|
||||||
|
f.Name = f.Alias
|
||||||
|
if l.Peek() == ':' {
|
||||||
|
l.ConsumeToken(':')
|
||||||
|
f.Name = l.ConsumeIdentWithLoc()
|
||||||
|
}
|
||||||
|
if l.Peek() == '(' {
|
||||||
|
f.Arguments = common.ParseArgumentList(l)
|
||||||
|
}
|
||||||
|
f.Directives = common.ParseDirectives(l)
|
||||||
|
if l.Peek() == '{' {
|
||||||
|
f.SelectionSetLoc = l.Location()
|
||||||
|
f.SelectionSet = parseSelectionSet(l)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSpread(l *common.Lexer) types.Selection {
|
||||||
|
loc := l.Location()
|
||||||
|
l.ConsumeToken('.')
|
||||||
|
l.ConsumeToken('.')
|
||||||
|
l.ConsumeToken('.')
|
||||||
|
|
||||||
|
f := &types.InlineFragment{Loc: loc}
|
||||||
|
if l.Peek() == scanner.Ident {
|
||||||
|
ident := l.ConsumeIdentWithLoc()
|
||||||
|
if ident.Name != "on" {
|
||||||
|
fs := &types.FragmentSpread{
|
||||||
|
Name: ident,
|
||||||
|
Loc: loc,
|
||||||
|
}
|
||||||
|
fs.Directives = common.ParseDirectives(l)
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
f.On = types.TypeName{Ident: l.ConsumeIdentWithLoc()}
|
||||||
|
}
|
||||||
|
f.Directives = common.ParseDirectives(l)
|
||||||
|
f.Selections = parseSelectionSet(l)
|
||||||
|
return f
|
||||||
|
}
|
||||||
203
vendor/github.com/graph-gophers/graphql-go/internal/schema/meta.go
сгенерированный
поставляемый
Обычный файл
203
vendor/github.com/graph-gophers/graphql-go/internal/schema/meta.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,203 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
_ = newMeta()
|
||||||
|
}
|
||||||
|
|
||||||
|
// newMeta initializes an instance of the meta Schema.
|
||||||
|
func newMeta() *types.Schema {
|
||||||
|
s := &types.Schema{
|
||||||
|
EntryPointNames: make(map[string]string),
|
||||||
|
Types: make(map[string]types.NamedType),
|
||||||
|
Directives: make(map[string]*types.DirectiveDefinition),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := Parse(s, metaSrc, false)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
`
|
||||||
586
vendor/github.com/graph-gophers/graphql-go/internal/schema/schema.go
сгенерированный
поставляемый
Обычный файл
586
vendor/github.com/graph-gophers/graphql-go/internal/schema/schema.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,586 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"text/scanner"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/common"
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New initializes an instance of Schema.
|
||||||
|
func New() *types.Schema {
|
||||||
|
s := &types.Schema{
|
||||||
|
EntryPointNames: make(map[string]string),
|
||||||
|
Types: make(map[string]types.NamedType),
|
||||||
|
Directives: make(map[string]*types.DirectiveDefinition),
|
||||||
|
}
|
||||||
|
m := newMeta()
|
||||||
|
for n, t := range m.Types {
|
||||||
|
s.Types[n] = t
|
||||||
|
}
|
||||||
|
for n, d := range m.Directives {
|
||||||
|
s.Directives[n] = d
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(s *types.Schema, schemaString string, useStringDescriptions bool) error {
|
||||||
|
l := common.NewLexer(schemaString, useStringDescriptions)
|
||||||
|
err := l.CatchSyntaxError(func() { parseSchema(s, l) })
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mergeExtensions(s); 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.Arguments {
|
||||||
|
t, err := common.ResolveType(arg.Type, s.Resolve)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
arg.Type = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://graphql.github.io/graphql-spec/June2018/#sec-Root-Operation-Types
|
||||||
|
// > While any type can be the root operation type for a GraphQL operation, the type system definition language can
|
||||||
|
// > omit the schema definition when the query, mutation, and subscription root types are named Query, Mutation,
|
||||||
|
// > and Subscription respectively.
|
||||||
|
if len(s.EntryPointNames) == 0 {
|
||||||
|
if _, ok := s.Types["Query"]; ok {
|
||||||
|
s.EntryPointNames["query"] = "Query"
|
||||||
|
}
|
||||||
|
if _, ok := s.Types["Mutation"]; ok {
|
||||||
|
s.EntryPointNames["mutation"] = "Mutation"
|
||||||
|
}
|
||||||
|
if _, ok := s.Types["Subscription"]; ok {
|
||||||
|
s.EntryPointNames["subscription"] = "Subscription"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.EntryPoints = make(map[string]types.NamedType)
|
||||||
|
for key, name := range s.EntryPointNames {
|
||||||
|
t, ok := s.Types[name]
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("type %q not found", name)
|
||||||
|
}
|
||||||
|
s.EntryPoints[key] = t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface types need validation: https://spec.graphql.org/draft/#sec-Interfaces.Interfaces-Implementing-Interfaces
|
||||||
|
for _, typeDef := range s.Types {
|
||||||
|
switch t := typeDef.(type) {
|
||||||
|
case *types.InterfaceTypeDefinition:
|
||||||
|
for i, implements := range t.Interfaces {
|
||||||
|
typ, ok := s.Types[implements.Name]
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("interface %q not found", implements)
|
||||||
|
}
|
||||||
|
inteface, ok := typ.(*types.InterfaceTypeDefinition)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("type %q is not an interface", inteface)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range inteface.Fields.Names() {
|
||||||
|
if t.Fields.Get(f) == nil {
|
||||||
|
return errors.Errorf("interface %q expects field %q but %q does not provide it", inteface.Name, f, t.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Interfaces[i] = inteface
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, obj := range s.Objects {
|
||||||
|
obj.Interfaces = make([]*types.InterfaceTypeDefinition, len(obj.InterfaceNames))
|
||||||
|
if err := resolveDirectives(s, obj.Directives, "OBJECT"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, field := range obj.Fields {
|
||||||
|
if err := resolveDirectives(s, field.Directives, "FIELD_DEFINITION"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, intfName := range obj.InterfaceNames {
|
||||||
|
t, ok := s.Types[intfName]
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("interface %q not found", intfName)
|
||||||
|
}
|
||||||
|
intf, ok := t.(*types.InterfaceTypeDefinition)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("type %q is not an interface", intfName)
|
||||||
|
}
|
||||||
|
for _, f := range intf.Fields.Names() {
|
||||||
|
if obj.Fields.Get(f) == nil {
|
||||||
|
return errors.Errorf("interface %q expects field %q but %q does not provide it", intfName, f, obj.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.Interfaces[i] = intf
|
||||||
|
intf.PossibleTypes = append(intf.PossibleTypes, obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, union := range s.Unions {
|
||||||
|
if err := resolveDirectives(s, union.Directives, "UNION"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
union.UnionMemberTypes = make([]*types.ObjectTypeDefinition, 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.(*types.ObjectTypeDefinition)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("type %q is not an object", name)
|
||||||
|
}
|
||||||
|
union.UnionMemberTypes[i] = obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, enum := range s.Enums {
|
||||||
|
if err := resolveDirectives(s, enum.Directives, "ENUM"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, value := range enum.EnumValuesDefinition {
|
||||||
|
if err := resolveDirectives(s, value.Directives, "ENUM_VALUE"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseSchema(schemaString string, useStringDescriptions bool) (*types.Schema, error) {
|
||||||
|
s := New()
|
||||||
|
err := Parse(s, schemaString, useStringDescriptions)
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeExtensions(s *types.Schema) error {
|
||||||
|
for _, ext := range s.Extensions {
|
||||||
|
typ := s.Types[ext.Type.TypeName()]
|
||||||
|
if typ == nil {
|
||||||
|
return fmt.Errorf("trying to extend unknown type %q", ext.Type.TypeName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if typ.Kind() != ext.Type.Kind() {
|
||||||
|
return fmt.Errorf("trying to extend type %q with type %q", typ.Kind(), ext.Type.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
switch og := typ.(type) {
|
||||||
|
case *types.ObjectTypeDefinition:
|
||||||
|
e := ext.Type.(*types.ObjectTypeDefinition)
|
||||||
|
|
||||||
|
for _, field := range e.Fields {
|
||||||
|
if og.Fields.Get(field.Name) != nil {
|
||||||
|
return fmt.Errorf("extended field %q already exists", field.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
og.Fields = append(og.Fields, e.Fields...)
|
||||||
|
|
||||||
|
for _, en := range e.InterfaceNames {
|
||||||
|
for _, on := range og.InterfaceNames {
|
||||||
|
if on == en {
|
||||||
|
return fmt.Errorf("interface %q implemented in the extension is already implemented in %q", on, og.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
og.InterfaceNames = append(og.InterfaceNames, e.InterfaceNames...)
|
||||||
|
|
||||||
|
case *types.InputObject:
|
||||||
|
e := ext.Type.(*types.InputObject)
|
||||||
|
|
||||||
|
for _, field := range e.Values {
|
||||||
|
if og.Values.Get(field.Name.Name) != nil {
|
||||||
|
return fmt.Errorf("extended field %q already exists", field.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
og.Values = append(og.Values, e.Values...)
|
||||||
|
|
||||||
|
case *types.InterfaceTypeDefinition:
|
||||||
|
e := ext.Type.(*types.InterfaceTypeDefinition)
|
||||||
|
|
||||||
|
for _, field := range e.Fields {
|
||||||
|
if og.Fields.Get(field.Name) != nil {
|
||||||
|
return fmt.Errorf("extended field %s already exists", field.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
og.Fields = append(og.Fields, e.Fields...)
|
||||||
|
|
||||||
|
case *types.Union:
|
||||||
|
e := ext.Type.(*types.Union)
|
||||||
|
|
||||||
|
for _, en := range e.TypeNames {
|
||||||
|
for _, on := range og.TypeNames {
|
||||||
|
if on == en {
|
||||||
|
return fmt.Errorf("union type %q already declared in %q", on, og.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
og.TypeNames = append(og.TypeNames, e.TypeNames...)
|
||||||
|
|
||||||
|
case *types.EnumTypeDefinition:
|
||||||
|
e := ext.Type.(*types.EnumTypeDefinition)
|
||||||
|
|
||||||
|
for _, en := range e.EnumValuesDefinition {
|
||||||
|
for _, on := range og.EnumValuesDefinition {
|
||||||
|
if on.EnumValue == en.EnumValue {
|
||||||
|
return fmt.Errorf("enum value %q already declared in %q", on.EnumValue, og.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
og.EnumValuesDefinition = append(og.EnumValuesDefinition, e.EnumValuesDefinition...)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union" or "input"`, og.TypeName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveNamedType(s *types.Schema, t types.NamedType) error {
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.ObjectTypeDefinition:
|
||||||
|
for _, f := range t.Fields {
|
||||||
|
if err := resolveField(s, f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *types.InterfaceTypeDefinition:
|
||||||
|
for _, f := range t.Fields {
|
||||||
|
if err := resolveField(s, f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *types.InputObject:
|
||||||
|
if err := resolveInputObject(s, t.Values); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveField(s *types.Schema, f *types.FieldDefinition) error {
|
||||||
|
t, err := common.ResolveType(f.Type, s.Resolve)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.Type = t
|
||||||
|
if err := resolveDirectives(s, f.Directives, "FIELD_DEFINITION"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return resolveInputObject(s, f.Arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveDirectives(s *types.Schema, directives types.DirectiveList, loc string) error {
|
||||||
|
for _, d := range directives {
|
||||||
|
dirName := d.Name.Name
|
||||||
|
dd, ok := s.Directives[dirName]
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("directive %q not found", dirName)
|
||||||
|
}
|
||||||
|
validLoc := false
|
||||||
|
for _, l := range dd.Locations {
|
||||||
|
if l == loc {
|
||||||
|
validLoc = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !validLoc {
|
||||||
|
return errors.Errorf("invalid location %q for directive %q (must be one of %v)", loc, dirName, dd.Locations)
|
||||||
|
}
|
||||||
|
for _, arg := range d.Arguments {
|
||||||
|
if dd.Arguments.Get(arg.Name.Name) == nil {
|
||||||
|
return errors.Errorf("invalid argument %q for directive %q", arg.Name.Name, dirName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, arg := range dd.Arguments {
|
||||||
|
if _, ok := d.Arguments.Get(arg.Name.Name); !ok {
|
||||||
|
d.Arguments = append(d.Arguments, &types.Argument{Name: arg.Name, Value: arg.Default})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveInputObject(s *types.Schema, values types.ArgumentsDefinition) 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 *types.Schema, l *common.Lexer) {
|
||||||
|
l.ConsumeWhitespace()
|
||||||
|
|
||||||
|
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 := parseObjectDef(l)
|
||||||
|
obj.Desc = desc
|
||||||
|
s.Types[obj.Name] = obj
|
||||||
|
s.Objects = append(s.Objects, obj)
|
||||||
|
|
||||||
|
case "interface":
|
||||||
|
iface := parseInterfaceDef(l)
|
||||||
|
iface.Desc = desc
|
||||||
|
s.Types[iface.Name] = iface
|
||||||
|
|
||||||
|
case "union":
|
||||||
|
union := parseUnionDef(l)
|
||||||
|
union.Desc = desc
|
||||||
|
s.Types[union.Name] = union
|
||||||
|
s.Unions = append(s.Unions, union)
|
||||||
|
|
||||||
|
case "enum":
|
||||||
|
enum := parseEnumDef(l)
|
||||||
|
enum.Desc = desc
|
||||||
|
s.Types[enum.Name] = enum
|
||||||
|
s.Enums = append(s.Enums, enum)
|
||||||
|
|
||||||
|
case "input":
|
||||||
|
input := parseInputDef(l)
|
||||||
|
input.Desc = desc
|
||||||
|
s.Types[input.Name] = input
|
||||||
|
|
||||||
|
case "scalar":
|
||||||
|
loc := l.Location()
|
||||||
|
name := l.ConsumeIdent()
|
||||||
|
directives := common.ParseDirectives(l)
|
||||||
|
s.Types[name] = &types.ScalarTypeDefinition{Name: name, Desc: desc, Directives: directives, Loc: loc}
|
||||||
|
|
||||||
|
case "directive":
|
||||||
|
directive := parseDirectiveDef(l)
|
||||||
|
directive.Desc = desc
|
||||||
|
s.Directives[directive.Name] = directive
|
||||||
|
|
||||||
|
case "extend":
|
||||||
|
parseExtension(s, l)
|
||||||
|
|
||||||
|
default:
|
||||||
|
// TODO: Add support for type extensions.
|
||||||
|
l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union", "input", "scalar" or "directive"`, x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseObjectDef(l *common.Lexer) *types.ObjectTypeDefinition {
|
||||||
|
object := &types.ObjectTypeDefinition{Loc: l.Location(), Name: l.ConsumeIdent()}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if l.Peek() == '{' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.Peek() == '@' {
|
||||||
|
object.Directives = common.ParseDirectives(l)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.Peek() == scanner.Ident {
|
||||||
|
l.ConsumeKeyword("implements")
|
||||||
|
|
||||||
|
for l.Peek() != '{' && l.Peek() != '@' {
|
||||||
|
if l.Peek() == '&' {
|
||||||
|
l.ConsumeToken('&')
|
||||||
|
}
|
||||||
|
|
||||||
|
object.InterfaceNames = append(object.InterfaceNames, l.ConsumeIdent())
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
l.ConsumeToken('{')
|
||||||
|
object.Fields = parseFieldsDef(l)
|
||||||
|
l.ConsumeToken('}')
|
||||||
|
|
||||||
|
return object
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInterfaceDef(l *common.Lexer) *types.InterfaceTypeDefinition {
|
||||||
|
i := &types.InterfaceTypeDefinition{Loc: l.Location(), Name: l.ConsumeIdent()}
|
||||||
|
|
||||||
|
if l.Peek() == scanner.Ident {
|
||||||
|
l.ConsumeKeyword("implements")
|
||||||
|
i.Interfaces = append(i.Interfaces, &types.InterfaceTypeDefinition{Name: l.ConsumeIdent()})
|
||||||
|
|
||||||
|
for l.Peek() == '&' {
|
||||||
|
l.ConsumeToken('&')
|
||||||
|
i.Interfaces = append(i.Interfaces, &types.InterfaceTypeDefinition{Name: l.ConsumeIdent()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Directives = common.ParseDirectives(l)
|
||||||
|
|
||||||
|
l.ConsumeToken('{')
|
||||||
|
i.Fields = parseFieldsDef(l)
|
||||||
|
l.ConsumeToken('}')
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUnionDef(l *common.Lexer) *types.Union {
|
||||||
|
union := &types.Union{Loc: l.Location(), Name: l.ConsumeIdent()}
|
||||||
|
|
||||||
|
union.Directives = common.ParseDirectives(l)
|
||||||
|
l.ConsumeToken('=')
|
||||||
|
union.TypeNames = []string{l.ConsumeIdent()}
|
||||||
|
for l.Peek() == '|' {
|
||||||
|
l.ConsumeToken('|')
|
||||||
|
union.TypeNames = append(union.TypeNames, l.ConsumeIdent())
|
||||||
|
}
|
||||||
|
|
||||||
|
return union
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInputDef(l *common.Lexer) *types.InputObject {
|
||||||
|
i := &types.InputObject{}
|
||||||
|
i.Loc = l.Location()
|
||||||
|
i.Name = l.ConsumeIdent()
|
||||||
|
i.Directives = common.ParseDirectives(l)
|
||||||
|
l.ConsumeToken('{')
|
||||||
|
for l.Peek() != '}' {
|
||||||
|
i.Values = append(i.Values, common.ParseInputValue(l))
|
||||||
|
}
|
||||||
|
l.ConsumeToken('}')
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEnumDef(l *common.Lexer) *types.EnumTypeDefinition {
|
||||||
|
enum := &types.EnumTypeDefinition{Loc: l.Location(), Name: l.ConsumeIdent()}
|
||||||
|
|
||||||
|
enum.Directives = common.ParseDirectives(l)
|
||||||
|
l.ConsumeToken('{')
|
||||||
|
for l.Peek() != '}' {
|
||||||
|
v := &types.EnumValueDefinition{
|
||||||
|
Desc: l.DescComment(),
|
||||||
|
Loc: l.Location(),
|
||||||
|
EnumValue: l.ConsumeIdent(),
|
||||||
|
Directives: common.ParseDirectives(l),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum.EnumValuesDefinition = append(enum.EnumValuesDefinition, v)
|
||||||
|
}
|
||||||
|
l.ConsumeToken('}')
|
||||||
|
return enum
|
||||||
|
}
|
||||||
|
func parseDirectiveDef(l *common.Lexer) *types.DirectiveDefinition {
|
||||||
|
l.ConsumeToken('@')
|
||||||
|
loc := l.Location()
|
||||||
|
d := &types.DirectiveDefinition{Name: l.ConsumeIdent(), Loc: loc}
|
||||||
|
|
||||||
|
if l.Peek() == '(' {
|
||||||
|
l.ConsumeToken('(')
|
||||||
|
for l.Peek() != ')' {
|
||||||
|
v := common.ParseInputValue(l)
|
||||||
|
d.Arguments = append(d.Arguments, v)
|
||||||
|
}
|
||||||
|
l.ConsumeToken(')')
|
||||||
|
}
|
||||||
|
|
||||||
|
l.ConsumeKeyword("on")
|
||||||
|
|
||||||
|
for {
|
||||||
|
loc := l.ConsumeIdent()
|
||||||
|
d.Locations = append(d.Locations, loc)
|
||||||
|
if l.Peek() != '|' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
l.ConsumeToken('|')
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseExtension(s *types.Schema, l *common.Lexer) {
|
||||||
|
loc := l.Location()
|
||||||
|
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 := parseObjectDef(l)
|
||||||
|
s.Extensions = append(s.Extensions, &types.Extension{Type: obj, Loc: loc})
|
||||||
|
|
||||||
|
case "interface":
|
||||||
|
iface := parseInterfaceDef(l)
|
||||||
|
s.Extensions = append(s.Extensions, &types.Extension{Type: iface, Loc: loc})
|
||||||
|
|
||||||
|
case "union":
|
||||||
|
union := parseUnionDef(l)
|
||||||
|
s.Extensions = append(s.Extensions, &types.Extension{Type: union, Loc: loc})
|
||||||
|
|
||||||
|
case "enum":
|
||||||
|
enum := parseEnumDef(l)
|
||||||
|
s.Extensions = append(s.Extensions, &types.Extension{Type: enum, Loc: loc})
|
||||||
|
|
||||||
|
case "input":
|
||||||
|
input := parseInputDef(l)
|
||||||
|
s.Extensions = append(s.Extensions, &types.Extension{Type: input, Loc: loc})
|
||||||
|
|
||||||
|
default:
|
||||||
|
// TODO: Add ScalarTypeDefinition when adding directives
|
||||||
|
l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union" or "input"`, x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFieldsDef(l *common.Lexer) types.FieldsDefinition {
|
||||||
|
var fields types.FieldsDefinition
|
||||||
|
for l.Peek() != '}' {
|
||||||
|
f := &types.FieldDefinition{}
|
||||||
|
f.Desc = l.DescComment()
|
||||||
|
f.Loc = l.Location()
|
||||||
|
f.Name = l.ConsumeIdent()
|
||||||
|
if l.Peek() == '(' {
|
||||||
|
l.ConsumeToken('(')
|
||||||
|
for l.Peek() != ')' {
|
||||||
|
f.Arguments = append(f.Arguments, common.ParseInputValue(l))
|
||||||
|
}
|
||||||
|
l.ConsumeToken(')')
|
||||||
|
}
|
||||||
|
l.ConsumeToken(':')
|
||||||
|
f.Type = common.ParseType(l)
|
||||||
|
f.Directives = common.ParseDirectives(l)
|
||||||
|
fields = append(fields, f)
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
71
vendor/github.com/graph-gophers/graphql-go/internal/validation/suggestion.go
сгенерированный
поставляемый
Обычный файл
71
vendor/github.com/graph-gophers/graphql-go/internal/validation/suggestion.go
сгенерированный
поставляемый
Обычный файл
@@ -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
|
||||||
|
}
|
||||||
980
vendor/github.com/graph-gophers/graphql-go/internal/validation/validation.go
сгенерированный
поставляемый
Обычный файл
980
vendor/github.com/graph-gophers/graphql-go/internal/validation/validation.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,980 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/scanner"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/common"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/query"
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type varSet map[*types.InputValueDefinition]struct{}
|
||||||
|
|
||||||
|
type selectionPair struct{ a, b types.Selection }
|
||||||
|
|
||||||
|
type nameSet map[string]errors.Location
|
||||||
|
|
||||||
|
type fieldInfo struct {
|
||||||
|
sf *types.FieldDefinition
|
||||||
|
parent types.NamedType
|
||||||
|
}
|
||||||
|
|
||||||
|
type context struct {
|
||||||
|
schema *types.Schema
|
||||||
|
doc *types.ExecutableDefinition
|
||||||
|
errs []*errors.QueryError
|
||||||
|
opErrs map[*types.OperationDefinition][]*errors.QueryError
|
||||||
|
usedVars map[*types.OperationDefinition]varSet
|
||||||
|
fieldMap map[*types.Field]fieldInfo
|
||||||
|
overlapValidated map[selectionPair]struct{}
|
||||||
|
maxDepth int
|
||||||
|
}
|
||||||
|
|
||||||
|
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 []*types.OperationDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
func newContext(s *types.Schema, doc *types.ExecutableDefinition, maxDepth int) *context {
|
||||||
|
return &context{
|
||||||
|
schema: s,
|
||||||
|
doc: doc,
|
||||||
|
opErrs: make(map[*types.OperationDefinition][]*errors.QueryError),
|
||||||
|
usedVars: make(map[*types.OperationDefinition]varSet),
|
||||||
|
fieldMap: make(map[*types.Field]fieldInfo),
|
||||||
|
overlapValidated: make(map[selectionPair]struct{}),
|
||||||
|
maxDepth: maxDepth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Validate(s *types.Schema, doc *types.ExecutableDefinition, variables map[string]interface{}, maxDepth int) []*errors.QueryError {
|
||||||
|
c := newContext(s, doc, maxDepth)
|
||||||
|
|
||||||
|
opNames := make(nameSet)
|
||||||
|
fragUsedBy := make(map[*types.FragmentDefinition][]*types.OperationDefinition)
|
||||||
|
for _, op := range doc.Operations {
|
||||||
|
c.usedVars[op] = make(varSet)
|
||||||
|
opc := &opContext{c, []*types.OperationDefinition{op}}
|
||||||
|
|
||||||
|
// Check if max depth is exceeded, if it's set. If max depth is exceeded,
|
||||||
|
// don't continue to validate the document and exit early.
|
||||||
|
if validateMaxDepth(opc, op.Selections, nil, 1) {
|
||||||
|
return c.errs
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
validateValue(opc, v, variables[v.Name.Name], t)
|
||||||
|
|
||||||
|
if v.Default != nil {
|
||||||
|
validateLiteral(opc, v.Default)
|
||||||
|
|
||||||
|
if t != nil {
|
||||||
|
if nn, ok := t.(*types.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 types.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[*types.FragmentDefinition]struct{})
|
||||||
|
markUsedFragments(c, op.Selections, fragUsed)
|
||||||
|
for frag := range fragUsed {
|
||||||
|
fragUsedBy[frag] = append(fragUsedBy[frag], op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragNames := make(nameSet)
|
||||||
|
fragVisited := make(map[*types.FragmentDefinition]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 validateValue(c *opContext, v *types.InputValueDefinition, val interface{}, t types.Type) {
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.NonNull:
|
||||||
|
if val == nil {
|
||||||
|
c.addErr(v.Loc, "VariablesOfCorrectType", "Variable \"%s\" has invalid value null.\nExpected type \"%s\", found null.", v.Name.Name, t)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
validateValue(c, v, val, t.OfType)
|
||||||
|
case *types.List:
|
||||||
|
if val == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vv, ok := val.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
// Input coercion rules allow single items without wrapping array
|
||||||
|
validateValue(c, v, val, t.OfType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, elem := range vv {
|
||||||
|
validateValue(c, v, elem, t.OfType)
|
||||||
|
}
|
||||||
|
case *types.EnumTypeDefinition:
|
||||||
|
if val == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e, ok := val.(string)
|
||||||
|
if !ok {
|
||||||
|
c.addErr(v.Loc, "VariablesOfCorrectType", "Variable \"%s\" has invalid type %T.\nExpected type \"%s\", found %v.", v.Name.Name, val, t, val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, option := range t.EnumValuesDefinition {
|
||||||
|
if option.EnumValue == e {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.addErr(v.Loc, "VariablesOfCorrectType", "Variable \"%s\" has invalid value %s.\nExpected type \"%s\", found %s.", v.Name.Name, e, t, e)
|
||||||
|
case *types.InputObject:
|
||||||
|
if val == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
in, ok := val.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
c.addErr(v.Loc, "VariablesOfCorrectType", "Variable \"%s\" has invalid type %T.\nExpected type \"%s\", found %s.", v.Name.Name, val, t, val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, f := range t.Values {
|
||||||
|
fieldVal := in[f.Name.Name]
|
||||||
|
validateValue(c, f, fieldVal, f.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates the query doesn't go deeper than maxDepth (if set). Returns whether
|
||||||
|
// or not query validated max depth to avoid excessive recursion.
|
||||||
|
//
|
||||||
|
// The visited map is necessary to ensure that max depth validation does not get stuck in cyclical
|
||||||
|
// fragment spreads.
|
||||||
|
func validateMaxDepth(c *opContext, sels []types.Selection, visited map[*types.FragmentDefinition]struct{}, depth int) bool {
|
||||||
|
// maxDepth checking is turned off when maxDepth is 0
|
||||||
|
if c.maxDepth == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
exceededMaxDepth := false
|
||||||
|
if visited == nil {
|
||||||
|
visited = map[*types.FragmentDefinition]struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sel := range sels {
|
||||||
|
switch sel := sel.(type) {
|
||||||
|
case *types.Field:
|
||||||
|
if depth > c.maxDepth {
|
||||||
|
exceededMaxDepth = true
|
||||||
|
c.addErr(sel.Alias.Loc, "MaxDepthExceeded", "Field %q has depth %d that exceeds max depth %d", sel.Name.Name, depth, c.maxDepth)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.SelectionSet, visited, depth+1)
|
||||||
|
|
||||||
|
case *types.InlineFragment:
|
||||||
|
// Depth is not checked because inline fragments resolve to other fields which are checked.
|
||||||
|
// Depth is not incremented because inline fragments have the same depth as neighboring fields
|
||||||
|
exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.Selections, visited, depth)
|
||||||
|
case *types.FragmentSpread:
|
||||||
|
// Depth is not checked because fragments resolve to other fields which are checked.
|
||||||
|
frag := c.doc.Fragments.Get(sel.Name.Name)
|
||||||
|
if frag == nil {
|
||||||
|
// In case of unknown fragment (invalid request), ignore max depth evaluation
|
||||||
|
c.addErr(sel.Loc, "MaxDepthEvaluationError", "Unknown fragment %q. Unable to evaluate depth.", sel.Name.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := visited[frag]; ok {
|
||||||
|
// we've already seen this fragment, don't check depth again.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
visited[frag] = struct{}{}
|
||||||
|
|
||||||
|
// Depth is not incremented because fragments have the same depth as surrounding fields
|
||||||
|
exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, frag.Selections, visited, depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exceededMaxDepth
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSelectionSet(c *opContext, sels []types.Selection, t types.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 types.Selection, t types.NamedType) {
|
||||||
|
switch sel := sel.(type) {
|
||||||
|
case *types.Field:
|
||||||
|
validateDirectives(c, "FIELD", sel.Directives)
|
||||||
|
|
||||||
|
fieldName := sel.Name.Name
|
||||||
|
var f *types.FieldDefinition
|
||||||
|
switch fieldName {
|
||||||
|
case "__typename":
|
||||||
|
f = &types.FieldDefinition{
|
||||||
|
Name: "__typename",
|
||||||
|
Type: c.schema.Types["String"],
|
||||||
|
}
|
||||||
|
case "__schema":
|
||||||
|
f = &types.FieldDefinition{
|
||||||
|
Name: "__schema",
|
||||||
|
Type: c.schema.Types["__Schema"],
|
||||||
|
}
|
||||||
|
case "__type":
|
||||||
|
f = &types.FieldDefinition{
|
||||||
|
Name: "__type",
|
||||||
|
Arguments: types.ArgumentsDefinition{
|
||||||
|
&types.InputValueDefinition{
|
||||||
|
Name: types.Ident{Name: "name"},
|
||||||
|
Type: &types.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.Arguments, 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 types.Type
|
||||||
|
if f != nil {
|
||||||
|
ft = f.Type
|
||||||
|
sf := hasSubfields(ft)
|
||||||
|
if sf && sel.SelectionSet == 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.SelectionSet != nil {
|
||||||
|
c.addErr(sel.SelectionSetLoc, "ScalarLeafs", "Field %q must not have a selection since type %q has no subfields.", fieldName, ft)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sel.SelectionSet != nil {
|
||||||
|
validateSelectionSet(c, sel.SelectionSet, unwrapType(ft))
|
||||||
|
}
|
||||||
|
|
||||||
|
case *types.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 *types.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 types.Type) bool {
|
||||||
|
for _, pta := range possibleTypes(a) {
|
||||||
|
for _, ptb := range possibleTypes(b) {
|
||||||
|
if pta == ptb {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func possibleTypes(t types.Type) []*types.ObjectTypeDefinition {
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.ObjectTypeDefinition:
|
||||||
|
return []*types.ObjectTypeDefinition{t}
|
||||||
|
case *types.InterfaceTypeDefinition:
|
||||||
|
return t.PossibleTypes
|
||||||
|
case *types.Union:
|
||||||
|
return t.UnionMemberTypes
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func markUsedFragments(c *context, sels []types.Selection, fragUsed map[*types.FragmentDefinition]struct{}) {
|
||||||
|
for _, sel := range sels {
|
||||||
|
switch sel := sel.(type) {
|
||||||
|
case *types.Field:
|
||||||
|
if sel.SelectionSet != nil {
|
||||||
|
markUsedFragments(c, sel.SelectionSet, fragUsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *types.InlineFragment:
|
||||||
|
markUsedFragments(c, sel.Selections, fragUsed)
|
||||||
|
|
||||||
|
case *types.FragmentSpread:
|
||||||
|
frag := c.doc.Fragments.Get(sel.Name.Name)
|
||||||
|
if frag == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := fragUsed[frag]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fragUsed[frag] = struct{}{}
|
||||||
|
markUsedFragments(c, frag.Selections, fragUsed)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectFragmentCycle(c *context, sels []types.Selection, fragVisited map[*types.FragmentDefinition]struct{}, spreadPath []*types.FragmentSpread, spreadPathIndex map[string]int) {
|
||||||
|
for _, sel := range sels {
|
||||||
|
detectFragmentCycleSel(c, sel, fragVisited, spreadPath, spreadPathIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectFragmentCycleSel(c *context, sel types.Selection, fragVisited map[*types.FragmentDefinition]struct{}, spreadPath []*types.FragmentSpread, spreadPathIndex map[string]int) {
|
||||||
|
switch sel := sel.(type) {
|
||||||
|
case *types.Field:
|
||||||
|
if sel.SelectionSet != nil {
|
||||||
|
detectFragmentCycle(c, sel.SelectionSet, fragVisited, spreadPath, spreadPathIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *types.InlineFragment:
|
||||||
|
detectFragmentCycle(c, sel.Selections, fragVisited, spreadPath, spreadPathIndex)
|
||||||
|
|
||||||
|
case *types.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 types.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 *types.Field:
|
||||||
|
switch b := b.(type) {
|
||||||
|
case *types.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 *types.InlineFragment:
|
||||||
|
for _, sel := range b.Selections {
|
||||||
|
c.validateOverlap(a, sel, reasons, locs)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *types.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 *types.InlineFragment:
|
||||||
|
for _, sel := range a.Selections {
|
||||||
|
c.validateOverlap(sel, b, reasons, locs)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *types.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 *types.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.SelectionSet {
|
||||||
|
for _, b2 := range b.SelectionSet {
|
||||||
|
c.validateOverlap(a2, b2, &reasons, &locs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reasons, locs
|
||||||
|
}
|
||||||
|
|
||||||
|
func argumentsConflict(a, b types.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.Deserialize(nil), valB.Deserialize(nil)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func fields(t types.Type) types.FieldsDefinition {
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.ObjectTypeDefinition:
|
||||||
|
return t.Fields
|
||||||
|
case *types.InterfaceTypeDefinition:
|
||||||
|
return t.Fields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unwrapType(t types.Type) types.NamedType {
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
switch t2 := t.(type) {
|
||||||
|
case types.NamedType:
|
||||||
|
return t2
|
||||||
|
case *types.List:
|
||||||
|
t = t2.OfType
|
||||||
|
case *types.NonNull:
|
||||||
|
t = t2.OfType
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveType(c *context, t types.Type) types.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 types.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.Arguments)
|
||||||
|
|
||||||
|
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.Locations {
|
||||||
|
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.Arguments, dd.Arguments, d.Name.Loc,
|
||||||
|
func() string { return fmt.Sprintf("directive %q", "@"+dirName) },
|
||||||
|
func() string { return fmt.Sprintf("Directive %q", "@"+dirName) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateName(c *context, set nameSet, name types.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 types.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
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateArgumentTypes(c *opContext, args types.ArgumentList, argDecls types.ArgumentsDefinition, 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.(*types.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 types.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 types.Value) {
|
||||||
|
switch l := l.(type) {
|
||||||
|
case *types.ObjectValue:
|
||||||
|
fieldNames := make(nameSet)
|
||||||
|
for _, f := range l.Fields {
|
||||||
|
validateName(c.context, fieldNames, f.Name, "UniqueInputFieldNames", "input field")
|
||||||
|
validateLiteral(c, f.Value)
|
||||||
|
}
|
||||||
|
case *types.ListValue:
|
||||||
|
for _, entry := range l.Values {
|
||||||
|
validateLiteral(c, entry)
|
||||||
|
}
|
||||||
|
case *types.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
|
||||||
|
}
|
||||||
|
validateValueType(c, l, resolveType(c.context, v.Type))
|
||||||
|
c.usedVars[op][v] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateValueType(c *opContext, v types.Value, t types.Type) (bool, string) {
|
||||||
|
if v, ok := v.(*types.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.(*types.NonNull); !ok && v2.Default != nil {
|
||||||
|
t2 = &types.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.(*types.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 *types.ScalarTypeDefinition, *types.EnumTypeDefinition:
|
||||||
|
if lit, ok := v.(*types.PrimitiveValue); ok {
|
||||||
|
if validateBasicLit(lit, t) {
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
return false, fmt.Sprintf("Expected type %q, found %s.", t, v)
|
||||||
|
}
|
||||||
|
return true, ""
|
||||||
|
|
||||||
|
case *types.List:
|
||||||
|
list, ok := v.(*types.ListValue)
|
||||||
|
if !ok {
|
||||||
|
return validateValueType(c, v, t.OfType) // single value instead of list
|
||||||
|
}
|
||||||
|
for i, entry := range list.Values {
|
||||||
|
if ok, reason := validateValueType(c, entry, t.OfType); !ok {
|
||||||
|
return false, fmt.Sprintf("In element #%d: %s", i, reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, ""
|
||||||
|
|
||||||
|
case *types.InputObject:
|
||||||
|
v, ok := v.(*types.ObjectValue)
|
||||||
|
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.(*types.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 *types.PrimitiveValue, t types.Type) bool {
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.ScalarTypeDefinition:
|
||||||
|
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 *types.EnumTypeDefinition:
|
||||||
|
if v.Type != scanner.Ident {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, option := range t.EnumValuesDefinition {
|
||||||
|
if option.EnumValue == v.Text {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func canBeFragment(t types.Type) bool {
|
||||||
|
switch t.(type) {
|
||||||
|
case *types.ObjectTypeDefinition, *types.InterfaceTypeDefinition, *types.Union:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func canBeInput(t types.Type) bool {
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.InputObject, *types.ScalarTypeDefinition, *types.EnumTypeDefinition:
|
||||||
|
return true
|
||||||
|
case *types.List:
|
||||||
|
return canBeInput(t.OfType)
|
||||||
|
case *types.NonNull:
|
||||||
|
return canBeInput(t.OfType)
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasSubfields(t types.Type) bool {
|
||||||
|
switch t := t.(type) {
|
||||||
|
case *types.ObjectTypeDefinition, *types.InterfaceTypeDefinition, *types.Union:
|
||||||
|
return true
|
||||||
|
case *types.List:
|
||||||
|
return hasSubfields(t.OfType)
|
||||||
|
case *types.NonNull:
|
||||||
|
return hasSubfields(t.OfType)
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLeaf(t types.Type) bool {
|
||||||
|
switch t.(type) {
|
||||||
|
case *types.ScalarTypeDefinition, *types.EnumTypeDefinition:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNull(lit interface{}) bool {
|
||||||
|
_, ok := lit.(*types.NullValue)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func typesCompatible(a, b types.Type) bool {
|
||||||
|
al, aIsList := a.(*types.List)
|
||||||
|
bl, bIsList := b.(*types.List)
|
||||||
|
if aIsList || bIsList {
|
||||||
|
return aIsList && bIsList && typesCompatible(al.OfType, bl.OfType)
|
||||||
|
}
|
||||||
|
|
||||||
|
ann, aIsNN := a.(*types.NonNull)
|
||||||
|
bnn, bIsNN := b.(*types.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 types.Type) bool {
|
||||||
|
nnT, okT := t.(*types.NonNull)
|
||||||
|
if okT {
|
||||||
|
t = nnT.OfType
|
||||||
|
}
|
||||||
|
|
||||||
|
nnAs, okAs := as.(*types.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.(*types.List); ok {
|
||||||
|
if lAs, ok := as.(*types.List); ok {
|
||||||
|
return typeCanBeUsedAs(lT.OfType, lAs.OfType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
118
vendor/github.com/graph-gophers/graphql-go/introspection.go
сгенерированный
поставляемый
Обычный файл
118
vendor/github.com/graph-gophers/graphql-go/introspection.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,118 @@
|
|||||||
|
package graphql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
|
||||||
|
"github.com/graph-gophers/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{
|
||||||
|
Meta: s.res.Meta,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
312
vendor/github.com/graph-gophers/graphql-go/introspection/introspection.go
сгенерированный
поставляемый
Обычный файл
312
vendor/github.com/graph-gophers/graphql-go/introspection/introspection.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,312 @@
|
|||||||
|
package introspection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Schema struct {
|
||||||
|
schema *types.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapSchema is only used internally.
|
||||||
|
func WrapSchema(schema *types.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 types.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapType is only used internally.
|
||||||
|
func WrapType(typ types.Type) *Type {
|
||||||
|
return &Type{typ}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Type) Kind() string {
|
||||||
|
return r.typ.Kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Type) Name() *string {
|
||||||
|
if named, ok := r.typ.(types.NamedType); ok {
|
||||||
|
name := named.TypeName()
|
||||||
|
return &name
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Type) Description() *string {
|
||||||
|
if named, ok := r.typ.(types.NamedType); ok {
|
||||||
|
desc := named.Description()
|
||||||
|
if desc == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &desc
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Type) Fields(args *struct{ IncludeDeprecated bool }) *[]*Field {
|
||||||
|
var fields types.FieldsDefinition
|
||||||
|
switch t := r.typ.(type) {
|
||||||
|
case *types.ObjectTypeDefinition:
|
||||||
|
fields = t.Fields
|
||||||
|
case *types.InterfaceTypeDefinition:
|
||||||
|
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{field: f})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Type) Interfaces() *[]*Type {
|
||||||
|
t, ok := r.typ.(*types.ObjectTypeDefinition)
|
||||||
|
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 []*types.ObjectTypeDefinition
|
||||||
|
switch t := r.typ.(type) {
|
||||||
|
case *types.InterfaceTypeDefinition:
|
||||||
|
possibleTypes = t.PossibleTypes
|
||||||
|
case *types.Union:
|
||||||
|
possibleTypes = t.UnionMemberTypes
|
||||||
|
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.(*types.EnumTypeDefinition)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var l []*EnumValue
|
||||||
|
for _, v := range t.EnumValuesDefinition {
|
||||||
|
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.(*types.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 *types.List:
|
||||||
|
return &Type{t.OfType}
|
||||||
|
case *types.NonNull:
|
||||||
|
return &Type{t.OfType}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Field struct {
|
||||||
|
field *types.FieldDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Arguments))
|
||||||
|
for i, v := range r.field.Arguments {
|
||||||
|
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.Arguments.MustGet("reason").Deserialize(nil).(string)
|
||||||
|
return &reason
|
||||||
|
}
|
||||||
|
|
||||||
|
type InputValue struct {
|
||||||
|
value *types.InputValueDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *types.EnumValueDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EnumValue) Name() string {
|
||||||
|
return r.value.EnumValue
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Arguments.MustGet("reason").Deserialize(nil).(string)
|
||||||
|
return &reason
|
||||||
|
}
|
||||||
|
|
||||||
|
type Directive struct {
|
||||||
|
directive *types.DirectiveDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Locations
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Directive) Args() []*InputValue {
|
||||||
|
l := make([]*InputValue, len(r.directive.Arguments))
|
||||||
|
for i, v := range r.directive.Arguments {
|
||||||
|
l[i] = &InputValue{v}
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
23
vendor/github.com/graph-gophers/graphql-go/log/log.go
сгенерированный
поставляемый
Обычный файл
23
vendor/github.com/graph-gophers/graphql-go/log/log.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,23 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger is the interface used to log panics that occur during query execution. It is settable via graphql.ParseSchema
|
||||||
|
type Logger interface {
|
||||||
|
LogPanic(ctx context.Context, value interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultLogger is the default logger used to log panics that occur during query execution
|
||||||
|
type DefaultLogger struct{}
|
||||||
|
|
||||||
|
// LogPanic is used to log recovered panic values that occur during query execution
|
||||||
|
func (l *DefaultLogger) LogPanic(ctx 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\ncontext: %v", value, buf, ctx)
|
||||||
|
}
|
||||||
166
vendor/github.com/graph-gophers/graphql-go/nullable_types.go
сгенерированный
поставляемый
Обычный файл
166
vendor/github.com/graph-gophers/graphql-go/nullable_types.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,166 @@
|
|||||||
|
package graphql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NullString is a string that can be null. Use it in input structs to
|
||||||
|
// differentiate a value explicitly set to null from an omitted value.
|
||||||
|
// When the value is defined (either null or a value) Set is true.
|
||||||
|
type NullString struct {
|
||||||
|
Value *string
|
||||||
|
Set bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (NullString) ImplementsGraphQLType(name string) bool {
|
||||||
|
return name == "String"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NullString) UnmarshalGraphQL(input interface{}) error {
|
||||||
|
s.Set = true
|
||||||
|
|
||||||
|
if input == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := input.(type) {
|
||||||
|
case string:
|
||||||
|
s.Value = &v
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("wrong type for String: %T", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NullString) Nullable() {}
|
||||||
|
|
||||||
|
// NullBool is a string that can be null. Use it in input structs to
|
||||||
|
// differentiate a value explicitly set to null from an omitted value.
|
||||||
|
// When the value is defined (either null or a value) Set is true.
|
||||||
|
type NullBool struct {
|
||||||
|
Value *bool
|
||||||
|
Set bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (NullBool) ImplementsGraphQLType(name string) bool {
|
||||||
|
return name == "Boolean"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NullBool) UnmarshalGraphQL(input interface{}) error {
|
||||||
|
s.Set = true
|
||||||
|
|
||||||
|
if input == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := input.(type) {
|
||||||
|
case bool:
|
||||||
|
s.Value = &v
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("wrong type for Boolean: %T", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NullBool) Nullable() {}
|
||||||
|
|
||||||
|
// NullInt is a string that can be null. Use it in input structs to
|
||||||
|
// differentiate a value explicitly set to null from an omitted value.
|
||||||
|
// When the value is defined (either null or a value) Set is true.
|
||||||
|
type NullInt struct {
|
||||||
|
Value *int32
|
||||||
|
Set bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (NullInt) ImplementsGraphQLType(name string) bool {
|
||||||
|
return name == "Int"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NullInt) UnmarshalGraphQL(input interface{}) error {
|
||||||
|
s.Set = true
|
||||||
|
|
||||||
|
if input == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := input.(type) {
|
||||||
|
case int32:
|
||||||
|
s.Value = &v
|
||||||
|
return nil
|
||||||
|
case float64:
|
||||||
|
coerced := int32(v)
|
||||||
|
if v < math.MinInt32 || v > math.MaxInt32 || float64(coerced) != v {
|
||||||
|
return fmt.Errorf("not a 32-bit integer")
|
||||||
|
}
|
||||||
|
s.Value = &coerced
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("wrong type for Int: %T", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NullInt) Nullable() {}
|
||||||
|
|
||||||
|
// NullFloat is a string that can be null. Use it in input structs to
|
||||||
|
// differentiate a value explicitly set to null from an omitted value.
|
||||||
|
// When the value is defined (either null or a value) Set is true.
|
||||||
|
type NullFloat struct {
|
||||||
|
Value *float64
|
||||||
|
Set bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (NullFloat) ImplementsGraphQLType(name string) bool {
|
||||||
|
return name == "Float"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NullFloat) UnmarshalGraphQL(input interface{}) error {
|
||||||
|
s.Set = true
|
||||||
|
|
||||||
|
if input == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := input.(type) {
|
||||||
|
case float64:
|
||||||
|
s.Value = &v
|
||||||
|
return nil
|
||||||
|
case int32:
|
||||||
|
coerced := float64(v)
|
||||||
|
s.Value = &coerced
|
||||||
|
return nil
|
||||||
|
case int:
|
||||||
|
coerced := float64(v)
|
||||||
|
s.Value = &coerced
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("wrong type for Float: %T", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NullFloat) Nullable() {}
|
||||||
|
|
||||||
|
// NullTime is a string that can be null. Use it in input structs to
|
||||||
|
// differentiate a value explicitly set to null from an omitted value.
|
||||||
|
// When the value is defined (either null or a value) Set is true.
|
||||||
|
type NullTime struct {
|
||||||
|
Value *Time
|
||||||
|
Set bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (NullTime) ImplementsGraphQLType(name string) bool {
|
||||||
|
return name == "Time"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NullTime) UnmarshalGraphQL(input interface{}) error {
|
||||||
|
s.Set = true
|
||||||
|
|
||||||
|
if input == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Value = new(Time)
|
||||||
|
return s.Value.UnmarshalGraphQL(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NullTime) Nullable() {}
|
||||||
96
vendor/github.com/graph-gophers/graphql-go/subscriptions.go
сгенерированный
поставляемый
Обычный файл
96
vendor/github.com/graph-gophers/graphql-go/subscriptions.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,96 @@
|
|||||||
|
package graphql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
qerrors "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/common"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec/resolvable"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/exec/selected"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/query"
|
||||||
|
"github.com/graph-gophers/graphql-go/internal/validation"
|
||||||
|
"github.com/graph-gophers/graphql-go/introspection"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Subscribe returns a response channel for the given subscription with the schema's
|
||||||
|
// resolver. It returns an error if the schema was created without a resolver.
|
||||||
|
// If the context gets cancelled, the response channel will be closed and no
|
||||||
|
// further resolvers will be called. The context error will be returned as soon
|
||||||
|
// as possible (not immediately).
|
||||||
|
func (s *Schema) Subscribe(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) (<-chan interface{}, error) {
|
||||||
|
if !s.res.Resolver.IsValid() {
|
||||||
|
return nil, errors.New("schema created without resolver, can not subscribe")
|
||||||
|
}
|
||||||
|
if _, ok := s.schema.EntryPoints["subscription"]; !ok {
|
||||||
|
return nil, errors.New("no subscriptions are offered by the schema")
|
||||||
|
}
|
||||||
|
return s.subscribe(ctx, queryString, operationName, variables, s.res), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Schema) subscribe(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, res *resolvable.Schema) <-chan interface{} {
|
||||||
|
doc, qErr := query.Parse(queryString)
|
||||||
|
if qErr != nil {
|
||||||
|
return sendAndReturnClosed(&Response{Errors: []*qerrors.QueryError{qErr}})
|
||||||
|
}
|
||||||
|
|
||||||
|
validationFinish := s.validationTracer.TraceValidation(ctx)
|
||||||
|
errs := validation.Validate(s.schema, doc, variables, s.maxDepth)
|
||||||
|
validationFinish(errs)
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return sendAndReturnClosed(&Response{Errors: errs})
|
||||||
|
}
|
||||||
|
|
||||||
|
op, err := getOperation(doc, operationName)
|
||||||
|
if err != nil {
|
||||||
|
return sendAndReturnClosed(&Response{Errors: []*qerrors.QueryError{qerrors.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,
|
||||||
|
PanicHandler: s.panicHandler,
|
||||||
|
SubscribeResolverTimeout: s.subscribeResolverTimeout,
|
||||||
|
}
|
||||||
|
varTypes := make(map[string]*introspection.Type)
|
||||||
|
for _, v := range op.Vars {
|
||||||
|
t, err := common.ResolveType(v.Type, s.schema.Resolve)
|
||||||
|
if err != nil {
|
||||||
|
return sendAndReturnClosed(&Response{Errors: []*qerrors.QueryError{err}})
|
||||||
|
}
|
||||||
|
varTypes[v.Name.Name] = introspection.WrapType(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if op.Type == query.Query || op.Type == query.Mutation {
|
||||||
|
data, errs := r.Execute(ctx, res, op)
|
||||||
|
return sendAndReturnClosed(&Response{Data: data, Errors: errs})
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := r.Subscribe(ctx, res, op)
|
||||||
|
c := make(chan interface{})
|
||||||
|
go func() {
|
||||||
|
for resp := range responses {
|
||||||
|
c <- &Response{
|
||||||
|
Data: resp.Data,
|
||||||
|
Errors: resp.Errors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(c)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendAndReturnClosed(resp *Response) chan interface{} {
|
||||||
|
c := make(chan interface{}, 1)
|
||||||
|
c <- resp
|
||||||
|
close(c)
|
||||||
|
return c
|
||||||
|
}
|
||||||
64
vendor/github.com/graph-gophers/graphql-go/time.go
сгенерированный
поставляемый
Обычный файл
64
vendor/github.com/graph-gophers/graphql-go/time.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,64 @@
|
|||||||
|
package graphql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImplementsGraphQLType maps this custom Go type
|
||||||
|
// to the graphql scalar type in the schema.
|
||||||
|
func (Time) ImplementsGraphQLType(name string) bool {
|
||||||
|
return name == "Time"
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalGraphQL is a custom unmarshaler for Time
|
||||||
|
//
|
||||||
|
// This function will be called whenever you use the
|
||||||
|
// time scalar as an input
|
||||||
|
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 []byte:
|
||||||
|
var err error
|
||||||
|
t.Time, err = time.Parse(time.RFC3339, string(input))
|
||||||
|
return err
|
||||||
|
case int32:
|
||||||
|
t.Time = time.Unix(int64(input), 0)
|
||||||
|
return nil
|
||||||
|
case int64:
|
||||||
|
if input >= 1e10 {
|
||||||
|
sec := input / 1e9
|
||||||
|
nsec := input - (sec * 1e9)
|
||||||
|
t.Time = time.Unix(sec, nsec)
|
||||||
|
} else {
|
||||||
|
t.Time = time.Unix(input, 0)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case float64:
|
||||||
|
t.Time = time.Unix(int64(input), 0)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("wrong type for Time: %T", input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON is a custom marshaler for Time
|
||||||
|
//
|
||||||
|
// This function will be called whenever you
|
||||||
|
// query for fields that use the Time type
|
||||||
|
func (t Time) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(t.Time)
|
||||||
|
}
|
||||||
96
vendor/github.com/graph-gophers/graphql-go/trace/trace.go
сгенерированный
поставляемый
Обычный файл
96
vendor/github.com/graph-gophers/graphql-go/trace/trace.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,96 @@
|
|||||||
|
package trace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
"github.com/graph-gophers/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 (OpenTracingTracer) TraceValidation(ctx context.Context) TraceValidationFinishFunc {
|
||||||
|
span, _ := opentracing.StartSpanFromContext(ctx, "Validate Query")
|
||||||
|
|
||||||
|
return 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 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) {}
|
||||||
|
}
|
||||||
25
vendor/github.com/graph-gophers/graphql-go/trace/validation_trace.go
сгенерированный
поставляемый
Обычный файл
25
vendor/github.com/graph-gophers/graphql-go/trace/validation_trace.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,25 @@
|
|||||||
|
package trace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TraceValidationFinishFunc = TraceQueryFinishFunc
|
||||||
|
|
||||||
|
// Deprecated: use ValidationTracerContext.
|
||||||
|
type ValidationTracer interface {
|
||||||
|
TraceValidation() TraceValidationFinishFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidationTracerContext interface {
|
||||||
|
TraceValidation(ctx context.Context) TraceValidationFinishFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
type NoopValidationTracer struct{}
|
||||||
|
|
||||||
|
// Deprecated: use a Tracer which implements ValidationTracerContext.
|
||||||
|
func (NoopValidationTracer) TraceValidation() TraceValidationFinishFunc {
|
||||||
|
return func(errs []*errors.QueryError) {}
|
||||||
|
}
|
||||||
44
vendor/github.com/graph-gophers/graphql-go/types/argument.go
сгенерированный
поставляемый
Обычный файл
44
vendor/github.com/graph-gophers/graphql-go/types/argument.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,44 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// Argument is a representation of the GraphQL Argument.
|
||||||
|
//
|
||||||
|
// https://spec.graphql.org/draft/#sec-Language.Arguments
|
||||||
|
type Argument struct {
|
||||||
|
Name Ident
|
||||||
|
Value Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgumentList is a collection of GraphQL Arguments.
|
||||||
|
type ArgumentList []*Argument
|
||||||
|
|
||||||
|
// Returns a Value in the ArgumentList by name.
|
||||||
|
func (l ArgumentList) Get(name string) (Value, bool) {
|
||||||
|
for _, arg := range l {
|
||||||
|
if arg.Name.Name == name {
|
||||||
|
return arg.Value, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGet returns a Value in the ArgumentList by name.
|
||||||
|
// MustGet will panic if the argument name is not found in the ArgumentList.
|
||||||
|
func (l ArgumentList) MustGet(name string) Value {
|
||||||
|
value, ok := l.Get(name)
|
||||||
|
if !ok {
|
||||||
|
panic("argument not found")
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArgumentsDefinition []*InputValueDefinition
|
||||||
|
|
||||||
|
// Get returns an InputValueDefinition in the ArgumentsDefinition by name or nil if not found.
|
||||||
|
func (a ArgumentsDefinition) Get(name string) *InputValueDefinition {
|
||||||
|
for _, inputValue := range a {
|
||||||
|
if inputValue.Name.Name == name {
|
||||||
|
return inputValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
34
vendor/github.com/graph-gophers/graphql-go/types/directive.go
сгенерированный
поставляемый
Обычный файл
34
vendor/github.com/graph-gophers/graphql-go/types/directive.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,34 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
|
||||||
|
// Directive is a representation of the GraphQL Directive.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Language.Directives
|
||||||
|
type Directive struct {
|
||||||
|
Name Ident
|
||||||
|
Arguments ArgumentList
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirectiveDefinition is a representation of the GraphQL DirectiveDefinition.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Type-System.Directives
|
||||||
|
type DirectiveDefinition struct {
|
||||||
|
Name string
|
||||||
|
Desc string
|
||||||
|
Locations []string
|
||||||
|
Arguments ArgumentsDefinition
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
type DirectiveList []*Directive
|
||||||
|
|
||||||
|
// Returns the Directive in the DirectiveList by name or nil if not found.
|
||||||
|
func (l DirectiveList) Get(name string) *Directive {
|
||||||
|
for _, d := range l {
|
||||||
|
if d.Name.Name == name {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
9
vendor/github.com/graph-gophers/graphql-go/types/doc.go
сгенерированный
поставляемый
Обычный файл
9
vendor/github.com/graph-gophers/graphql-go/types/doc.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
Package types represents all types from the GraphQL specification in code.
|
||||||
|
|
||||||
|
|
||||||
|
The names of the Go types, whenever possible, match 1:1 with the names from
|
||||||
|
the specification.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package types
|
||||||
32
vendor/github.com/graph-gophers/graphql-go/types/enum.go
сгенерированный
поставляемый
Обычный файл
32
vendor/github.com/graph-gophers/graphql-go/types/enum.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,32 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
|
||||||
|
// EnumTypeDefinition defines a set of possible enum values.
|
||||||
|
//
|
||||||
|
// Like scalar types, an EnumTypeDefinition also represents a leaf value in a GraphQL type system.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Enums
|
||||||
|
type EnumTypeDefinition struct {
|
||||||
|
Name string
|
||||||
|
EnumValuesDefinition []*EnumValueDefinition
|
||||||
|
Desc string
|
||||||
|
Directives DirectiveList
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumValueDefinition are unique values that may be serialized as a string: the name of the
|
||||||
|
// represented value.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#EnumValueDefinition
|
||||||
|
type EnumValueDefinition struct {
|
||||||
|
EnumValue string
|
||||||
|
Directives DirectiveList
|
||||||
|
Desc string
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*EnumTypeDefinition) Kind() string { return "ENUM" }
|
||||||
|
func (t *EnumTypeDefinition) String() string { return t.Name }
|
||||||
|
func (t *EnumTypeDefinition) TypeName() string { return t.Name }
|
||||||
|
func (t *EnumTypeDefinition) Description() string { return t.Desc }
|
||||||
13
vendor/github.com/graph-gophers/graphql-go/types/extension.go
сгенерированный
поставляемый
Обычный файл
13
vendor/github.com/graph-gophers/graphql-go/types/extension.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,13 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
|
||||||
|
// Extension type defines a GraphQL type extension.
|
||||||
|
// Schemas, Objects, Inputs and Scalars can be extended.
|
||||||
|
//
|
||||||
|
// https://spec.graphql.org/draft/#sec-Type-System-Extensions
|
||||||
|
type Extension struct {
|
||||||
|
Type NamedType
|
||||||
|
Directives DirectiveList
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
39
vendor/github.com/graph-gophers/graphql-go/types/field.go
сгенерированный
поставляемый
Обычный файл
39
vendor/github.com/graph-gophers/graphql-go/types/field.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,39 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
|
||||||
|
// FieldDefinition is a representation of a GraphQL FieldDefinition.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#FieldDefinition
|
||||||
|
type FieldDefinition struct {
|
||||||
|
Name string
|
||||||
|
Arguments ArgumentsDefinition
|
||||||
|
Type Type
|
||||||
|
Directives DirectiveList
|
||||||
|
Desc string
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldsDefinition is a list of an ObjectTypeDefinition's Fields.
|
||||||
|
//
|
||||||
|
// https://spec.graphql.org/draft/#FieldsDefinition
|
||||||
|
type FieldsDefinition []*FieldDefinition
|
||||||
|
|
||||||
|
// Get returns a FieldDefinition in a FieldsDefinition by name or nil if not found.
|
||||||
|
func (l FieldsDefinition) Get(name string) *FieldDefinition {
|
||||||
|
for _, f := range l {
|
||||||
|
if f.Name == name {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names returns a slice of FieldDefinition names.
|
||||||
|
func (l FieldsDefinition) Names() []string {
|
||||||
|
names := make([]string, len(l))
|
||||||
|
for i, f := range l {
|
||||||
|
names[i] = f.Name
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
51
vendor/github.com/graph-gophers/graphql-go/types/fragment.go
сгенерированный
поставляемый
Обычный файл
51
vendor/github.com/graph-gophers/graphql-go/types/fragment.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,51 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
|
||||||
|
type Fragment struct {
|
||||||
|
On TypeName
|
||||||
|
Selections SelectionSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// InlineFragment is a representation of the GraphQL InlineFragment.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#InlineFragment
|
||||||
|
type InlineFragment struct {
|
||||||
|
Fragment
|
||||||
|
Directives DirectiveList
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
// FragmentDefinition is a representation of the GraphQL FragmentDefinition.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#FragmentDefinition
|
||||||
|
type FragmentDefinition struct {
|
||||||
|
Fragment
|
||||||
|
Name Ident
|
||||||
|
Directives DirectiveList
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
// FragmentSpread is a representation of the GraphQL FragmentSpread.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#FragmentSpread
|
||||||
|
type FragmentSpread struct {
|
||||||
|
Name Ident
|
||||||
|
Directives DirectiveList
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
type FragmentList []*FragmentDefinition
|
||||||
|
|
||||||
|
// Returns a FragmentDefinition by name or nil if not found.
|
||||||
|
func (l FragmentList) Get(name string) *FragmentDefinition {
|
||||||
|
for _, f := range l {
|
||||||
|
if f.Name.Name == name {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (InlineFragment) isSelection() {}
|
||||||
|
func (FragmentSpread) isSelection() {}
|
||||||
47
vendor/github.com/graph-gophers/graphql-go/types/input.go
сгенерированный
поставляемый
Обычный файл
47
vendor/github.com/graph-gophers/graphql-go/types/input.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,47 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
|
||||||
|
// InputValueDefinition is a representation of the GraphQL InputValueDefinition.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#InputValueDefinition
|
||||||
|
type InputValueDefinition struct {
|
||||||
|
Name Ident
|
||||||
|
Type Type
|
||||||
|
Default Value
|
||||||
|
Desc string
|
||||||
|
Directives DirectiveList
|
||||||
|
Loc errors.Location
|
||||||
|
TypeLoc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
type InputValueDefinitionList []*InputValueDefinition
|
||||||
|
|
||||||
|
// Returns an InputValueDefinition by name or nil if not found.
|
||||||
|
func (l InputValueDefinitionList) Get(name string) *InputValueDefinition {
|
||||||
|
for _, v := range l {
|
||||||
|
if v.Name.Name == name {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputObject types define a set of input fields; the input fields are either scalars, enums, or
|
||||||
|
// other input objects.
|
||||||
|
//
|
||||||
|
// This allows arguments to accept arbitrarily complex structs.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Input-Objects
|
||||||
|
type InputObject struct {
|
||||||
|
Name string
|
||||||
|
Desc string
|
||||||
|
Values ArgumentsDefinition
|
||||||
|
Directives DirectiveList
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*InputObject) Kind() string { return "INPUT_OBJECT" }
|
||||||
|
func (t *InputObject) String() string { return t.Name }
|
||||||
|
func (t *InputObject) TypeName() string { return t.Name }
|
||||||
|
func (t *InputObject) Description() string { return t.Desc }
|
||||||
25
vendor/github.com/graph-gophers/graphql-go/types/interface.go
сгенерированный
поставляемый
Обычный файл
25
vendor/github.com/graph-gophers/graphql-go/types/interface.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,25 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
|
||||||
|
// InterfaceTypeDefinition recusrively defines list of named fields with their arguments via the
|
||||||
|
// implementation chain of interfaces.
|
||||||
|
//
|
||||||
|
// GraphQL objects can then implement these interfaces which requires that the object type will
|
||||||
|
// define all fields defined by those interfaces.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Interfaces
|
||||||
|
type InterfaceTypeDefinition struct {
|
||||||
|
Name string
|
||||||
|
PossibleTypes []*ObjectTypeDefinition
|
||||||
|
Fields FieldsDefinition
|
||||||
|
Desc string
|
||||||
|
Directives DirectiveList
|
||||||
|
Loc errors.Location
|
||||||
|
Interfaces []*InterfaceTypeDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*InterfaceTypeDefinition) Kind() string { return "INTERFACE" }
|
||||||
|
func (t *InterfaceTypeDefinition) String() string { return t.Name }
|
||||||
|
func (t *InterfaceTypeDefinition) TypeName() string { return t.Name }
|
||||||
|
func (t *InterfaceTypeDefinition) Description() string { return t.Desc }
|
||||||
25
vendor/github.com/graph-gophers/graphql-go/types/object.go
сгенерированный
поставляемый
Обычный файл
25
vendor/github.com/graph-gophers/graphql-go/types/object.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,25 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
|
||||||
|
// ObjectTypeDefinition represents a GraphQL ObjectTypeDefinition.
|
||||||
|
//
|
||||||
|
// type FooObject {
|
||||||
|
// foo: String
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// https://spec.graphql.org/draft/#sec-Objects
|
||||||
|
type ObjectTypeDefinition struct {
|
||||||
|
Name string
|
||||||
|
Interfaces []*InterfaceTypeDefinition
|
||||||
|
Fields FieldsDefinition
|
||||||
|
Desc string
|
||||||
|
Directives DirectiveList
|
||||||
|
InterfaceNames []string
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ObjectTypeDefinition) Kind() string { return "OBJECT" }
|
||||||
|
func (t *ObjectTypeDefinition) String() string { return t.Name }
|
||||||
|
func (t *ObjectTypeDefinition) TypeName() string { return t.Name }
|
||||||
|
func (t *ObjectTypeDefinition) Description() string { return t.Desc }
|
||||||
62
vendor/github.com/graph-gophers/graphql-go/types/query.go
сгенерированный
поставляемый
Обычный файл
62
vendor/github.com/graph-gophers/graphql-go/types/query.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,62 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
|
||||||
|
// ExecutableDefinition represents a set of operations or fragments that can be executed
|
||||||
|
// against a schema.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#ExecutableDefinition
|
||||||
|
type ExecutableDefinition struct {
|
||||||
|
Operations OperationList
|
||||||
|
Fragments FragmentList
|
||||||
|
}
|
||||||
|
|
||||||
|
// OperationDefinition represents a GraphQL Operation.
|
||||||
|
//
|
||||||
|
// https://spec.graphql.org/draft/#sec-Language.Operations
|
||||||
|
type OperationDefinition struct {
|
||||||
|
Type OperationType
|
||||||
|
Name Ident
|
||||||
|
Vars ArgumentsDefinition
|
||||||
|
Selections SelectionSet
|
||||||
|
Directives DirectiveList
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
type OperationType string
|
||||||
|
|
||||||
|
// A Selection is a field requested in a GraphQL operation.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#Selection
|
||||||
|
type Selection interface {
|
||||||
|
isSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SelectionSet represents a collection of Selections
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Selection-Sets
|
||||||
|
type SelectionSet []Selection
|
||||||
|
|
||||||
|
// Field represents a field used in a query.
|
||||||
|
type Field struct {
|
||||||
|
Alias Ident
|
||||||
|
Name Ident
|
||||||
|
Arguments ArgumentList
|
||||||
|
Directives DirectiveList
|
||||||
|
SelectionSet SelectionSet
|
||||||
|
SelectionSetLoc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Field) isSelection() {}
|
||||||
|
|
||||||
|
type OperationList []*OperationDefinition
|
||||||
|
|
||||||
|
// Get returns an OperationDefinition by name or nil if not found.
|
||||||
|
func (l OperationList) Get(name string) *OperationDefinition {
|
||||||
|
for _, f := range l {
|
||||||
|
if f.Name.Name == name {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
22
vendor/github.com/graph-gophers/graphql-go/types/scalar.go
сгенерированный
поставляемый
Обычный файл
22
vendor/github.com/graph-gophers/graphql-go/types/scalar.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,22 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
|
||||||
|
// ScalarTypeDefinition types represent primitive leaf values (e.g. a string or an integer) in a GraphQL type
|
||||||
|
// system.
|
||||||
|
//
|
||||||
|
// GraphQL responses take the form of a hierarchical tree; the leaves on these trees are GraphQL
|
||||||
|
// scalars.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Scalars
|
||||||
|
type ScalarTypeDefinition struct {
|
||||||
|
Name string
|
||||||
|
Desc string
|
||||||
|
Directives DirectiveList
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ScalarTypeDefinition) Kind() string { return "SCALAR" }
|
||||||
|
func (t *ScalarTypeDefinition) String() string { return t.Name }
|
||||||
|
func (t *ScalarTypeDefinition) TypeName() string { return t.Name }
|
||||||
|
func (t *ScalarTypeDefinition) Description() string { return t.Desc }
|
||||||
42
vendor/github.com/graph-gophers/graphql-go/types/schema.go
сгенерированный
поставляемый
Обычный файл
42
vendor/github.com/graph-gophers/graphql-go/types/schema.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,42 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// Schema represents a GraphQL service's collective type system capabilities.
|
||||||
|
// A schema is defined in terms of the types and directives it supports as well as the root
|
||||||
|
// operation types for each kind of operation: `query`, `mutation`, and `subscription`.
|
||||||
|
//
|
||||||
|
// For a more formal definition, read the relevant section in the specification:
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Schema
|
||||||
|
type Schema struct {
|
||||||
|
// EntryPoints determines the place in the type system where `query`, `mutation`, and
|
||||||
|
// `subscription` operations begin.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Root-Operation-Types
|
||||||
|
//
|
||||||
|
EntryPoints map[string]NamedType
|
||||||
|
|
||||||
|
// Types are the fundamental unit of any GraphQL schema.
|
||||||
|
// There are six kinds of named types, and two wrapping types.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Types
|
||||||
|
Types map[string]NamedType
|
||||||
|
|
||||||
|
// Directives are used to annotate various parts of a GraphQL document as an indicator that they
|
||||||
|
// should be evaluated differently by a validator, executor, or client tool such as a code
|
||||||
|
// generator.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/#sec-Type-System.Directives
|
||||||
|
Directives map[string]*DirectiveDefinition
|
||||||
|
|
||||||
|
UseFieldResolvers bool
|
||||||
|
|
||||||
|
EntryPointNames map[string]string
|
||||||
|
Objects []*ObjectTypeDefinition
|
||||||
|
Unions []*Union
|
||||||
|
Enums []*EnumTypeDefinition
|
||||||
|
Extensions []*Extension
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Schema) Resolve(name string) Type {
|
||||||
|
return s.Types[name]
|
||||||
|
}
|
||||||
63
vendor/github.com/graph-gophers/graphql-go/types/types.go
сгенерированный
поставляемый
Обычный файл
63
vendor/github.com/graph-gophers/graphql-go/types/types.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,63 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TypeName is a base building block for GraphQL type references.
|
||||||
|
type TypeName struct {
|
||||||
|
Ident
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedType represents a type with a name.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#NamedType
|
||||||
|
type NamedType interface {
|
||||||
|
Type
|
||||||
|
TypeName() string
|
||||||
|
Description() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ident struct {
|
||||||
|
Name string
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
type Type interface {
|
||||||
|
// Kind returns one possible GraphQL type kind. A type kind must be
|
||||||
|
// valid as defined by the GraphQL spec.
|
||||||
|
//
|
||||||
|
// https://spec.graphql.org/draft/#sec-Type-Kinds
|
||||||
|
Kind() string
|
||||||
|
|
||||||
|
// String serializes a Type into a GraphQL specification format type.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Serialization-Format
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// List represents a GraphQL ListType.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#ListType
|
||||||
|
type List struct {
|
||||||
|
// OfType represents the inner-type of a List type.
|
||||||
|
// For example, the List type `[Foo]` has an OfType of Foo.
|
||||||
|
OfType Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// NonNull represents a GraphQL NonNullType.
|
||||||
|
//
|
||||||
|
// https://spec.graphql.org/draft/#NonNullType
|
||||||
|
type NonNull struct {
|
||||||
|
// OfType represents the inner-type of a NonNull type.
|
||||||
|
// For example, the NonNull type `Foo!` has an OfType of Foo.
|
||||||
|
OfType Type
|
||||||
|
}
|
||||||
|
|
||||||
|
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") }
|
||||||
24
vendor/github.com/graph-gophers/graphql-go/types/union.go
сгенерированный
поставляемый
Обычный файл
24
vendor/github.com/graph-gophers/graphql-go/types/union.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,24 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
|
||||||
|
// Union types represent objects that could be one of a list of GraphQL object types, but provides no
|
||||||
|
// guaranteed fields between those types.
|
||||||
|
//
|
||||||
|
// They also differ from interfaces in that object types declare what interfaces they implement, but
|
||||||
|
// are not aware of what unions contain them.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Unions
|
||||||
|
type Union struct {
|
||||||
|
Name string
|
||||||
|
UnionMemberTypes []*ObjectTypeDefinition
|
||||||
|
Desc string
|
||||||
|
Directives DirectiveList
|
||||||
|
TypeNames []string
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Union) Kind() string { return "UNION" }
|
||||||
|
func (t *Union) String() string { return t.Name }
|
||||||
|
func (t *Union) TypeName() string { return t.Name }
|
||||||
|
func (t *Union) Description() string { return t.Desc }
|
||||||
141
vendor/github.com/graph-gophers/graphql-go/types/value.go
сгенерированный
поставляемый
Обычный файл
141
vendor/github.com/graph-gophers/graphql-go/types/value.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,141 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/scanner"
|
||||||
|
|
||||||
|
"github.com/graph-gophers/graphql-go/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Value represents a literal input or literal default value in the GraphQL Specification.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Input-Values
|
||||||
|
type Value interface {
|
||||||
|
// Deserialize transforms a GraphQL specification format literal into a Go type.
|
||||||
|
Deserialize(vars map[string]interface{}) interface{}
|
||||||
|
|
||||||
|
// String serializes a Value into a GraphQL specification format literal.
|
||||||
|
String() string
|
||||||
|
Location() errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrimitiveValue represents one of the following GraphQL scalars: Int, Float,
|
||||||
|
// String, or Boolean
|
||||||
|
type PrimitiveValue struct {
|
||||||
|
Type rune
|
||||||
|
Text string
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func (val *PrimitiveValue) Deserialize(vars map[string]interface{}) interface{} {
|
||||||
|
switch val.Type {
|
||||||
|
case scanner.Int:
|
||||||
|
value, err := strconv.ParseInt(val.Text, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return int32(value)
|
||||||
|
|
||||||
|
case scanner.Float:
|
||||||
|
value, err := strconv.ParseFloat(val.Text, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
|
||||||
|
case scanner.String:
|
||||||
|
value, err := strconv.Unquote(val.Text)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
|
||||||
|
case scanner.Ident:
|
||||||
|
switch val.Text {
|
||||||
|
case "true":
|
||||||
|
return true
|
||||||
|
case "false":
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return val.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("invalid literal value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (val *PrimitiveValue) String() string { return val.Text }
|
||||||
|
func (val *PrimitiveValue) Location() errors.Location { return val.Loc }
|
||||||
|
|
||||||
|
// ListValue represents a literal list Value in the GraphQL specification.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-List-Value
|
||||||
|
type ListValue struct {
|
||||||
|
Values []Value
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func (val *ListValue) Deserialize(vars map[string]interface{}) interface{} {
|
||||||
|
entries := make([]interface{}, len(val.Values))
|
||||||
|
for i, entry := range val.Values {
|
||||||
|
entries[i] = entry.Deserialize(vars)
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
func (val *ListValue) String() string {
|
||||||
|
entries := make([]string, len(val.Values))
|
||||||
|
for i, entry := range val.Values {
|
||||||
|
entries[i] = entry.String()
|
||||||
|
}
|
||||||
|
return "[" + strings.Join(entries, ", ") + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (val *ListValue) Location() errors.Location { return val.Loc }
|
||||||
|
|
||||||
|
// ObjectValue represents a literal object Value in the GraphQL specification.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Object-Value
|
||||||
|
type ObjectValue struct {
|
||||||
|
Fields []*ObjectField
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectField represents field/value pairs in a literal ObjectValue.
|
||||||
|
type ObjectField struct {
|
||||||
|
Name Ident
|
||||||
|
Value Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (val *ObjectValue) Deserialize(vars map[string]interface{}) interface{} {
|
||||||
|
fields := make(map[string]interface{}, len(val.Fields))
|
||||||
|
for _, f := range val.Fields {
|
||||||
|
fields[f.Name.Name] = f.Value.Deserialize(vars)
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (val *ObjectValue) String() string {
|
||||||
|
entries := make([]string, 0, len(val.Fields))
|
||||||
|
for _, f := range val.Fields {
|
||||||
|
entries = append(entries, f.Name.Name+": "+f.Value.String())
|
||||||
|
}
|
||||||
|
return "{" + strings.Join(entries, ", ") + "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (val *ObjectValue) Location() errors.Location {
|
||||||
|
return val.Loc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NullValue represents a literal `null` Value in the GraphQL specification.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#sec-Null-Value
|
||||||
|
type NullValue struct {
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func (val *NullValue) Deserialize(vars map[string]interface{}) interface{} { return nil }
|
||||||
|
func (val *NullValue) String() string { return "null" }
|
||||||
|
func (val *NullValue) Location() errors.Location { return val.Loc }
|
||||||
15
vendor/github.com/graph-gophers/graphql-go/types/variable.go
сгенерированный
поставляемый
Обычный файл
15
vendor/github.com/graph-gophers/graphql-go/types/variable.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,15 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/graph-gophers/graphql-go/errors"
|
||||||
|
|
||||||
|
// Variable is used in GraphQL operations to parameterize an input value.
|
||||||
|
//
|
||||||
|
// http://spec.graphql.org/draft/#Variable
|
||||||
|
type Variable struct {
|
||||||
|
Name string
|
||||||
|
Loc errors.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Variable) Deserialize(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 }
|
||||||
17
vendor/modules.txt
поставляемый
17
vendor/modules.txt
поставляемый
@@ -283,6 +283,23 @@ github.com/gorilla/schema
|
|||||||
# github.com/gorilla/websocket v1.4.2
|
# github.com/gorilla/websocket v1.4.2
|
||||||
## explicit
|
## explicit
|
||||||
github.com/gorilla/websocket
|
github.com/gorilla/websocket
|
||||||
|
# github.com/graph-gophers/graphql-go v1.3.0
|
||||||
|
## explicit
|
||||||
|
github.com/graph-gophers/graphql-go
|
||||||
|
github.com/graph-gophers/graphql-go/decode
|
||||||
|
github.com/graph-gophers/graphql-go/errors
|
||||||
|
github.com/graph-gophers/graphql-go/internal/common
|
||||||
|
github.com/graph-gophers/graphql-go/internal/exec
|
||||||
|
github.com/graph-gophers/graphql-go/internal/exec/packer
|
||||||
|
github.com/graph-gophers/graphql-go/internal/exec/resolvable
|
||||||
|
github.com/graph-gophers/graphql-go/internal/exec/selected
|
||||||
|
github.com/graph-gophers/graphql-go/internal/query
|
||||||
|
github.com/graph-gophers/graphql-go/internal/schema
|
||||||
|
github.com/graph-gophers/graphql-go/internal/validation
|
||||||
|
github.com/graph-gophers/graphql-go/introspection
|
||||||
|
github.com/graph-gophers/graphql-go/log
|
||||||
|
github.com/graph-gophers/graphql-go/trace
|
||||||
|
github.com/graph-gophers/graphql-go/types
|
||||||
# github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
|
# github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
|
||||||
github.com/gregjones/httpcache
|
github.com/gregjones/httpcache
|
||||||
# github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c
|
# github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user