mirror of
https://github.com/lukaszraczylo/go-simple-graphql.git
synced 2026-10-14 00:58:32 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8ed2290b4
|
||
|
|
d1406eeac8
|
||
|
|
ea7ec683ef
|
||
|
|
5cd22318f1
|
@@ -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{}{
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user