mirror of
https://github.com/lukaszraczylo/go-simple-graphql.git
synced 2026-10-12 00:45:31 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f4c8cc5d8 | ||
|
|
154233df14
|
||
|
|
29afbd08fd
|
||
|
|
a107ee953b
|
||
|
|
58094f85c7
|
||
|
|
ac957ac95b
|
||
|
|
ef4ab45424
|
||
|
|
4fc826c1f6
|
||
|
|
3217edf37f
|
||
|
|
ae96059f61
|
||
|
|
2ce7016d7e
|
||
|
|
9da7c9bec9
|
||
|
|
ca13be5ef6
|
||
|
|
1df2ae5d16
|
||
|
|
39f489210b
|
||
|
|
21c9119bcb
|
||
|
|
a7b385f0b7
|
||
|
|
7d3abf5464
|
||
|
|
7af4e1f96a
|
||
|
|
7623410bfe
|
||
|
|
8e26d79df7
|
||
|
|
aa25c43795
|
||
|
|
dfb082ad51
|
||
|
|
ce736958c6
|
||
|
|
4472041b21
|
||
|
|
b81e612f51
|
||
|
|
3c508eaf15
|
||
|
|
4d0c04cb26
|
||
|
|
55182f2f3d
|
||
|
|
841c136e71
|
||
|
|
833517f35c
|
||
|
|
07e4f75a0a
|
||
|
|
b8044c5d67
|
||
|
|
cf3076d701
|
||
|
|
5a222aa316
|
||
|
|
d843238d46
|
@@ -12,6 +12,6 @@ jobs:
|
|||||||
shared:
|
shared:
|
||||||
uses: telegram-bot-app/ci-scripts/.github/workflows/library-test-publish.yaml@main
|
uses: telegram-bot-app/ci-scripts/.github/workflows/library-test-publish.yaml@main
|
||||||
with:
|
with:
|
||||||
enable-code-scans: true
|
enable-code-scans: false
|
||||||
secrets:
|
secrets:
|
||||||
ghcr-token: ${{ secrets.GHCR_TOKEN }}
|
ghcr-token: ${{ secrets.GHCR_TOKEN }}
|
||||||
@@ -9,6 +9,7 @@ Ps. It's Hasura friendly.
|
|||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Usage example](#usage-example)
|
- [Usage example](#usage-example)
|
||||||
- [Environment variables](#environment-variables)
|
- [Environment variables](#environment-variables)
|
||||||
|
- [Modifiers on the fly](#modifiers-on-the-fly)
|
||||||
- [Cache](#cache)
|
- [Cache](#cache)
|
||||||
- [Example reader code](#example-reader-code)
|
- [Example reader code](#example-reader-code)
|
||||||
- [Tips](#tips)
|
- [Tips](#tips)
|
||||||
@@ -40,6 +41,11 @@ Therefore, I present you the simple client to which you can copy & paste your gr
|
|||||||
* `GRAPHQL_RETRIES_NUMBER` - Number of retries: Default: `1`
|
* `GRAPHQL_RETRIES_NUMBER` - Number of retries: Default: `1`
|
||||||
* `GRAPHQL_RETRIES_DELAY` - Delay in retries in milliseconds. Default: `250`
|
* `GRAPHQL_RETRIES_DELAY` - Delay in retries in milliseconds. Default: `250`
|
||||||
|
|
||||||
|
### Modifiers on the fly
|
||||||
|
|
||||||
|
* `gql.SetEndpoint('your-endpoint-url')` - modifies endpoint, without the need to set the environment variable
|
||||||
|
* `gql.SetOutput('byte')` - modifies output format, without the need to set the environment variable
|
||||||
|
|
||||||
### Cache
|
### Cache
|
||||||
|
|
||||||
You have two options to enable the cache:
|
You have two options to enable the cache:
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
package gql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/lukaszraczylo/go-simple-graphql/utils/concurrency"
|
|
||||||
"github.com/lukaszraczylo/go-simple-graphql/utils/logger"
|
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
|
||||||
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/allegro/bigcache"
|
|
||||||
"github.com/gookit/goutil/envutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewConnection() *BaseClient {
|
|
||||||
|
|
||||||
b := &BaseClient{
|
|
||||||
client: &http.Client{},
|
|
||||||
concurrencyManager: concurrency.NewPool(-1),
|
|
||||||
MaxGoRoutines: -1,
|
|
||||||
LoggingLevel: logger.Warn,
|
|
||||||
LoggerColorful: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.LoggerWriter == nil {
|
|
||||||
b.LoggerWriter = log.New(os.Stdout, "", log.LstdFlags)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.endpoint = envutil.Getenv("GRAPHQL_ENDPOINT", "https://api.github.com/graphql")
|
|
||||||
b.responseType = envutil.Getenv("GRAPHQL_OUTPUT", "string")
|
|
||||||
b.validate = envutil.GetBool("GRAPHQL_VALIDATE", false)
|
|
||||||
b.cache.enabled = envutil.GetBool("GRAPHQL_CACHE_ENABLED", true)
|
|
||||||
b.cache.ttl = envutil.GetInt("GRAPHQL_CACHE_TTL", 60)
|
|
||||||
|
|
||||||
b.retries.enabled = envutil.GetBool("GRAPHQL_RETRIES_ENABLED", true)
|
|
||||||
b.retries.max = envutil.GetInt("GRAPHQL_RETRIES_MAX", 1)
|
|
||||||
b.retries.delay = envutil.GetInt("GRAPHQL_RETRIES_DELAY", 300)
|
|
||||||
|
|
||||||
if b.cache.enabled {
|
|
||||||
var err error
|
|
||||||
b.cache.client, err = bigcache.NewBigCache(bigcache.DefaultConfig(time.Duration(b.cache.ttl) * time.Second))
|
|
||||||
if err != nil {
|
|
||||||
b.Logger.Error(b, "Error while creating cache client;", "error", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b_tmp_log_level := envutil.Getenv("LOG_LEVEL", "info")
|
|
||||||
|
|
||||||
switch b_tmp_log_level {
|
|
||||||
case "silent":
|
|
||||||
b.LoggingLevel = logger.Silent
|
|
||||||
case "error":
|
|
||||||
b.LoggingLevel = logger.Error
|
|
||||||
case "warn":
|
|
||||||
b.LoggingLevel = logger.Warn
|
|
||||||
case "info":
|
|
||||||
b.LoggingLevel = logger.Info
|
|
||||||
case "debug":
|
|
||||||
b.LoggingLevel = logger.Debug
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Logger = NewLogger(b.LoggerWriter, logger.Config{
|
|
||||||
Colorful: b.LoggerColorful,
|
|
||||||
LogLevel: b.LoggingLevel,
|
|
||||||
})
|
|
||||||
|
|
||||||
var httpClient *http.Client
|
|
||||||
if strings.HasPrefix(b.endpoint, "http://") {
|
|
||||||
httpClient = &http.Client{
|
|
||||||
Transport: http.DefaultTransport,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tlsClientConfig := &tls.Config{}
|
|
||||||
if strings.HasPrefix(b.endpoint, "https://") {
|
|
||||||
tlsClientConfig.InsecureSkipVerify = true
|
|
||||||
}
|
|
||||||
http2Transport := &http2.Transport{
|
|
||||||
AllowHTTP: true,
|
|
||||||
TLSClientConfig: tlsClientConfig,
|
|
||||||
}
|
|
||||||
httpClient = &http.Client{
|
|
||||||
Transport: http2Transport,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.client = httpClient
|
|
||||||
|
|
||||||
b.Logger.Debug(b, "graphQL client initialized;", "endpoint", b.endpoint, "responseType", b.responseType, "validate", b.validate, "cache", b.cache.enabled, "cacheTTL", b.cache.ttl, "maxGoRoutines", b.MaxGoRoutines, "loggingLevel", b_tmp_log_level, "loggerColorful", b.LoggerColorful)
|
|
||||||
b.Logger.Info(b, "graphQL client initialized;")
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BaseClient) SetEndpoint(endpoint string) {
|
|
||||||
b.endpoint = endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BaseClient) SetOutput(output string) {
|
|
||||||
b.responseType = output
|
|
||||||
}
|
|
||||||
-131
@@ -1,131 +0,0 @@
|
|||||||
package gql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/lukaszraczylo/go-simple-graphql/utils/logger"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewConnection(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
want *BaseClient
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test NewConnection",
|
|
||||||
want: &BaseClient{
|
|
||||||
endpoint: "https://api.github.com/graphql",
|
|
||||||
responseType: "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// temporarily set GRAPHQL_ENDPOINT env variable to https://api.github.com/graphql
|
|
||||||
// and GRAPHQL_OUTPUT to string
|
|
||||||
current := os.Getenv("GRAPHQL_ENDPOINT")
|
|
||||||
defer os.Setenv("GRAPHQL_ENDPOINT", current)
|
|
||||||
os.Setenv("GRAPHQL_ENDPOINT", "https://api.github.com/graphql")
|
|
||||||
|
|
||||||
got := NewConnection()
|
|
||||||
assert.Equal(t, tt.want.endpoint, got.endpoint)
|
|
||||||
assert.Equal(t, tt.want.responseType, got.responseType)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewConnectionLogLevels(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
want *BaseClient
|
|
||||||
name string
|
|
||||||
env string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test NewConnection with log level set to debug",
|
|
||||||
env: "debug",
|
|
||||||
want: &BaseClient{
|
|
||||||
endpoint: "https://api.github.com/graphql",
|
|
||||||
responseType: "string",
|
|
||||||
LoggingLevel: logger.Debug,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "Test NewConnection with log level set to info",
|
|
||||||
env: "info",
|
|
||||||
want: &BaseClient{
|
|
||||||
endpoint: "https://api.github.com/graphql",
|
|
||||||
responseType: "string",
|
|
||||||
LoggingLevel: logger.Info,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "Test NewConnection with log level set to warn",
|
|
||||||
env: "warn",
|
|
||||||
want: &BaseClient{
|
|
||||||
endpoint: "https://api.github.com/graphql",
|
|
||||||
responseType: "string",
|
|
||||||
LoggingLevel: logger.Warn,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "Test NewConnection with log level set to error",
|
|
||||||
env: "error",
|
|
||||||
want: &BaseClient{
|
|
||||||
endpoint: "https://api.github.com/graphql",
|
|
||||||
responseType: "string",
|
|
||||||
LoggingLevel: logger.Error,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
// temporarily set GRAPHQL_ENDPOINT env variable to https://api.github.com/graphql
|
|
||||||
// and GRAPHQL_OUTPUT to string
|
|
||||||
current := os.Getenv("GRAPHQL_ENDPOINT")
|
|
||||||
defer os.Setenv("GRAPHQL_ENDPOINT", current)
|
|
||||||
os.Setenv("GRAPHQL_ENDPOINT", "https://api.github.com/graphql")
|
|
||||||
current_log_level := os.Getenv("LOG_LEVEL")
|
|
||||||
defer os.Setenv("LOG_LEVEL", current_log_level)
|
|
||||||
os.Setenv("LOG_LEVEL", tt.env)
|
|
||||||
|
|
||||||
got := NewConnection()
|
|
||||||
assert.Equal(t, tt.want.endpoint, got.endpoint)
|
|
||||||
assert.Equal(t, tt.want.responseType, got.responseType)
|
|
||||||
assert.Equal(t, tt.want.LoggingLevel, got.LoggingLevel)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBaseClient_SetEndpoint(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
endpoint string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
args args
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test SetEndpoint",
|
|
||||||
args: args{
|
|
||||||
endpoint: "https://potato/graphql",
|
|
||||||
},
|
|
||||||
want: "https://potato/graphql",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
b := NewConnection()
|
|
||||||
b.SetEndpoint(tt.args.endpoint)
|
|
||||||
assert.Equal(t, tt.want, b.endpoint)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package gql
|
|
||||||
|
|
||||||
func (c *BaseClient) cacheLookup(hash string) []byte {
|
|
||||||
if c.cache.client != nil {
|
|
||||||
g, err := c.cache.client.Get(hash)
|
|
||||||
if err == nil {
|
|
||||||
c.Logger.Debug(c, "Cache hit;", "hash", hash)
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
// if error is not nil it means that hash is not present in cache
|
|
||||||
c.Logger.Debug(c, "Cache miss;", "hash", hash)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Vendored
+148
@@ -0,0 +1,148 @@
|
|||||||
|
package libpack_cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CacheEntry struct {
|
||||||
|
ExpiresAt time.Time
|
||||||
|
Value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cache struct {
|
||||||
|
bytePool sync.Pool
|
||||||
|
entries sync.Map
|
||||||
|
globalTTL time.Duration
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(globalTTL time.Duration) *Cache {
|
||||||
|
cache := &Cache{
|
||||||
|
globalTTL: globalTTL,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the byte pool.
|
||||||
|
cache.bytePool.New = func() interface{} {
|
||||||
|
return make([]byte, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the cache cleanup.
|
||||||
|
if flag.Lookup("test.v") == nil {
|
||||||
|
go cache.cleanupRoutine(globalTTL)
|
||||||
|
}
|
||||||
|
return cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) cleanupRoutine(globalTTL time.Duration) {
|
||||||
|
ticker := time.NewTicker(globalTTL / 2)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for range ticker.C {
|
||||||
|
c.CleanExpiredEntries()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Set(key string, value []byte, ttl time.Duration) {
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
expiresAt := time.Now().Add(ttl)
|
||||||
|
|
||||||
|
compressedValue, err := c.compress(value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a byte slice from the pool and ensure it's properly sized.
|
||||||
|
b := c.bytePool.Get().([]byte)
|
||||||
|
if cap(b) < len(compressedValue) {
|
||||||
|
b = make([]byte, len(compressedValue))
|
||||||
|
} else {
|
||||||
|
b = b[:len(compressedValue)]
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(b, compressedValue)
|
||||||
|
|
||||||
|
entry := CacheEntry{
|
||||||
|
Value: b,
|
||||||
|
ExpiresAt: expiresAt,
|
||||||
|
}
|
||||||
|
c.entries.Store(key, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Get(key string) ([]byte, bool) {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
entry, ok := c.entries.Load(key)
|
||||||
|
if !ok || entry.(CacheEntry).ExpiresAt.Before(time.Now()) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
compressedValue := entry.(CacheEntry).Value
|
||||||
|
value, err := c.decompress(compressedValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Delete(key string) {
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
|
||||||
|
entry, ok := c.entries.Load(key)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the byte slice to the pool.
|
||||||
|
c.bytePool.Put(entry.(CacheEntry).Value)
|
||||||
|
|
||||||
|
// Delete the entry from the cache.
|
||||||
|
c.entries.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) CleanExpiredEntries() {
|
||||||
|
now := time.Now()
|
||||||
|
c.entries.Range(func(key, value interface{}) bool {
|
||||||
|
entry := value.(CacheEntry)
|
||||||
|
if entry.ExpiresAt.Before(now) {
|
||||||
|
// Return the byte slice to the pool.
|
||||||
|
c.bytePool.Put(entry.Value)
|
||||||
|
|
||||||
|
// Delete the entry from the cache.
|
||||||
|
c.entries.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true to continue iterating over the map.
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) compress(data []byte) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
w := gzip.NewWriter(&buf)
|
||||||
|
_, err := w.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) decompress(data []byte) ([]byte, error) {
|
||||||
|
r, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
return io.ReadAll(r)
|
||||||
|
}
|
||||||
Vendored
+148
@@ -0,0 +1,148 @@
|
|||||||
|
package libpack_cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CacheTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CacheTestSuite) SetupTest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCachingTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(CacheTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CacheTestSuite) Test_New() {
|
||||||
|
suite.T().Run("should return a new cache", func(t *testing.T) {
|
||||||
|
cache := New(2 * time.Second)
|
||||||
|
suite.NotNil(cache)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CacheTestSuite) Test_CacheUse() {
|
||||||
|
cache := New(30 * time.Second)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cache_value string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test1",
|
||||||
|
cache_value: "test1-123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test2",
|
||||||
|
cache_value: "test2-123",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
suite.T().Run(tt.name, func(t *testing.T) {
|
||||||
|
cache.Set(tt.name, []byte(tt.name), 5*time.Second)
|
||||||
|
c, ok := cache.Get(tt.name)
|
||||||
|
suite.Equal(true, ok)
|
||||||
|
suite.Equal(tt.name, string(c))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CacheTestSuite) Test_CacheDelete() {
|
||||||
|
cache := New(30 * time.Second)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cache_value string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test1",
|
||||||
|
cache_value: "test1-123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test2",
|
||||||
|
cache_value: "test2-123",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
suite.T().Run(tt.name, func(t *testing.T) {
|
||||||
|
cache.Set(tt.name, []byte(tt.name), 5*time.Second)
|
||||||
|
c, ok := cache.Get(tt.name)
|
||||||
|
suite.Equal(true, ok)
|
||||||
|
suite.Equal(tt.name, string(c))
|
||||||
|
cache.Delete(tt.name)
|
||||||
|
c, ok = cache.Get(tt.name)
|
||||||
|
suite.Equal(false, ok)
|
||||||
|
suite.Equal("", string(c))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CacheTestSuite) Test_CacheExpire() {
|
||||||
|
cache := New(30 * time.Second)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cache_value string
|
||||||
|
ttl time.Duration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test1",
|
||||||
|
cache_value: "test1-123",
|
||||||
|
ttl: 2 * time.Second,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test2",
|
||||||
|
cache_value: "test2-123",
|
||||||
|
ttl: 5 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
suite.T().Run(tt.name, func(t *testing.T) {
|
||||||
|
cache.Set(tt.name, []byte(tt.name), tt.ttl)
|
||||||
|
c, ok := cache.Get(tt.name)
|
||||||
|
suite.Equal(true, ok)
|
||||||
|
suite.Equal(tt.name, string(c))
|
||||||
|
time.Sleep(tt.ttl)
|
||||||
|
c, ok = cache.Get(tt.name)
|
||||||
|
suite.Equal(false, ok)
|
||||||
|
suite.Equal("", string(c))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CacheTestSuite) Test_CacheCleanExpiredEntries() {
|
||||||
|
cache := New(5 * time.Second)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cache_value string
|
||||||
|
ttl time.Duration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test1",
|
||||||
|
cache_value: "test1-123",
|
||||||
|
ttl: 2 * time.Second,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test2",
|
||||||
|
cache_value: "test2-123",
|
||||||
|
ttl: 5 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
suite.T().Run(tt.name, func(t *testing.T) {
|
||||||
|
cache.Set(tt.name, []byte(tt.name), tt.ttl)
|
||||||
|
c, ok := cache.Get(tt.name)
|
||||||
|
suite.Equal(true, ok)
|
||||||
|
suite.Equal(tt.name, string(c))
|
||||||
|
time.Sleep(tt.ttl)
|
||||||
|
c, ok = cache.Get(tt.name)
|
||||||
|
suite.Equal(false, ok)
|
||||||
|
suite.Equal("", string(c))
|
||||||
|
cache.CleanExpiredEntries()
|
||||||
|
c, ok = cache.Get(tt.name)
|
||||||
|
suite.Equal(false, ok)
|
||||||
|
suite.Equal("", string(c))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package gql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/avast/retry-go/v4"
|
||||||
|
"github.com/goccy/go-json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (qe *QueryExecutor) executeQuery() ([]byte, error) {
|
||||||
|
httpRequest, err := http.NewRequest("POST", qe.endpoint, bytes.NewBuffer(qe.Query))
|
||||||
|
if err != nil {
|
||||||
|
qe.Logger.Error("Can't create HTTP request;", map[string]interface{}{"error": err})
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range qe.Headers {
|
||||||
|
strValue, ok := value.(string)
|
||||||
|
if !ok {
|
||||||
|
strValue = fmt.Sprintf("%v", value)
|
||||||
|
}
|
||||||
|
httpRequest.Header.Set(key, strValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
var retries_max int
|
||||||
|
|
||||||
|
if qe.Retries {
|
||||||
|
qe.Logger.Debug("Retries enabled - setting max retries", map[string]interface{}{"retries": qe.retries_number})
|
||||||
|
retries_max = qe.retries_number
|
||||||
|
} else {
|
||||||
|
qe.Logger.Debug("Retries disabled - setting max retries", map[string]interface{}{"retries": 1})
|
||||||
|
retries_max = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpResponse *http.Response
|
||||||
|
var queryResult queryResults
|
||||||
|
|
||||||
|
err = retry.Do(
|
||||||
|
func() error {
|
||||||
|
httpResponse, err = qe.client.Do(httpRequest)
|
||||||
|
if err != nil {
|
||||||
|
qe.Logger.Debug("Error while executing http request", map[string]interface{}{"error": err.Error()})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_, err := io.Copy(io.Discard, httpResponse.Body)
|
||||||
|
if err != nil {
|
||||||
|
qe.Logger.Debug("Error while discarding http response body;", map[string]interface{}{"error": err.Error()})
|
||||||
|
}
|
||||||
|
httpResponse.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 204 {
|
||||||
|
return fmt.Errorf("HTTP error - unacceptable status code: \"%s\" for \"%s\"", httpResponse.Status, httpRequest.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
var reader io.ReadCloser
|
||||||
|
encoding := httpResponse.Header.Get("Content-Encoding")
|
||||||
|
if encoding == "gzip" {
|
||||||
|
reader, err = gzip.NewReader(httpResponse.Body)
|
||||||
|
if err != nil {
|
||||||
|
qe.Logger.Debug("Error while creating gzip reader;", map[string]interface{}{"error": err.Error()})
|
||||||
|
return fmt.Errorf("Error while creating gzip reader: %s", err.Error())
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
} else {
|
||||||
|
reader = httpResponse.Body
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
qe.Logger.Debug("Error while reading http response;", map[string]interface{}{"error": err.Error()})
|
||||||
|
return fmt.Errorf("Error while reading http response: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &queryResult)
|
||||||
|
if err != nil {
|
||||||
|
qe.Logger.Debug("Error while unmarshalling http response;", map[string]interface{}{"error": err.Error()})
|
||||||
|
return fmt.Errorf("Error while unmarshalling http response: %s", err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
retry.OnRetry(func(n uint, err error) {
|
||||||
|
qe.Logger.Warning("Retrying query", map[string]interface{}{"error": err.Error(), "attempt": n})
|
||||||
|
}),
|
||||||
|
retry.Attempts(uint(retries_max)),
|
||||||
|
retry.DelayType(retry.BackOffDelay),
|
||||||
|
retry.Delay(time.Duration(qe.retries_delay)),
|
||||||
|
retry.LastErrorOnly(true),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
qe.Logger.Debug("Error while executing http request - target server", map[string]interface{}{"error": err.Error()})
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(queryResult.Errors) > 0 {
|
||||||
|
qe.Logger.Debug("Error while executing query;", map[string]interface{}{"error": queryResult.Errors})
|
||||||
|
return nil, fmt.Errorf("Error while executing query: %s", queryResult.Errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryResult.Data == nil {
|
||||||
|
qe.Logger.Debug("Error while executing query", map[string]interface{}{"error": "no data"})
|
||||||
|
return nil, fmt.Errorf("Error while executing query: no data")
|
||||||
|
}
|
||||||
|
|
||||||
|
json_data, err := json.Marshal(queryResult.Data)
|
||||||
|
if err != nil {
|
||||||
|
qe.Logger.Debug("Error while marshalling query result;", map[string]interface{}{"error": err.Error(), "data": queryResult.Data})
|
||||||
|
return nil, fmt.Errorf("Error while marshalling query result: %s. Data: %s", err.Error(), queryResult.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if qe.CacheKey != "no-cache" {
|
||||||
|
qe.cache.Set(qe.CacheKey, json_data, qe.CacheTTL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_data, nil
|
||||||
|
}
|
||||||
@@ -1,29 +1,26 @@
|
|||||||
module github.com/lukaszraczylo/go-simple-graphql
|
module github.com/lukaszraczylo/go-simple-graphql
|
||||||
|
|
||||||
go 1.20
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/allegro/bigcache v1.2.1
|
github.com/avast/retry-go/v4 v4.5.1
|
||||||
github.com/avast/retry-go/v4 v4.3.3
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/gookit/goutil v0.6.8
|
github.com/gookit/goutil v0.6.15
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/rs/zerolog v1.32.0
|
||||||
github.com/stretchr/testify v1.8.2
|
github.com/stretchr/testify v1.8.4
|
||||||
golang.org/x/net v0.9.0
|
golang.org/x/net v0.20.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gookit/color v1.5.3 // indirect
|
github.com/gookit/color v1.5.4 // indirect
|
||||||
github.com/kr/pretty v0.3.0 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 // indirect
|
golang.org/x/sync v0.6.0 // indirect
|
||||||
golang.org/x/sync v0.1.0 // indirect
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.7.0 // indirect
|
golang.org/x/term v0.16.0 // indirect
|
||||||
golang.org/x/term v0.7.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,61 +1,47 @@
|
|||||||
github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
|
github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o=
|
||||||
github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc=
|
||||||
github.com/avast/retry-go/v4 v4.3.3 h1:G56Bp6mU0b5HE1SkaoVjscZjlQb0oy4mezwY/cGH19w=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/avast/retry-go/v4 v4.3.3/go.mod h1:rg6XFaiuFYII0Xu3RDbZQkxCofFwruZKW8oEF1jpWiU=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gookit/goutil v0.6.8 h1:B2XXSCGav5TXWtKRT9i/s/owOLXXB7sY6UsfqeSLroE=
|
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||||
github.com/gookit/goutil v0.6.8/go.mod h1:u+Isykc6RQcZ4GQzulsaGm+Famd97U5Tzp3aQyo+jyA=
|
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/gookit/goutil v0.6.15 h1:mMQ0ElojNZoyPD0eVROk5QXJPh2uKR4g06slgPDF5Jo=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/gookit/goutil v0.6.15/go.mod h1:qdKdYEHQdEtyH+4fNdQNZfJHhI0jUZzHxQVAV3DaMDY=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4=
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
||||||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||||
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package gql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/goccy/go-json"
|
||||||
|
"github.com/gookit/goutil/strutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func searchForKeysInMapStringInterface(msi map[string]interface{}, key string) (value any) {
|
||||||
|
if msi == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return msi[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateHash(query *Query) string {
|
||||||
|
return strutil.Md5(query.JsonQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseClient) cacheLookup(hash string) []byte {
|
||||||
|
obj, _ := b.cache.Get(hash)
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseClient) decodeResponse(response []byte) (any, error) {
|
||||||
|
switch b.responseType {
|
||||||
|
case "mapstring":
|
||||||
|
var result map[string]interface{}
|
||||||
|
err := json.Unmarshal(response, &result)
|
||||||
|
if err != nil {
|
||||||
|
b.Logger.Error("Can't decode response into mapstring", map[string]interface{}{"error": err.Error()})
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
case "string":
|
||||||
|
return string(response), nil
|
||||||
|
case "byte":
|
||||||
|
return response, nil
|
||||||
|
default:
|
||||||
|
b.Logger.Error("Can't decode response;", map[string]interface{}{"error": "unknown response type"})
|
||||||
|
return nil, fmt.Errorf("Can't decode response - unknown response type specified")
|
||||||
|
}
|
||||||
|
}
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
package gql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (suite *Tests) Test_searchForKeysInMapStringInterface() {
|
||||||
|
type args struct {
|
||||||
|
msi map[string]interface{}
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
wantValue any
|
||||||
|
args args
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test_searchForKeysInMapStringInterface_exists",
|
||||||
|
args: args{
|
||||||
|
msi: map[string]interface{}{
|
||||||
|
"gqlcache": true,
|
||||||
|
"test": "test2",
|
||||||
|
},
|
||||||
|
key: "test",
|
||||||
|
},
|
||||||
|
wantValue: "test2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test_searchForKeysInMapStringInterface_not_exists",
|
||||||
|
args: args{
|
||||||
|
msi: map[string]interface{}{
|
||||||
|
"gqlcache": true,
|
||||||
|
"test": "test2",
|
||||||
|
},
|
||||||
|
key: "test-new",
|
||||||
|
},
|
||||||
|
wantValue: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
suite.T().Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(tt.wantValue, searchForKeysInMapStringInterface(tt.args.msi, tt.args.key))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *Tests) TestBaseClient_decodeResponse() {
|
||||||
|
type args struct {
|
||||||
|
response []byte
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want any
|
||||||
|
wantErr bool
|
||||||
|
setType string
|
||||||
|
wantType string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_decodeResponse_mapstring",
|
||||||
|
args: args{
|
||||||
|
response: []byte(`{"data":{"viewer":{"login":"lukaszraczylo"}}}`),
|
||||||
|
},
|
||||||
|
want: map[string]interface{}{
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"viewer": map[string]interface{}{
|
||||||
|
"login": "lukaszraczylo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setType: "mapstring",
|
||||||
|
wantType: "map[string]interface {}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_decodeResponse_string",
|
||||||
|
args: args{
|
||||||
|
response: []byte(`{"data":{"viewer":{"login":"lukaszraczylo"}}}`),
|
||||||
|
},
|
||||||
|
want: `{"data":{"viewer":{"login":"lukaszraczylo"}}}`,
|
||||||
|
setType: "string",
|
||||||
|
wantType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_decodeResponse_byte",
|
||||||
|
args: args{
|
||||||
|
response: []byte(`{"data":{"viewer":{"login":"lukaszraczylo"}}}`),
|
||||||
|
},
|
||||||
|
want: []byte(`{"data":{"viewer":{"login":"lukaszraczylo"}}}`),
|
||||||
|
setType: "byte",
|
||||||
|
wantType: "[]uint8",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
suite.T().Run(tt.name, func(t *testing.T) {
|
||||||
|
b := NewConnection()
|
||||||
|
b.SetOutput(tt.setType)
|
||||||
|
got, err := b.decodeResponse(tt.args.response)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("decodeResponse() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(tt.wantType, reflect.TypeOf(got).String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package gql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *BaseClient) createHttpClient() (http_client *http.Client) {
|
||||||
|
httpTransport := &http.Transport{
|
||||||
|
MaxIdleConns: 512,
|
||||||
|
MaxConnsPerHost: 512,
|
||||||
|
MaxIdleConnsPerHost: 512,
|
||||||
|
IdleConnTimeout: 15 * time.Second,
|
||||||
|
ResponseHeaderTimeout: 15 * time.Second,
|
||||||
|
DisableKeepAlives: false,
|
||||||
|
DisableCompression: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(b.endpoint, "http://") {
|
||||||
|
http_client = &http.Client{
|
||||||
|
Timeout: 15 * time.Second,
|
||||||
|
Transport: httpTransport,
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b.Logger.Debug("Using HTTP/1.1 over http")
|
||||||
|
} else if strings.HasPrefix(b.endpoint, "https://") {
|
||||||
|
tlsClientConfig := &tls.Config{}
|
||||||
|
if strings.HasPrefix(b.endpoint, "https://") {
|
||||||
|
tlsClientConfig.InsecureSkipVerify = true
|
||||||
|
}
|
||||||
|
http2Transport := &http2.Transport{
|
||||||
|
AllowHTTP: true,
|
||||||
|
TLSClientConfig: tlsClientConfig,
|
||||||
|
ReadIdleTimeout: 15 * time.Second,
|
||||||
|
PingTimeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
http_client = &http.Client{
|
||||||
|
Transport: http2Transport,
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b.Logger.Debug("Using HTTP/2 over https")
|
||||||
|
} else {
|
||||||
|
b.Logger.Critical("Invalid endpoint - neither http or https")
|
||||||
|
}
|
||||||
|
return http_client
|
||||||
|
}
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
package gql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/lukaszraczylo/go-simple-graphql/utils/logger"
|
|
||||||
|
|
||||||
"github.com/gookit/goutil/timex"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewLogger(writer Writer, config logger.Config) Logger {
|
|
||||||
colorfulPrint := "%s[%s]%s %%s %%s"
|
|
||||||
closeColor, debugColor, infoColor, warnColor, errorColor := "\033[0m", "\033[1;34m", "\033[1;32m", "\033[1;33m", "\033[1;31m"
|
|
||||||
if !config.Colorful {
|
|
||||||
closeColor, debugColor, infoColor, warnColor, errorColor = "", "", "", "", ""
|
|
||||||
}
|
|
||||||
debugStr := fmt.Sprintf(colorfulPrint, debugColor, "DEBUG", closeColor)
|
|
||||||
infoStr := fmt.Sprintf(colorfulPrint, infoColor, "INFO", closeColor)
|
|
||||||
warnStr := fmt.Sprintf(colorfulPrint, warnColor, "WARN", closeColor)
|
|
||||||
errStr := fmt.Sprintf(colorfulPrint, errorColor, "ERROR", closeColor)
|
|
||||||
|
|
||||||
return &logging{
|
|
||||||
Writer: writer,
|
|
||||||
Config: config,
|
|
||||||
infoStr: infoStr,
|
|
||||||
warnStr: warnStr,
|
|
||||||
errStr: errStr,
|
|
||||||
debugStr: debugStr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setDate() string {
|
|
||||||
tx := time.Now()
|
|
||||||
return timex.DateFormat(tx, "Y-m-d H:I:S")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l logging) Debug(client *BaseClient, msg string, data ...any) {
|
|
||||||
if l.LogLevel >= logger.Debug {
|
|
||||||
t := setDate()
|
|
||||||
msg, data = censorSensitiveInfo(client, msg, data...)
|
|
||||||
if client != nil {
|
|
||||||
l.Printf(l.debugStr, fmt.Sprintf("[%s] %s", t, msg), sprint(data...))
|
|
||||||
} else {
|
|
||||||
l.Printf(l.debugStr, msg, sprint(data...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l logging) Info(client *BaseClient, msg string, data ...any) {
|
|
||||||
if l.LogLevel >= logger.Info {
|
|
||||||
t := setDate()
|
|
||||||
msg, data = censorSensitiveInfo(client, msg, data...)
|
|
||||||
if client != nil {
|
|
||||||
l.Printf(l.infoStr, fmt.Sprintf("[%s] %s", t, msg), sprint(data...))
|
|
||||||
} else {
|
|
||||||
l.Printf(l.infoStr, msg, sprint(data...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l logging) Warn(client *BaseClient, msg string, data ...any) {
|
|
||||||
if l.LogLevel >= logger.Warn {
|
|
||||||
t := setDate()
|
|
||||||
msg, data = censorSensitiveInfo(client, msg, data...)
|
|
||||||
if client != nil {
|
|
||||||
l.Printf(l.warnStr, fmt.Sprintf("[%s] %s", t, msg), sprint(data...))
|
|
||||||
} else {
|
|
||||||
l.Printf(l.warnStr, msg, sprint(data...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l logging) Error(client *BaseClient, msg string, data ...any) {
|
|
||||||
if l.LogLevel >= logger.Error {
|
|
||||||
t := setDate()
|
|
||||||
msg, data = censorSensitiveInfo(client, msg, data...)
|
|
||||||
if client != nil {
|
|
||||||
l.Printf(l.errStr, fmt.Sprintf("[%s] %s", t, msg), sprint(data...))
|
|
||||||
} else {
|
|
||||||
l.Printf(l.errStr, msg, sprint(data...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Logger interface {
|
|
||||||
Debug(*BaseClient, string, ...any)
|
|
||||||
Info(*BaseClient, string, ...any)
|
|
||||||
Warn(*BaseClient, string, ...any)
|
|
||||||
Error(*BaseClient, string, ...any)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Writer interface {
|
|
||||||
Printf(string, ...any)
|
|
||||||
}
|
|
||||||
|
|
||||||
type logging struct {
|
|
||||||
Writer
|
|
||||||
debugStr string
|
|
||||||
infoStr string
|
|
||||||
warnStr string
|
|
||||||
errStr string
|
|
||||||
logger.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func censorSensitiveInfo(client *BaseClient, a string, b ...any) (string, []any) {
|
|
||||||
// if client == nil || client.Token == "" {
|
|
||||||
// return a, b
|
|
||||||
// }
|
|
||||||
// tokenFixed := strings.Split(client.Token, ":")[1]
|
|
||||||
// for t := range b {
|
|
||||||
// if b[t] != nil {
|
|
||||||
// rV := reflect.TypeOf(b[t]).Kind()
|
|
||||||
// if rV == reflect.String {
|
|
||||||
// b[t] = strings.ReplaceAll(b[t].(string), tokenFixed, strings.Repeat("X", len(tokenFixed)))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// a = strings.ReplaceAll(a, tokenFixed, strings.Repeat("X", len(tokenFixed)))
|
|
||||||
// return a, b
|
|
||||||
return a, b
|
|
||||||
}
|
|
||||||
|
|
||||||
func sprint(a ...any) string {
|
|
||||||
for t := range a {
|
|
||||||
if a[t] != nil {
|
|
||||||
rV := reflect.TypeOf(a[t]).Kind()
|
|
||||||
if rV == reflect.Ptr {
|
|
||||||
a[t] = reflect.ValueOf(a[t]).Elem().Interface()
|
|
||||||
}
|
|
||||||
rV = reflect.TypeOf(a[t]).Kind()
|
|
||||||
if rV == reflect.Struct || rV == reflect.Map || rV == reflect.Slice {
|
|
||||||
a[t], _ = logger.Serialize(a[t])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r := fmt.Sprintln(a...)
|
|
||||||
r = r[:len(r)-1]
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package libpack_logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gookit/goutil/envutil"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogConfig struct {
|
||||||
|
baseLogger zerolog.Logger
|
||||||
|
errorLogger zerolog.Logger
|
||||||
|
nopLogger zerolog.Logger
|
||||||
|
mu sync.Mutex
|
||||||
|
minLevel zerolog.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
zerolog.TimeFieldFormat = time.RFC3339
|
||||||
|
zerolog.MessageFieldName = "msg"
|
||||||
|
zerolog.TimestampFieldName = "ts"
|
||||||
|
zerolog.LevelFieldName = "level"
|
||||||
|
zerolog.LevelFatalValue = "critical"
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMinLogLevel() zerolog.Level {
|
||||||
|
levelStr := strings.ToLower(envutil.Getenv("LOG_LEVEL", "info"))
|
||||||
|
return matchLogLevel(levelStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add function set the minimum log level instead of using environment variable
|
||||||
|
func (lc *LogConfig) SetLogLevel(level string) {
|
||||||
|
lc.mu.Lock()
|
||||||
|
defer lc.mu.Unlock()
|
||||||
|
lc.minLevel = matchLogLevel(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match the log level provided as string with the log level provided as zerolog.Level
|
||||||
|
func matchLogLevel(level string) zerolog.Level {
|
||||||
|
switch level {
|
||||||
|
case "debug":
|
||||||
|
return zerolog.DebugLevel
|
||||||
|
case "warn":
|
||||||
|
return zerolog.WarnLevel
|
||||||
|
case "error":
|
||||||
|
return zerolog.ErrorLevel
|
||||||
|
case "critical":
|
||||||
|
return zerolog.FatalLevel
|
||||||
|
default:
|
||||||
|
return zerolog.InfoLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogger() *LogConfig {
|
||||||
|
zl := zerolog.New(os.Stdout).With().Timestamp().Logger()
|
||||||
|
// Determine the minimum log level from environment variable
|
||||||
|
return &LogConfig{
|
||||||
|
baseLogger: zl,
|
||||||
|
errorLogger: zl.Output(os.Stderr),
|
||||||
|
nopLogger: zerolog.Nop(),
|
||||||
|
minLevel: getMinLogLevel(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lc *LogConfig) getLogger(level zerolog.Level) zerolog.Logger {
|
||||||
|
if level >= zerolog.ErrorLevel {
|
||||||
|
return lc.errorLogger
|
||||||
|
}
|
||||||
|
if level < lc.minLevel {
|
||||||
|
return lc.nopLogger
|
||||||
|
}
|
||||||
|
return lc.baseLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lc *LogConfig) Log(level zerolog.Level, message string, fields []map[string]interface{}) {
|
||||||
|
if level < lc.minLevel {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger := lc.getLogger(level)
|
||||||
|
event := logger.WithLevel(level).CallerSkipFrame(3)
|
||||||
|
if len(fields) == 0 {
|
||||||
|
event.Msg(message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
field := fields[0]
|
||||||
|
for k, val := range field {
|
||||||
|
switch v := val.(type) {
|
||||||
|
case string:
|
||||||
|
event.Str(k, v)
|
||||||
|
case int:
|
||||||
|
event.Int(k, v)
|
||||||
|
case float64:
|
||||||
|
event.Float64(k, v)
|
||||||
|
default:
|
||||||
|
event.Interface(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event.Msg(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lc *LogConfig) Info(message string, fields ...map[string]interface{}) {
|
||||||
|
lc.Log(zerolog.InfoLevel, message, fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lc *LogConfig) Debug(message string, fields ...map[string]interface{}) {
|
||||||
|
lc.Log(zerolog.DebugLevel, message, fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lc *LogConfig) Warn(message string, fields ...map[string]interface{}) {
|
||||||
|
lc.Log(zerolog.WarnLevel, message, fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// alias Warning to Warn
|
||||||
|
func (lc *LogConfig) Warning(message string, fields ...map[string]interface{}) {
|
||||||
|
lc.Warn(message, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lc *LogConfig) Error(message string, fields ...map[string]interface{}) {
|
||||||
|
lc.Log(zerolog.ErrorLevel, message, fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lc *LogConfig) Critical(message string, fields ...map[string]interface{}) {
|
||||||
|
lc.Log(zerolog.FatalLevel, message, fields)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// alias Fatal to Critical
|
||||||
|
func (lc *LogConfig) Fatal(message string, fields ...map[string]interface{}) {
|
||||||
|
lc.Critical(message, fields...)
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package libpack_logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkNewLogger(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
NewLogger()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkInfoLog(b *testing.B) {
|
||||||
|
oldEnv := os.Getenv("LOG_LEVEL")
|
||||||
|
os.Setenv("LOG_LEVEL", "info")
|
||||||
|
oldStdout := os.Stdout
|
||||||
|
oldStderr := os.Stderr
|
||||||
|
os.Stdout, _ = os.Open(os.DevNull)
|
||||||
|
os.Stderr, _ = os.Open(os.DevNull)
|
||||||
|
defer func() {
|
||||||
|
os.Stdout = oldStdout
|
||||||
|
os.Stderr = oldStderr
|
||||||
|
os.Setenv("LOG_LEVEL", oldEnv)
|
||||||
|
}()
|
||||||
|
|
||||||
|
testsLogger := NewLogger()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
testsLogger.Info("test", map[string]interface{}{"test": "test"})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package gql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gookit/goutil/envutil"
|
||||||
|
cache "github.com/lukaszraczylo/go-simple-graphql/cache"
|
||||||
|
logging "github.com/lukaszraczylo/go-simple-graphql/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewConnection() (b *BaseClient) {
|
||||||
|
b = &BaseClient{
|
||||||
|
endpoint: envutil.Getenv("GRAPHQL_ENDPOINT", "https://api.github.com/graphql"),
|
||||||
|
responseType: envutil.Getenv("GRAPHQL_OUTPUT", "string"),
|
||||||
|
Logger: logging.NewLogger(),
|
||||||
|
cache: cache.New(time.Duration(envutil.GetInt("GRAPHQL_CACHE_TTL", 5)) * time.Second),
|
||||||
|
cache_global: envutil.GetBool("GRAPHQL_CACHE_ENABLED", false),
|
||||||
|
retries_enable: envutil.GetBool("GRAPHQL_RETRIES_ENABLE", false),
|
||||||
|
retries_delay: time.Duration(envutil.GetInt("GRAPHQL_RETRIES_DELAY", 250) * int(time.Millisecond)),
|
||||||
|
retries_number: envutil.GetInt("GRAPHQL_RETRIES_NUMBER", 3),
|
||||||
|
}
|
||||||
|
b.client = b.createHttpClient()
|
||||||
|
b.Logger.Debug("Created new GraphQL client connection", map[string]interface{}{"values": b})
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseClient) SetEndpoint(endpoint string) {
|
||||||
|
b.endpoint = endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseClient) SetOutput(responseType string) {
|
||||||
|
// allowed are byte, string, mapstring
|
||||||
|
// check if responseType is allowed
|
||||||
|
// TODO: implement
|
||||||
|
b.responseType = responseType
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package gql
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func Benchmark_newConnection(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
NewConnection()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package gql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (suite *Tests) TestNewConnection() {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "TestNewConnection",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
suite.T().Run(tt.name, func(t *testing.T) {
|
||||||
|
got := NewConnection()
|
||||||
|
assert.NotNil(suite.T(), got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,155 +1,106 @@
|
|||||||
package gql
|
package gql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/lukaszraczylo/go-simple-graphql/utils/helpers"
|
|
||||||
|
|
||||||
|
"github.com/goccy/go-json"
|
||||||
|
"github.com/gookit/goutil"
|
||||||
"github.com/gookit/goutil/strutil"
|
"github.com/gookit/goutil/strutil"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
func (b *BaseClient) convertToJSON(v any) []byte {
|
||||||
|
jsonData, err := json.Marshal(v)
|
||||||
func (c *BaseClient) convertToJson(v any) []byte {
|
|
||||||
json, err := json.Marshal(v)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error(c, "Can't convert to json;", "error", err.Error())
|
b.Logger.Error("Can't convert to json;", map[string]interface{}{"error": err.Error()})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return json
|
return jsonData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BaseClient) NewQuery(q ...any) *Query {
|
func (b *BaseClient) compileQuery(query_partials ...any) *Query {
|
||||||
query := &Query{}
|
q := new(Query)
|
||||||
|
for _, partial := range query_partials {
|
||||||
if len(q) > 0 {
|
switch val := partial.(type) {
|
||||||
for _, v := range q {
|
case string:
|
||||||
switch val := v.(type) {
|
q.Query = val
|
||||||
case string:
|
case map[string]interface{}:
|
||||||
query.query = val
|
q.Variables = val
|
||||||
case map[string]interface{}:
|
|
||||||
query.variables = val
|
|
||||||
case context.Context:
|
|
||||||
query.context = val
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if q.Query == "" {
|
||||||
query.compiledQuery = c.convertToJson(request{
|
b.Logger.Error("Can't compile query;", map[string]interface{}{"error": "query is empty"})
|
||||||
Query: query.query,
|
return nil
|
||||||
Variables: query.variables,
|
|
||||||
})
|
|
||||||
|
|
||||||
c.Logger.Debug(c, "Clearing previously prepared variables and query")
|
|
||||||
query.variables = nil
|
|
||||||
|
|
||||||
if query.context == nil {
|
|
||||||
query.context = context.Background()
|
|
||||||
}
|
}
|
||||||
|
q.JsonQuery = b.convertToJSON(q)
|
||||||
if c.validate {
|
return q
|
||||||
c.Logger.Warn(c, "Validating query is not active")
|
|
||||||
}
|
|
||||||
|
|
||||||
query.query = ""
|
|
||||||
c.Logger.Debug(c, "Query prepared;", "query", helpers.BytesToString(query.compiledQuery))
|
|
||||||
|
|
||||||
return query
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query is a function that sends a query to the server
|
func (b *BaseClient) Query(query string, variables map[string]interface{}, headers map[string]interface{}) (returned_value any, err error) {
|
||||||
// It takes 3 arguments:
|
compiledQuery := b.compileQuery(query, variables)
|
||||||
// 1. queryContent: the query string
|
if compiledQuery.JsonQuery == nil {
|
||||||
// 2. queryVariables: the variables for the query
|
b.Logger.Error("Can't compile query;", map[string]interface{}{"error": "query is empty"})
|
||||||
// 3. queryHeaders: the headers for the query
|
return nil, fmt.Errorf("Can't compile query")
|
||||||
// It looks a bit weird because of the backward compatibility
|
}
|
||||||
|
|
||||||
func (c *BaseClient) Query(queryContent string, queryVariables interface{}, queryHeaders map[string]interface{}) (any, error) {
|
b.Logger.Debug("Compiled query;", map[string]interface{}{"query": compiledQuery})
|
||||||
query := c.NewQuery(queryContent, queryVariables)
|
enable_cache, enable_retries, recompile_required := compiledQuery.parseHeadersAndVariables(headers)
|
||||||
|
|
||||||
|
if recompile_required {
|
||||||
|
compiledQuery = b.compileQuery(query, variables)
|
||||||
|
}
|
||||||
|
|
||||||
var queryHash string
|
var queryHash string
|
||||||
var cachedResponse []byte
|
|
||||||
var cacheBaseClient BaseClient
|
|
||||||
|
|
||||||
// Check for library specific headers
|
if (enable_cache || b.cache_global) && strutil.HasPrefix(compiledQuery.Query, "query") {
|
||||||
if len(queryHeaders) > 0 {
|
b.Logger.Debug("Cache enabled")
|
||||||
cacheBaseClient = reflect.ValueOf(*c).Interface().(BaseClient)
|
queryHash = calculateHash(compiledQuery)
|
||||||
queryHeaders = c.parseQueryHeaders(queryHeaders)
|
cached_value := b.cacheLookup(queryHash)
|
||||||
}
|
if cached_value != nil {
|
||||||
|
b.Logger.Debug("Cache hit", map[string]interface{}{"query": compiledQuery})
|
||||||
if c.cache.enabled {
|
return cached_value, nil
|
||||||
queryHash = strutil.Md5(query.compiledQuery)
|
|
||||||
c.Logger.Debug(c, "Hash calculated;", "hash:", queryHash)
|
|
||||||
|
|
||||||
cachedResponse = c.cacheLookup(queryHash)
|
|
||||||
if cachedResponse != nil {
|
|
||||||
c.Logger.Debug(c, "Found cached response")
|
|
||||||
return c.decodeResponse(cachedResponse), nil
|
|
||||||
} else {
|
} else {
|
||||||
c.Logger.Debug(c, "No cached response found")
|
b.Logger.Debug("Cache miss", map[string]interface{}{"query": compiledQuery})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := c.executeQuery(query.compiledQuery, queryHeaders)
|
if enable_retries || b.retries_enable {
|
||||||
|
b.Logger.Debug("Retries enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
q := &QueryExecutor{
|
||||||
|
BaseClient: b,
|
||||||
|
Query: compiledQuery.JsonQuery,
|
||||||
|
Headers: headers,
|
||||||
|
CacheKey: func() string {
|
||||||
|
if queryHash != "" {
|
||||||
|
return queryHash
|
||||||
|
} else {
|
||||||
|
return "no-cache"
|
||||||
|
}
|
||||||
|
}(),
|
||||||
|
Retries: enable_retries || b.retries_enable,
|
||||||
|
}
|
||||||
|
defer func() { q = nil }()
|
||||||
|
rv, err := q.executeQuery()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Error(c, "Error while executing query;", "error", err.Error())
|
b.Logger.Error("Error while executing query;", map[string]interface{}{"error": err.Error()})
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
returned_value, err = q.decodeResponse(rv)
|
||||||
jsonData, err := json.Marshal(response)
|
return returned_value, err
|
||||||
if err != nil {
|
|
||||||
c.Logger.Error(c, "Error while converting to json;", "error", err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.cache.enabled {
|
|
||||||
c.cache.client.Set(queryHash, jsonData)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(queryHeaders) > 0 {
|
|
||||||
*c = cacheBaseClient
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.decodeResponse(jsonData), err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BaseClient) decodeResponse(jsonData []byte) any {
|
func (q *Query) parseHeadersAndVariables(headers map[string]interface{}) (enable_cache bool, enable_retries bool, recompile_required bool) {
|
||||||
switch c.responseType {
|
if headers != nil {
|
||||||
case "mapstring":
|
enable_cache, _ = goutil.ToBool(searchForKeysInMapStringInterface(headers, "gqlcache"))
|
||||||
var response map[string]interface{}
|
enable_retries, _ = goutil.ToBool(searchForKeysInMapStringInterface(headers, "gqlretries"))
|
||||||
err := json.Unmarshal(jsonData, &response)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger.Error(c, "Error while converting to map[string]interface{};", "error", err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return response
|
|
||||||
case "string":
|
|
||||||
return helpers.BytesToString(jsonData)
|
|
||||||
case "byte":
|
|
||||||
return jsonData
|
|
||||||
default:
|
|
||||||
c.Logger.Error(c, "Unknown response type", "response", c.responseType)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
if q.Variables != nil {
|
||||||
|
enable_cache, _ = goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlcache"))
|
||||||
func (c *BaseClient) parseQueryHeaders(queryHeaders map[string]interface{}) (returnHeaders map[string]interface{}) {
|
enable_retries, _ = goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlretries"))
|
||||||
returnHeaders = make(map[string]interface{})
|
delete(q.Variables, "gqlcache")
|
||||||
for k, v := range queryHeaders {
|
delete(q.Variables, "gqlretries")
|
||||||
if k == "gqlcache" {
|
recompile_required = true
|
||||||
c.cache.enabled, _ = strconv.ParseBool(fmt.Sprintf("%v", v))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if k == "gqlretries" {
|
|
||||||
c.retries.enabled, _ = strconv.ParseBool(fmt.Sprintf("%v", v))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
returnHeaders[k] = v
|
|
||||||
}
|
}
|
||||||
return returnHeaders
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package gql
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func Benchmark_compileQuery(bn *testing.B) {
|
||||||
|
b := NewConnection()
|
||||||
|
bn.ResetTimer()
|
||||||
|
for i := 0; i < bn.N; i++ {
|
||||||
|
b.compileQuery(
|
||||||
|
`query { viewer { login } }`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_convertToJson(bn *testing.B) {
|
||||||
|
b := NewConnection()
|
||||||
|
bn.ResetTimer()
|
||||||
|
for i := 0; i < bn.N; i++ {
|
||||||
|
b.convertToJSON(
|
||||||
|
`query { viewer { login } }`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+236
-259
@@ -3,339 +3,316 @@ package gql
|
|||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBaseClient_convertToJson(t *testing.T) {
|
func (suite *Tests) TestBaseClient_convertToJSON() {
|
||||||
type fields struct {
|
|
||||||
}
|
|
||||||
type args struct {
|
type args struct {
|
||||||
v any
|
v any
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fields fields
|
args args
|
||||||
args args
|
want []byte
|
||||||
want []byte
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Test convertToJson",
|
name: "TestBaseClient_convertToJSON_query",
|
||||||
fields: fields{},
|
|
||||||
args: args{
|
args: args{
|
||||||
v: map[string]interface{}{
|
v: `query { viewer { login } }`,
|
||||||
"query": "query { hello }",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
want: []byte(`{"query":"query { hello }"}`),
|
want: []byte(`"query { viewer { login } }"`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Test convertToJson with variables",
|
name: "TestBaseClient_convertToJSON_variables",
|
||||||
fields: fields{},
|
|
||||||
args: args{
|
args: args{
|
||||||
v: map[string]interface{}{
|
v: map[string]interface{}{
|
||||||
"query": "query { hello }",
|
"login": "lukaszraczylo",
|
||||||
"variables": map[string]interface{}{"name": "John"},
|
"first": 10,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: []byte(`{"query":"query { hello }","variables":{"name":"John"}}`),
|
want: []byte(`{"first":10,"login":"lukaszraczylo"}`),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
suite.T().Run(tt.name, func(t *testing.T) {
|
||||||
c := &BaseClient{}
|
b := NewConnection()
|
||||||
if got := c.convertToJson(tt.args.v); !reflect.DeepEqual(got, tt.want) {
|
if got := b.convertToJSON(tt.args.v); !reflect.DeepEqual(got, tt.want) {
|
||||||
t.Errorf("BaseClient.convertToJson() = %v, want %v", got, tt.want)
|
assert.Equal(tt.want, got)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBaseClient_NewQuery(t *testing.T) {
|
func (suite *Tests) TestBaseClient_compileQuery() {
|
||||||
type fields struct {
|
|
||||||
}
|
|
||||||
type args struct {
|
type args struct {
|
||||||
q []any
|
query_partials []any
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
fields fields
|
wantQ *Query
|
||||||
want *Query
|
name string
|
||||||
name string
|
args args
|
||||||
args args
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Test NewQuery",
|
name: "TestBaseClient_compileQuery_query",
|
||||||
fields: fields{},
|
|
||||||
args: args{
|
args: args{
|
||||||
q: []any{
|
query_partials: []any{
|
||||||
"query { hello }",
|
`query { viewer { login } }`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &Query{
|
wantQ: &Query{
|
||||||
compiledQuery: []byte(`{"variables":null,"query":"query { hello }"}`),
|
Query: `query { viewer { login } }`,
|
||||||
|
JsonQuery: []byte(`{"query":"query { viewer { login } }"}`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Test NewQuery with variables",
|
name: "TestBaseClient_compileQuery_with_variables",
|
||||||
fields: fields{},
|
|
||||||
args: args{
|
args: args{
|
||||||
q: []any{
|
query_partials: []any{
|
||||||
"query { hello }",
|
`query { viewer { login } }`,
|
||||||
map[string]interface{}{"name": "John"},
|
map[string]interface{}{
|
||||||
|
"login": "lukaszraczylo",
|
||||||
|
"first": 10,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &Query{
|
wantQ: &Query{
|
||||||
compiledQuery: []byte(`{"variables":{"name":"John"},"query":"query { hello }"}`),
|
Query: `query { viewer { login } }`,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"login": "lukaszraczylo",
|
||||||
|
"first": 10,
|
||||||
|
},
|
||||||
|
JsonQuery: []byte(`{"variables":{"first":10,"login":"lukaszraczylo"},"query":"query { viewer { login } }"}`),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_compileQuery_variables_without_query",
|
||||||
|
args: args{
|
||||||
|
query_partials: []any{
|
||||||
|
map[string]interface{}{
|
||||||
|
"login": "lukaszraczylo",
|
||||||
|
"first": 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantQ: nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
suite.T().Run(tt.name, func(t *testing.T) {
|
||||||
c := NewConnection()
|
b := NewConnection()
|
||||||
gotQuery := c.NewQuery(tt.args.q...)
|
got := b.compileQuery(tt.args.query_partials...)
|
||||||
assert.Equal(t, tt.want.query, gotQuery.query)
|
assert.Equal(tt.wantQ, got)
|
||||||
assert.Equal(t, tt.want.variables, gotQuery.variables)
|
|
||||||
assert.Equal(t, tt.want.compiledQuery, gotQuery.compiledQuery)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBaseClient_decodeResponse(t *testing.T) {
|
func (suite *Tests) TestBaseClient_Query() {
|
||||||
type fields struct {
|
|
||||||
responseType string
|
|
||||||
}
|
|
||||||
type args struct {
|
type args struct {
|
||||||
jsonData []byte
|
variables map[string]interface{}
|
||||||
|
headers map[string]interface{}
|
||||||
|
query string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
fields fields
|
args args
|
||||||
want any
|
wantReturned_value any
|
||||||
name string
|
name string
|
||||||
args args
|
graphQLServer string
|
||||||
|
wantType string
|
||||||
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Test decodeResponse - string",
|
name: "TestBaseClient_Query_spacex_working",
|
||||||
fields: fields{
|
graphQLServer: "https://spacex-production.up.railway.app/",
|
||||||
responseType: "string",
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
jsonData: []byte(`{"data":{"hello":"world"}}`),
|
query: `query Dragons {
|
||||||
},
|
|
||||||
want: `{"data":{"hello":"world"}}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test decodeResponse - byte",
|
|
||||||
fields: fields{
|
|
||||||
responseType: "byte",
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
jsonData: []byte(`{"data":{"hello":"world"}}`),
|
|
||||||
},
|
|
||||||
want: []byte(`{"data":{"hello":"world"}}`),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
c := NewConnection()
|
|
||||||
c.responseType = tt.fields.responseType
|
|
||||||
if got := c.decodeResponse(tt.args.jsonData); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("BaseClient.decodeResponse() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBaseClient_Query(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
graphql_endpoint string
|
|
||||||
repeat int
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
queryVariables interface{}
|
|
||||||
queryHeaders map[string]interface{}
|
|
||||||
queryContent string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
want any
|
|
||||||
args args
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test Query - failing",
|
|
||||||
fields: fields{
|
|
||||||
graphql_endpoint: "https://spacex-production.up.railway.app/",
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
queryContent: "query { hello }",
|
|
||||||
},
|
|
||||||
want: ``,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test Query - success",
|
|
||||||
fields: fields{
|
|
||||||
graphql_endpoint: "https://spacex-production.up.railway.app/",
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
queryContent: `query Dragons {
|
|
||||||
dragons {
|
dragons {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
queryHeaders: map[string]interface{}{
|
variables: nil,
|
||||||
"x-apollo-operation-name": "Missions-Potato-Test-Golang",
|
headers: map[string]interface{}{
|
||||||
|
"x-apollo-operation-name": "Dragons/github.com/lukaszraczylo/go-simple-graphql",
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantReturned_value: string(`{"dragons":[{"name":"Dragon 1"},{"name":"Dragon 2"}]}`),
|
||||||
want: `{"dragons":[{"name":"Dragon 1"},{"name":"Dragon 2"}]}`,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Test Query - success",
|
name: "TestBaseClient_Query_spacex_invalid_query",
|
||||||
fields: fields{
|
graphQLServer: "https://spacex-production.up.railway.app/",
|
||||||
repeat: 2,
|
|
||||||
graphql_endpoint: "https://spacex-production.up.railway.app/",
|
|
||||||
},
|
|
||||||
args: args{
|
args: args{
|
||||||
queryContent: `query Dragons {
|
query: `query Dragons {
|
||||||
dragons {
|
dragons {
|
||||||
name
|
name
|
||||||
first_flight
|
|
||||||
}
|
}
|
||||||
}`,
|
`,
|
||||||
queryHeaders: map[string]interface{}{
|
variables: nil,
|
||||||
"x-apollo-operation-name": "Missions-Potato-Test-Golang-Cached",
|
headers: map[string]interface{}{
|
||||||
|
"x-apollo-operation-name": "Dragons/github.com/lukaszraczylo/go-simple-graphql",
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantReturned_value: nil,
|
||||||
want: `{"dragons":[{"first_flight":"2010-12-08","name":"Dragon 1"},{"first_flight":"2019-03-02","name":"Dragon 2"}]}`,
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_Query_tgbotapp_working",
|
||||||
|
graphQLServer: "https://telegram-bot.app/v1/graphql",
|
||||||
|
args: args{
|
||||||
|
query: `query {
|
||||||
|
__type(name: "Query") {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
wantReturned_value: string(`{"__type":null}`),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_Query_tgbotapp_invalid_query",
|
||||||
|
graphQLServer: "https://telegram-bot.app/v1/graphql",
|
||||||
|
args: args{
|
||||||
|
query: `query {
|
||||||
|
__type(name: "Query") {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
wantReturned_value: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_Query_tgbotapp_invalid_wrong_field",
|
||||||
|
graphQLServer: "https://telegram-bot.app/v1/graphql",
|
||||||
|
args: args{
|
||||||
|
query: `query {
|
||||||
|
__type(name: "Query") {
|
||||||
|
name2
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
wantReturned_value: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_Query_tgbotapp_wrong_url",
|
||||||
|
graphQLServer: "https://telegram-bot.app/v0/graphql",
|
||||||
|
args: args{
|
||||||
|
query: `query {
|
||||||
|
__type(name: "Query") {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
wantReturned_value: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_Query_tgbotapp_valid_query_mapstring",
|
||||||
|
graphQLServer: "https://telegram-bot.app/v1/graphql",
|
||||||
|
wantType: "mapstring",
|
||||||
|
args: args{
|
||||||
|
query: `query {
|
||||||
|
__type(name: "Query") {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
wantReturned_value: map[string]interface{}{"__type": nil},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_Query_tgbotapp_valid_query_byte",
|
||||||
|
graphQLServer: "https://telegram-bot.app/v1/graphql",
|
||||||
|
wantType: "byte",
|
||||||
|
args: args{
|
||||||
|
query: `query {
|
||||||
|
__type(name: "Query") {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
wantReturned_value: []byte(`{"__type":null}`),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_Query_tgbotapp_valid_query_string",
|
||||||
|
graphQLServer: "https://telegram-bot.app/v1/graphql",
|
||||||
|
wantType: "string",
|
||||||
|
args: args{
|
||||||
|
query: `query {
|
||||||
|
__type(name: "Query") {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
wantReturned_value: string(`{"__type":null}`),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_Query_tgbotapp_valid_query_invalid_type",
|
||||||
|
graphQLServer: "https://telegram-bot.app/v1/graphql",
|
||||||
|
wantType: "potato",
|
||||||
|
args: args{
|
||||||
|
query: `query {
|
||||||
|
__type(name: "Query") {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
wantReturned_value: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_Query_tgbotapp_valid_query_cache",
|
||||||
|
graphQLServer: "https://telegram-bot.app/v1/graphql",
|
||||||
|
args: args{
|
||||||
|
query: `query {
|
||||||
|
__type(name: "Query") {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
variables: map[string]interface{}{
|
||||||
|
"gqlcache": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantReturned_value: string(`{"__type":null}`),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TestBaseClient_Query_tgbotapp_valid_query_with_retry",
|
||||||
|
graphQLServer: "https://telegram-bot.app/v0/graphql",
|
||||||
|
wantType: "byte",
|
||||||
|
args: args{
|
||||||
|
query: `query {
|
||||||
|
__type(name: "Query") {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
variables: map[string]interface{}{
|
||||||
|
"gqlretries": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantReturned_value: nil,
|
||||||
|
wantErr: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
suite.T().Run(tt.name, func(t *testing.T) {
|
||||||
c := NewConnection()
|
b := NewConnection()
|
||||||
|
if tt.graphQLServer != "" {
|
||||||
if tt.fields.graphql_endpoint != "" {
|
b.SetEndpoint(tt.graphQLServer)
|
||||||
c.endpoint = tt.fields.graphql_endpoint
|
|
||||||
}
|
}
|
||||||
|
if tt.wantType != "" {
|
||||||
repeat := 1
|
b.SetOutput(tt.wantType)
|
||||||
if tt.fields.repeat != 0 {
|
|
||||||
repeat = tt.fields.repeat
|
|
||||||
}
|
}
|
||||||
|
gotReturned_value, err := b.Query(tt.args.query, tt.args.variables, tt.args.headers)
|
||||||
for i := 0; i < repeat; i++ {
|
if tt.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
got, err := c.Query(tt.args.queryContent, tt.args.queryVariables, tt.args.queryHeaders)
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
if err != nil && tt.wantErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// benchmarks
|
|
||||||
|
|
||||||
func BenchmarkBaseClient_convertToJson(b *testing.B) {
|
|
||||||
c := NewConnection()
|
|
||||||
query := c.NewQuery("query { hello }")
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_ = c.convertToJson(query)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkNewQuery(b *testing.B) {
|
|
||||||
c := NewConnection()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_ = c.NewQuery("query { hello }")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDecodeResponse(b *testing.B) {
|
|
||||||
c := NewConnection()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_ = c.decodeResponse([]byte(`{"data":{"hello":"world"}}`))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkQueryNoCache(b *testing.B) {
|
|
||||||
c := NewConnection()
|
|
||||||
c.cache.enabled = false
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, _ = c.Query(`query MyQuery {
|
|
||||||
bots {
|
|
||||||
bot_name
|
|
||||||
}
|
|
||||||
}`, nil, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkQueryWithCache(b *testing.B) {
|
|
||||||
c := NewConnection()
|
|
||||||
c.cache.enabled = true
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, _ = c.Query(`query MyQuery {
|
|
||||||
bots {
|
|
||||||
bot_name
|
|
||||||
}
|
|
||||||
}`, nil, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBaseClient_parseQueryHeaders(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
queryHeaders map[string]interface{}
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
args args
|
|
||||||
wantReturnHeaders map[string]interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test parseQueryHeaders - no change",
|
|
||||||
fields: fields{},
|
|
||||||
args: args{
|
|
||||||
queryHeaders: map[string]interface{}{
|
|
||||||
"x-test-header": "test",
|
|
||||||
"x-test-header-next": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantReturnHeaders: map[string]interface{}{
|
|
||||||
"x-test-header": "test",
|
|
||||||
"x-test-header-next": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test parseQueryHeaders - lib-headers removed",
|
|
||||||
fields: fields{},
|
|
||||||
args: args{
|
|
||||||
queryHeaders: map[string]interface{}{
|
|
||||||
"x-test-header": "test",
|
|
||||||
"gqlcache": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantReturnHeaders: map[string]interface{}{
|
|
||||||
"x-test-header": "test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
c := NewConnection()
|
|
||||||
if gotReturnHeaders := c.parseQueryHeaders(tt.args.queryHeaders); !reflect.DeepEqual(gotReturnHeaders, tt.wantReturnHeaders) {
|
|
||||||
t.Errorf("BaseClient.parseQueryHeaders() = %v, want %v", gotReturnHeaders, tt.wantReturnHeaders)
|
|
||||||
}
|
}
|
||||||
|
assert.Equal(tt.wantReturned_value, gotReturned_value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-59
@@ -1,59 +0,0 @@
|
|||||||
package gql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/avast/retry-go/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *BaseClient) executeQuery(query []byte, headers any) (result any, err error) {
|
|
||||||
var queryResult queryResults
|
|
||||||
httpRequest, err := http.NewRequest("POST", c.endpoint, bytes.NewBuffer(query))
|
|
||||||
if err != nil {
|
|
||||||
c.Logger.Error(c, "Error while creating http request;", "error", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value := range headers.(map[string]interface{}) {
|
|
||||||
httpRequest.Header.Add(key, fmt.Sprintf("%s", value))
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpResponse *http.Response
|
|
||||||
|
|
||||||
err = retry.Do(
|
|
||||||
func() error {
|
|
||||||
httpResponse, err = c.client.Do(httpRequest)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger.Error(c, "Error while executing http request;", "error", err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer httpResponse.Body.Close()
|
|
||||||
|
|
||||||
if httpResponse.StatusCode <= 200 && httpResponse.StatusCode >= 204 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(httpResponse.Body)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger.Error(c, "Error while reading http response;", "error", err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(body, &queryResult)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger.Error(c, "Error while unmarshalling http response;", "error", err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(queryResult.Errors) > 0 {
|
|
||||||
return nil, fmt.Errorf("%v", queryResult.Errors)
|
|
||||||
}
|
|
||||||
return queryResult.Data, nil
|
|
||||||
}
|
|
||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
version: 1
|
version: 1
|
||||||
force:
|
force:
|
||||||
major: 1
|
major: 1
|
||||||
minor: 1
|
minor: 2
|
||||||
patch: 0
|
patch: 0
|
||||||
commit: 6a8f99f6ef8f65a303dad74c272ff12bf9fe526f
|
commit: 3217edf37fd1765c4f2594e3ba122a6a9c251296
|
||||||
wording:
|
wording:
|
||||||
minor:
|
minor:
|
||||||
- minor-release
|
- minor-release
|
||||||
|
|||||||
+25
-36
@@ -1,52 +1,41 @@
|
|||||||
package gql
|
package gql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/lukaszraczylo/go-simple-graphql/utils/concurrency"
|
cache "github.com/lukaszraczylo/go-simple-graphql/cache"
|
||||||
"github.com/lukaszraczylo/go-simple-graphql/utils/logger"
|
logging "github.com/lukaszraczylo/go-simple-graphql/logging"
|
||||||
|
|
||||||
"github.com/allegro/bigcache"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type cacheStore struct {
|
|
||||||
client *bigcache.BigCache
|
|
||||||
ttl int
|
|
||||||
enabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type retriesConfig struct {
|
|
||||||
enabled bool
|
|
||||||
max int
|
|
||||||
delay int
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseClient struct {
|
type BaseClient struct {
|
||||||
Logger Logger
|
cache *cache.Cache
|
||||||
LoggerWriter Writer
|
Logger *logging.LogConfig
|
||||||
client *http.Client
|
client *http.Client
|
||||||
concurrencyManager *concurrency.Pool
|
endpoint string
|
||||||
endpoint string
|
responseType string
|
||||||
responseType string
|
retries_delay time.Duration
|
||||||
cache cacheStore
|
retries_number int
|
||||||
MaxGoRoutines int
|
MaxGoRoutines int
|
||||||
LoggingLevel logger.LogLevel
|
cache_global bool
|
||||||
LoggerColorful bool
|
retries_enable bool
|
||||||
validate bool
|
|
||||||
retries retriesConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
context context.Context
|
Variables map[string]interface{} `json:"variables,omitempty"`
|
||||||
variables map[string]any `json:"variables"`
|
Query string `json:"query,omitempty"`
|
||||||
query string `json:"query"`
|
JsonQuery []byte `json:"jsonQuery,omitempty"`
|
||||||
compiledQuery []byte `json:"compiledQuery"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type request struct {
|
type QueryExecutor struct {
|
||||||
Variables any `json:"variables"`
|
Result any
|
||||||
Query string `json:"query"`
|
Error error
|
||||||
|
*BaseClient
|
||||||
|
Headers map[string]interface{}
|
||||||
|
CacheKey string
|
||||||
|
Query []byte
|
||||||
|
CacheTTL time.Duration
|
||||||
|
Retries bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type queryResults struct {
|
type queryResults struct {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package gql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
assertions "github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tests struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
assert *assertions.Assertions
|
||||||
|
)
|
||||||
|
|
||||||
|
func (suite *Tests) SetupTest() {
|
||||||
|
assert = assertions.New(suite.T())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *Tests) BeforeTest(suiteName, testName string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(Tests))
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package concurrency
|
|
||||||
|
|
||||||
type Pool struct {
|
|
||||||
queue chan struct{}
|
|
||||||
jobs int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPool(size int) *Pool {
|
|
||||||
if size < 0 {
|
|
||||||
size = 0
|
|
||||||
}
|
|
||||||
return &Pool{
|
|
||||||
jobs: size,
|
|
||||||
queue: make(chan struct{}, size),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool) Enqueue(job func(params ...any), params ...any) {
|
|
||||||
if p.jobs == 0 {
|
|
||||||
go job(params...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.queue <- struct{}{}
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
<-p.queue
|
|
||||||
}()
|
|
||||||
job(params...)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package concurrency
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewPool(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
want *Pool
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "TestNewPool",
|
|
||||||
args: args{
|
|
||||||
size: 0,
|
|
||||||
},
|
|
||||||
want: &Pool{
|
|
||||||
jobs: 0,
|
|
||||||
queue: make(chan struct{}, 0),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got := NewPool(tt.args.size)
|
|
||||||
assert.Equal(t, got.jobs, tt.want.jobs)
|
|
||||||
assert.Equal(t, cap(got.queue), cap(tt.want.queue))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPool_Enqueue(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
queue chan struct{}
|
|
||||||
jobs int
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
job func(params ...any)
|
|
||||||
params []any
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
args args
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "TestPool_Enqueue",
|
|
||||||
fields: fields{
|
|
||||||
queue: make(chan struct{}, 0),
|
|
||||||
jobs: 0,
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
job: func(params ...any) {
|
|
||||||
return
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
p := &Pool{
|
|
||||||
queue: tt.fields.queue,
|
|
||||||
jobs: tt.fields.jobs,
|
|
||||||
}
|
|
||||||
p.Enqueue(tt.args.job, tt.args.params...)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package helpers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func StringToBytes(s string) []byte {
|
|
||||||
return unsafe.Slice(unsafe.StringData(s), len(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func BytesToString(b []byte) string {
|
|
||||||
return unsafe.String(unsafe.SliceData(b), len(b))
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package helpers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStringToBytes(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
s string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want []byte
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "TestStringToBytes",
|
|
||||||
args: args{
|
|
||||||
s: "hello world",
|
|
||||||
},
|
|
||||||
want: []byte("hello world"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := StringToBytes(tt.args.s); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("StringToBytes() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBytesToString(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
b []byte
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
want string
|
|
||||||
args args
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "TestBytesToString",
|
|
||||||
args: args{
|
|
||||||
b: []byte("hello world"),
|
|
||||||
},
|
|
||||||
want: "hello world",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := BytesToString(tt.args.b); got != tt.want {
|
|
||||||
t.Errorf("BytesToString() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkStringToBytes(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
StringToBytes("hello world")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkBytesToString(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
BytesToString([]byte("hello world"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/lukaszraczylo/go-simple-graphql/utils/helpers"
|
|
||||||
"github.com/lukaszraczylo/go-simple-graphql/utils/ordered_map"
|
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
)
|
|
||||||
|
|
||||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
||||||
|
|
||||||
const (
|
|
||||||
reset = "\033[0m"
|
|
||||||
redBold = "\033[1;31m"
|
|
||||||
greenBold = "\033[1;32m"
|
|
||||||
yellowBold = "\033[1;33m"
|
|
||||||
blueBold = "\033[1;34m"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LogLevel int
|
|
||||||
|
|
||||||
const (
|
|
||||||
Silent LogLevel = iota
|
|
||||||
Error
|
|
||||||
Warn
|
|
||||||
Info
|
|
||||||
Debug
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Colorful bool
|
|
||||||
LogLevel LogLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
func Serialize(v any) (string, error) {
|
|
||||||
marshal, err := json.Marshal(internalSerialize(v))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return helpers.BytesToString(marshal), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func internalSerialize(v any) any {
|
|
||||||
if v != nil {
|
|
||||||
switch reflect.TypeOf(v).Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
r := ordered_map.New()
|
|
||||||
name := reflect.TypeOf(v).Name()
|
|
||||||
if name != "" {
|
|
||||||
r.Set("_", reflect.TypeOf(v).String())
|
|
||||||
}
|
|
||||||
for i := 0; i < reflect.ValueOf(v).NumField(); i++ {
|
|
||||||
r.Set(reflect.TypeOf(v).Field(i).Name, internalSerialize(reflect.ValueOf(v).Field(i).Interface()))
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
case reflect.Ptr:
|
|
||||||
if !reflect.ValueOf(v).IsNil() {
|
|
||||||
return internalSerialize(reflect.ValueOf(v).Elem().Interface())
|
|
||||||
}
|
|
||||||
case reflect.Slice:
|
|
||||||
var r []any
|
|
||||||
tmpSlice := reflect.ValueOf(v)
|
|
||||||
for i := 0; i < tmpSlice.Len(); i++ {
|
|
||||||
r = append(r, internalSerialize(tmpSlice.Index(i).Interface()))
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
type Person struct {
|
|
||||||
Name string
|
|
||||||
Age int
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSerializeStruct(t *testing.T) {
|
|
||||||
person := Person{Name: "Alice", Age: 30}
|
|
||||||
expected := `{"_":"logger.Person","Name":"Alice","Age":30}`
|
|
||||||
actual, err := Serialize(person)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error: %v", err)
|
|
||||||
}
|
|
||||||
if actual != expected {
|
|
||||||
t.Errorf("Expected: %v\nActual: %v", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSerializeNilPointer(t *testing.T) {
|
|
||||||
var person *Person
|
|
||||||
expected := `null`
|
|
||||||
actual, err := Serialize(person)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error: %v", err)
|
|
||||||
}
|
|
||||||
if actual != expected {
|
|
||||||
t.Errorf("Expected: %v\nActual: %v", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSerializeNonNilPointer(t *testing.T) {
|
|
||||||
person := &Person{Name: "Alice", Age: 30}
|
|
||||||
expected := `{"_":"logger.Person","Name":"Alice","Age":30}`
|
|
||||||
actual, err := Serialize(person)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error: %v", err)
|
|
||||||
}
|
|
||||||
if actual != expected {
|
|
||||||
t.Errorf("Expected: %v\nActual: %v", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSerializeSlice(t *testing.T) {
|
|
||||||
people := []Person{
|
|
||||||
{Name: "Alice", Age: 30},
|
|
||||||
{Name: "Bob", Age: 40},
|
|
||||||
}
|
|
||||||
expected := `[{"_":"logger.Person","Name":"Alice","Age":30},{"_":"logger.Person","Name":"Bob","Age":40}]`
|
|
||||||
actual, err := Serialize(people)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error: %v", err)
|
|
||||||
}
|
|
||||||
if actual != expected {
|
|
||||||
t.Errorf("Expected: %v\nActual: %v", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSerializeSliceOfPointers(t *testing.T) {
|
|
||||||
people := []*Person{
|
|
||||||
{Name: "Alice", Age: 30},
|
|
||||||
{Name: "Bob", Age: 40},
|
|
||||||
}
|
|
||||||
expected := `[{"_":"logger.Person","Name":"Alice","Age":30},{"_":"logger.Person","Name":"Bob","Age":40}]`
|
|
||||||
|
|
||||||
actual, err := Serialize(people)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error: %v", err)
|
|
||||||
}
|
|
||||||
if actual != expected {
|
|
||||||
t.Errorf("Expected: %v\nActual: %v", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSerializeUnsupportedType(t *testing.T) {
|
|
||||||
type UnsupportedType chan int
|
|
||||||
unsupportedType := make(UnsupportedType)
|
|
||||||
_, err := Serialize(unsupportedType)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Expected an error, but got nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package ordered_map
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
)
|
|
||||||
|
|
||||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
||||||
|
|
||||||
type OrderedMap struct {
|
|
||||||
values map[string]interface{}
|
|
||||||
keys []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *OrderedMap {
|
|
||||||
o := OrderedMap{}
|
|
||||||
o.keys = []string{}
|
|
||||||
o.values = map[string]interface{}{}
|
|
||||||
return &o
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OrderedMap) Set(key string, value interface{}) {
|
|
||||||
_, exists := o.values[key]
|
|
||||||
if !exists {
|
|
||||||
o.keys = append(o.keys, key)
|
|
||||||
}
|
|
||||||
o.values[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o OrderedMap) MarshalJSON() ([]byte, error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
buf.WriteByte('{')
|
|
||||||
encoder := json.NewEncoder(&buf)
|
|
||||||
encoder.SetEscapeHTML(false)
|
|
||||||
encoder.SetIndent("", "")
|
|
||||||
for i, k := range o.keys {
|
|
||||||
if i > 0 {
|
|
||||||
buf.WriteByte(',')
|
|
||||||
}
|
|
||||||
buf.WriteByte('"')
|
|
||||||
buf.WriteString(k)
|
|
||||||
buf.WriteString(`":`)
|
|
||||||
customEncoder := &customJsonEncoder{buf: &buf}
|
|
||||||
if err := customEncoder.Encode(o.values[k]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.WriteByte('}')
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type customJsonEncoder struct {
|
|
||||||
buf *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *customJsonEncoder) Encode(v interface{}) error {
|
|
||||||
data, err := json.Marshal(v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e.buf.Write(data)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
package ordered_map
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
want *OrderedMap
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "TestNew",
|
|
||||||
want: &OrderedMap{
|
|
||||||
keys: []string{},
|
|
||||||
values: map[string]interface{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := New(); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("New() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOrderedMap_Set(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
values map[string]interface{}
|
|
||||||
keys []string
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
value interface{}
|
|
||||||
key string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
args args
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "TestOrderedMap_Set",
|
|
||||||
fields: fields{
|
|
||||||
values: map[string]interface{}{},
|
|
||||||
keys: []string{},
|
|
||||||
},
|
|
||||||
args: args{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "TestOrderedMap_Set2",
|
|
||||||
fields: fields{
|
|
||||||
values: map[string]interface{}{
|
|
||||||
"hello": "world",
|
|
||||||
},
|
|
||||||
keys: []string{"hello"},
|
|
||||||
},
|
|
||||||
args: args{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
o := &OrderedMap{
|
|
||||||
values: tt.fields.values,
|
|
||||||
keys: tt.fields.keys,
|
|
||||||
}
|
|
||||||
o.Set(tt.args.key, tt.args.value)
|
|
||||||
for _, key := range o.keys {
|
|
||||||
if _, ok := o.values[key]; !ok {
|
|
||||||
t.Errorf("OrderedMap.Set() key not found in values map")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOrderedMap_MarshalJSON(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
values map[string]interface{}
|
|
||||||
keys []string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want []byte
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "TestOrderedMap_MarshalJSON",
|
|
||||||
fields: fields{
|
|
||||||
values: map[string]interface{}{
|
|
||||||
"hello": "world",
|
|
||||||
},
|
|
||||||
keys: []string{"hello"},
|
|
||||||
},
|
|
||||||
want: []byte(`{"hello":"world"}`),
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "TestOrderedMap_MarshalJSON2",
|
|
||||||
fields: fields{
|
|
||||||
values: map[string]interface{}{
|
|
||||||
"hello": "world",
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
keys: []string{"hello", "foo"},
|
|
||||||
},
|
|
||||||
want: []byte(`{"hello":"world","foo":"bar"}`),
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "TestOrderedMap_MarshalJSON_with_error",
|
|
||||||
fields: fields{
|
|
||||||
values: map[string]interface{}{
|
|
||||||
"hello": "world",
|
|
||||||
"foo": make(chan int),
|
|
||||||
},
|
|
||||||
keys: []string{"hello", "foo"},
|
|
||||||
},
|
|
||||||
want: nil,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
o := OrderedMap{
|
|
||||||
values: tt.fields.values,
|
|
||||||
keys: tt.fields.keys,
|
|
||||||
}
|
|
||||||
got, err := o.MarshalJSON()
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("OrderedMap.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("OrderedMap.MarshalJSON() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkOrderedMapTest(b *testing.B) {
|
|
||||||
m := New()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
m.Set("hello", "world")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkOrderedMarshalJSON(b *testing.B) {
|
|
||||||
m := New()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
m.Set("hello", "world")
|
|
||||||
m.MarshalJSON()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkMapTest(b *testing.B) {
|
|
||||||
m := make(map[string]interface{})
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
m["hello"] = "world"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user