Compare commits

...
4 Commits
Author SHA1 Message Date
lukaszraczylo c8ed2290b4 Add endpoint status code to log output.
Ensure library works with publicly available endpoints.
2021-12-11 00:03:25 +00:00
lukaszraczylo d1406eeac8 Improve http2 / SSL support 2021-12-10 22:33:47 +00:00
lukaszraczylo ea7ec683ef Check for possible cache eviction. 2021-12-03 15:12:02 +00:00
lukaszraczylo 5cd22318f1 Add environment variables to steer the query cache. 2021-12-03 09:12:59 +00:00
4 changed files with 93 additions and 16 deletions
+4 -1
View File
@@ -23,12 +23,15 @@ Therefore, I present you the simple client to which you can copy & paste your gr
* Executing GraphQL queries as they are, without types declaration * Executing GraphQL queries as they are, without types declaration
* HTTP2 support! * HTTP2 support!
* Support for additional headers * Support for additional headers
* Query cache
## Usage example ## Usage example
### Environment variables ### Environment variables
* `GRAPHQL_ENDPOINT` - Your GraphQL endpoint. Default: `http://127.0.0.1:9090/v1/graphql` * `GRAPHQL_ENDPOINT` - Your GraphQL endpoint. Default: `http://127.0.0.1:9090/v1/graphql`
* `GRAPHQL_CACHE` - Should the query cache be enabled? Default: `false`
* `GRAPHQL_CACHE_TTL` - Cache TTL in seconds for SELECT type of queries. Default: `5`
* `LOG_LEVEL` - Logging level. Default: `info` * `LOG_LEVEL` - Logging level. Default: `info`
### Example reader code ### Example reader code
@@ -37,7 +40,7 @@ Therefore, I present you the simple client to which you can copy & paste your gr
```go ```go
import ( import (
fmt fmt
gql "github.com/lukaszraczylo/simple-gql-client" gql "github.com/lukaszraczylo/go-simple-graphql"
) )
headers := map[string]interface{}{ headers := map[string]interface{}{
+34 -7
View File
@@ -15,9 +15,9 @@ package gql
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net"
"net/http" "net/http"
"os" "os"
"strconv"
"time" "time"
"github.com/allegro/bigcache/v3" "github.com/allegro/bigcache/v3"
@@ -49,27 +49,54 @@ func pickGraphqlEndpoint() (graphqlEndpoint string) {
return graphqlEndpoint return graphqlEndpoint
} }
func setCacheTTL() int {
value, present := os.LookupEnv("GRAPHQL_CACHE_TTL")
if present && value != "" {
i, err := strconv.Atoi(value)
if err != nil {
panic("Invalid value for query cache ttl")
}
return int(i)
} else {
return 5
}
}
func setCacheEnabled() bool {
value, present := os.LookupEnv("GRAPHQL_CACHE")
if present && value != "" {
b, err := strconv.ParseBool(value)
if err != nil {
panic("Invalid value for query cache")
}
return b
} else {
return false
}
}
func NewConnection() *GraphQL { func NewConnection() *GraphQL {
return &GraphQL{ g := GraphQL{
Endpoint: pickGraphqlEndpoint(), Endpoint: pickGraphqlEndpoint(),
HttpClient: &http.Client{ HttpClient: &http.Client{
Transport: &http2.Transport{ Transport: &http2.Transport{
DisableCompression: false, DisableCompression: false,
AllowHTTP: true, AllowHTTP: true,
PingTimeout: 5 * time.Second, PingTimeout: 10 * time.Second,
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { TLSClientConfig: &tls.Config{
return net.Dial(network, addr) InsecureSkipVerify: true,
}, },
}, },
}, },
Log: logging.NewLogger(), Log: logging.NewLogger(),
Cache: false, Cache: setCacheEnabled(),
CacheStore: setupCache(), CacheStore: setupCache(),
} }
return &g
} }
func setupCache() *bigcache.BigCache { func setupCache() *bigcache.BigCache {
cache, err := bigcache.NewBigCache(bigcache.DefaultConfig(10 * time.Second)) cache, err := bigcache.NewBigCache(bigcache.DefaultConfig(time.Duration(setCacheTTL()) * time.Second))
if err != nil { if err != nil {
panic("Error creating cache: " + err.Error()) panic("Error creating cache: " + err.Error())
} }
+8 -6
View File
@@ -52,16 +52,18 @@ func (g *GraphQL) Query(queryContent string, queryVariables interface{}, queryHe
if g.Cache { if g.Cache {
g.Log.Debug("Checking the cache for the query", map[string]interface{}{"_query": queryHash}) g.Log.Debug("Checking the cache for the query", map[string]interface{}{"_query": queryHash})
if entry, err := g.CacheStore.Get(queryHash); err == nil { if entry, entryInfo, err := g.CacheStore.GetWithInfo(queryHash); err == nil {
g.Log.Debug("Found the query in the cache", map[string]interface{}{"_query": queryHash}) g.Log.Debug("Found the query in the cache", map[string]interface{}{"_query": queryHash})
return string(entry), nil if pandati.IsZero(entryInfo.EntryStatus) {
return string(entry), nil
}
} else { } else {
g.Log.Debug("Unable to find the query in the cache", map[string]interface{}{"_query": queryHash, "_error": err.Error()}) g.Log.Debug("Unable to find the query in the cache", map[string]interface{}{"_query": queryHash, "_error": err.Error()})
} }
} }
httpRequest, err := http.NewRequest("POST", g.Endpoint, bytes.NewBuffer(query)) httpRequest, err := http.NewRequest("POST", g.Endpoint, bytes.NewBuffer(query))
httpRequest.Header.Add("Content-Type", "application/json") //httpRequest.Header.Add("Content-Type", "application/json")
for header, value := range queryHeaders { for header, value := range queryHeaders {
httpRequest.Header.Add(fmt.Sprintf("%v", header), fmt.Sprintf("%v", value)) httpRequest.Header.Add(fmt.Sprintf("%v", header), fmt.Sprintf("%v", value))
} }
@@ -87,18 +89,18 @@ func (g *GraphQL) Query(queryContent string, queryVariables interface{}, queryHe
} }
if !pandati.IsZero(queryResult.Errors) { if !pandati.IsZero(queryResult.Errors) {
g.Log.Error("Query returned error", map[string]interface{}{"_query": queryContent, "_variables": queryVariables, "_error": fmt.Sprintf("%v", queryResult.Errors)}) g.Log.Error("Query returned error", map[string]interface{}{"_query": queryContent, "_variables": queryVariables, "_error": fmt.Sprintf("%v", queryResult.Errors), "_response_code": httpResponse.StatusCode})
return "", fmt.Errorf("%v", queryResult.Errors[0].Message) return "", fmt.Errorf("%v", queryResult.Errors[0].Message)
} }
if pandati.IsZero(queryResult.Data) { if pandati.IsZero(queryResult.Data) {
g.Log.Error("Query returned no data", map[string]interface{}{"_query": queryContent, "_variables": queryVariables}) g.Log.Error("Query returned no data", map[string]interface{}{"_query": queryContent, "_variables": queryVariables, "_response_code": httpResponse.StatusCode})
return "", errors.New("Query returned no data") return "", errors.New("Query returned no data")
} }
responseContent, err = json.MarshalToString(queryResult.Data) responseContent, err = json.MarshalToString(queryResult.Data)
if err != nil { if err != nil {
g.Log.Error("Invalid data result", map[string]interface{}{"_query": queryContent, "_variables": queryVariables, "_result": responseContent}) g.Log.Error("Invalid data result", map[string]interface{}{"_query": queryContent, "_variables": queryVariables, "_result": responseContent, "_response_code": httpResponse.StatusCode})
return "", err return "", err
} }
+47 -2
View File
@@ -2,6 +2,7 @@ package gql
import ( import (
"fmt" "fmt"
"os"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -59,6 +60,7 @@ func (suite *TestSuite) Test_GraphQL_Query() {
type args struct { type args struct {
queryContent string queryContent string
queryVariables interface{} queryVariables interface{}
queryHeaders map[string]interface{}
} }
tests := []struct { tests := []struct {
name string name string
@@ -132,6 +134,49 @@ func (suite *TestSuite) Test_GraphQL_Query() {
wantResult: ``, wantResult: ``,
wantErr: true, wantErr: true,
}, },
{
name: "Valid query to https endpoint",
endpoint: "https://web-dev.telegram-bot.app/v1/graphql",
isLocal: false,
args: args{
queryContent: `query packages_prices {
available_packages: tbl_available_packages(where: {enabled: {_eq: true}}) {
package_name
package_discount
package_price
package_size
package_type
}
}`,
queryVariables: nil,
},
wantResult: `{"available_packages":[{"package_discount":0,"package_name":"media_1000","package_price":750,"package_size":1000,"package_type":"media"},{"package_discount":5,"package_name":"media_5000","package_price":3563,"package_size":5000,"package_type":"media"},{"package_discount":10,"package_name":"media_10000","package_price":6750,"package_size":10000,"package_type":"media"},{"package_discount":15,"package_name":"media_25000","package_price":15938,"package_size":25000,"package_type":"media"},{"package_discount":0,"package_name":"voice_500","package_price":1000,"package_size":500,"package_type":"voice"},{"package_discount":10,"package_name":"voice_1000","package_price":1800,"package_size":1000,"package_type":"voice"},{"package_discount":15,"package_name":"voice_2500","package_price":4250,"package_size":2500,"package_type":"voice"}]}`,
wantErr: false,
},
{
name: "Valid query to github endpoint",
endpoint: "https://api.github.com/graphql",
isLocal: false,
args: args{
queryContent: `query {
repository(name: "semver-generator", owner: "lukaszraczylo", followRenames: true) {
releases(last: 2) {
nodes {
tag {
name
}
}
}
}
}`,
queryVariables: nil,
queryHeaders: map[string]interface{}{
"Authorization": "Bearer " + os.Getenv("GITHUB_TOKEN"),
},
},
wantResult: `{"repository":{"releases":{"nodes":[`,
wantErr: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
suite.T().Run(tt.name, func(t *testing.T) { suite.T().Run(tt.name, func(t *testing.T) {
@@ -140,11 +185,11 @@ func (suite *TestSuite) Test_GraphQL_Query() {
// } // }
g.Endpoint = tt.endpoint g.Endpoint = tt.endpoint
g.Cache = tt.cache_enabled g.Cache = tt.cache_enabled
gotResult, gotErr := g.Query(tt.args.queryContent, tt.args.queryVariables, nil) gotResult, gotErr := g.Query(tt.args.queryContent, tt.args.queryVariables, tt.args.queryHeaders)
if tt.wantErr { if tt.wantErr {
assert.Error(t, gotErr) assert.Error(t, gotErr)
} }
assert.Equal(t, tt.wantResult, gotResult) assert.Contains(t, gotResult, tt.wantResult)
}) })
} }
fmt.Println(g.CacheStore.Capacity()) fmt.Println(g.CacheStore.Capacity())