192 lines
4.4 KiB
Go
192 lines
4.4 KiB
Go
package graphql
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"reflect"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/llehouerou/go-graphql-client"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/time/rate"
|
|
)
|
|
|
|
const (
|
|
rateLimitPeriod = time.Second
|
|
rateLimitBurst = 60
|
|
MaxAuthenticatedQueryComplexity = 30000
|
|
MaxAnonymousQueryComplexity = 500
|
|
)
|
|
|
|
type Client struct {
|
|
httpClient *http.Client
|
|
gql *graphql.Client
|
|
rl *rate.Limiter
|
|
apirl *rate.Limiter
|
|
lock *sync.Mutex
|
|
authenticated bool
|
|
token JwtToken
|
|
apiKey string
|
|
}
|
|
|
|
func NewClient(httpclient *http.Client, baseUrl string) *Client {
|
|
return &Client{
|
|
httpClient: httpclient,
|
|
gql: graphql.NewClient(baseUrl, httpclient),
|
|
rl: rate.NewLimiter(rate.Every(rateLimitPeriod), rateLimitBurst),
|
|
apirl: rate.NewLimiter(rate.Every(rateLimitPeriod/10), rateLimitBurst*10),
|
|
lock: &sync.Mutex{},
|
|
authenticated: false,
|
|
}
|
|
}
|
|
|
|
func (c *Client) GetCurrentToken() JwtToken {
|
|
return c.token
|
|
}
|
|
|
|
func (c *Client) SetJWTToken(token JwtToken, audience string) {
|
|
c.gql = c.gql.WithRequestModifier(func(request *http.Request) {
|
|
request.Header.Set("Authorization", "Bearer "+token.Token)
|
|
request.Header.Set("JWT-AUD", audience)
|
|
})
|
|
c.token = token
|
|
c.authenticated = true
|
|
}
|
|
|
|
func (c *Client) SetApiKey(apiKey string) {
|
|
c.gql = c.gql.WithRequestModifier(func(request *http.Request) {
|
|
request.Header.Set("APIKEY", apiKey)
|
|
})
|
|
c.apiKey = apiKey
|
|
}
|
|
|
|
func (c *Client) MaxQueryComplexity() int {
|
|
if c.apiKey != "" || c.authenticated {
|
|
return MaxAuthenticatedQueryComplexity
|
|
}
|
|
return MaxAnonymousQueryComplexity
|
|
}
|
|
|
|
func (c *Client) ConstructRawQuery(
|
|
q interface{},
|
|
variables map[string]interface{},
|
|
) (string, error) {
|
|
return graphql.ConstructQuery(q, variables)
|
|
}
|
|
|
|
func (c *Client) Query(
|
|
ctx context.Context,
|
|
q interface{},
|
|
variables interface{},
|
|
options ...graphql.Option,
|
|
) error {
|
|
if c.apiKey != "" {
|
|
err := c.apirl.Wait(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "waiting for rate limit")
|
|
}
|
|
} else {
|
|
err := c.rl.Wait(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "waiting for rate limit")
|
|
}
|
|
}
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
return c.gql.Query(ctx, q, variables, options...)
|
|
}
|
|
|
|
func (c *Client) QueryRaw(
|
|
ctx context.Context,
|
|
q interface{},
|
|
variables interface{},
|
|
options ...graphql.Option,
|
|
) ([]byte, error) {
|
|
if c.apiKey != "" {
|
|
err := c.apirl.Wait(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "waiting for rate limit")
|
|
}
|
|
} else {
|
|
err := c.rl.Wait(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "waiting for rate limit")
|
|
}
|
|
}
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
return c.gql.QueryRaw(ctx, q, variables, options...)
|
|
}
|
|
|
|
func (c *Client) Mutate(
|
|
ctx context.Context,
|
|
q interface{},
|
|
variables interface{},
|
|
options ...graphql.Option,
|
|
) error {
|
|
if c.apiKey != "" {
|
|
err := c.apirl.Wait(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "waiting for rate limit")
|
|
}
|
|
} else {
|
|
err := c.rl.Wait(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "waiting for rate limit")
|
|
}
|
|
}
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
return c.gql.Mutate(ctx, q, variables, options...)
|
|
}
|
|
|
|
func GetTypeComplexity(t reflect.Type) int {
|
|
var checkStruct func(t reflect.Type, complexity *int)
|
|
checkStruct = func(t reflect.Type, complexity *int) {
|
|
if t.Kind() != reflect.Struct {
|
|
return
|
|
}
|
|
for i := 0; i < t.NumField(); i++ {
|
|
field := t.Field(i)
|
|
tag := field.Tag.Get("graphql")
|
|
if field.Type.Kind() == reflect.Struct {
|
|
*complexity++
|
|
if len(tag) > 6 && tag[:6] == "... on" {
|
|
*complexity--
|
|
}
|
|
checkStruct(field.Type, complexity)
|
|
continue
|
|
}
|
|
if field.Type.Kind() == reflect.Slice {
|
|
if isSimpleType(field.Type.Elem()) {
|
|
*complexity++
|
|
} else {
|
|
*complexity++
|
|
tmpcomplexity := 0
|
|
checkStruct(field.Type.Elem(), &tmpcomplexity)
|
|
*complexity += tmpcomplexity * 10
|
|
}
|
|
continue
|
|
}
|
|
if tag != "" && tag != "__typename" {
|
|
*complexity++
|
|
}
|
|
}
|
|
}
|
|
complexity := 0
|
|
checkStruct(t, &complexity)
|
|
return complexity
|
|
}
|
|
|
|
func isSimpleType(t reflect.Type) bool {
|
|
switch t.Kind() {
|
|
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
|
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|