diff --git a/README.md b/README.md index 7f3bd96..1897f1d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ Therefore, I present you the simple client to which you can copy & paste your gr * `GRAPHQL_CACHE` - Should the query cache be enabled? Default: `false` * `GRAPHQL_CACHE_TTL` - Cache TTL in seconds for SELECT type of queries. Default: `5` * `LOG_LEVEL` - Logging level. Default: `info` +* `RETRIES_ENABLE` - Should retries be enabled? Default: `false` +* `RETRIES_NUMBER` - Number of retries: Default: `1` +* `RETRIES_DELAY` - Delay in retries in milliseconds. Default: `250` ### Example reader code diff --git a/go.mod b/go.mod index fca596b..03b9578 100644 --- a/go.mod +++ b/go.mod @@ -4,24 +4,28 @@ go 1.18 require ( github.com/allegro/bigcache/v3 v3.0.2 + github.com/avast/retry-go v3.0.0+incompatible github.com/buger/jsonparser v1.1.1 github.com/json-iterator/go v1.1.12 github.com/lukaszraczylo/pandati v0.0.15 - github.com/rs/zerolog v1.26.1 + github.com/rs/zerolog v1.27.0 github.com/stretchr/testify v1.7.1 - golang.org/x/net v0.0.0-20220526153639-5463443f8c37 + golang.org/x/net v0.0.0-20220607020251-c690dde0001d ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/imdario/mergo v0.3.13 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/tidwall/gjson v1.14.0 // indirect + github.com/tidwall/gjson v1.14.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/wI2L/jsondiff v0.2.0 // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/yaml.v3 v3.0.0 // indirect ) diff --git a/go.sum b/go.sum index efde151..76a5f0c 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,11 @@ github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI= github.com/allegro/bigcache/v3 v3.0.2/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= +github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= +github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -14,8 +17,14 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/lukaszraczylo/pandati v0.0.15 h1:1BNtbmhIQbKgs1Gm9WlH3CMms+TgkgxZcU7utHbjloY= github.com/lukaszraczylo/pandati v0.0.15/go.mod h1:5E0mqlNgdOps6FW2X4tPilr+3B8s9JGWFgA6ZVFixyQ= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 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/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -24,12 +33,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs= +github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w= github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo= +github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= @@ -47,13 +60,19 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8= golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/gql.go b/gql.go index 3f9f1a6..4d9bb67 100644 --- a/gql.go +++ b/gql.go @@ -31,11 +31,14 @@ var ( ) type GraphQL struct { - Endpoint string - HttpClient *http.Client - Log *logging.LogConfig - Cache bool // Enable caching for read queries - CacheStore *bigcache.BigCache + Endpoint string + HttpClient *http.Client + Log *logging.LogConfig + Cache bool // Enable caching for read queries + CacheStore *bigcache.BigCache + RetriesEnable bool + RetriesNumber int + RetriesDelay int } func pickGraphqlEndpoint() (graphqlEndpoint string) { @@ -89,9 +92,33 @@ func NewConnection() *GraphQL { }, }, }, - Log: logging.NewLogger(), - Cache: setCacheEnabled(), - CacheStore: setupCache(), + Log: logging.NewLogger(), + Cache: setCacheEnabled(), + CacheStore: setupCache(), + RetriesEnable: false, + RetriesNumber: 1, + RetriesDelay: 250, + } + var err error + retriesEnable, retriesEnableExists := os.LookupEnv("RETRIES_ENABLE") + if retriesEnableExists { + g.RetriesEnable, err = strconv.ParseBool(retriesEnable) + if err != nil { + panic("Invalid value for RETRIES_ENABLE") + } + retriesNumber, retriesNumberExists := os.LookupEnv("RETRIES_NUMBER") + if !retriesNumberExists { + panic("RETRIES_NUMBER environment variable is not set but RETRIES_ENABLE is") + } + g.RetriesNumber, err = strconv.Atoi(retriesNumber) + if err != nil { + panic("Invalid value for RETRIES_NUMBER") + } + retriesDelay, retriesDelayExists := os.LookupEnv("RETRIES_DELAY") + if !retriesDelayExists { + panic("RETRIES_DELAY environment variable is not set but RETRIES_ENABLE is") + } + g.RetriesDelay, err = strconv.Atoi(retriesDelay) } return &g } diff --git a/query.go b/query.go index ced1c78..5d03b18 100644 --- a/query.go +++ b/query.go @@ -8,7 +8,9 @@ import ( "io" "io/ioutil" "net/http" + "time" + retry "github.com/avast/retry-go" "github.com/lukaszraczylo/pandati" ) @@ -68,11 +70,29 @@ func (g *GraphQL) Query(queryContent string, queryVariables interface{}, queryHe httpRequest.Header.Add(fmt.Sprintf("%v", header), fmt.Sprintf("%v", value)) } - httpResponse, err := g.HttpClient.Do(httpRequest) - if err != nil { - g.Log.Error("Unable to send the query", map[string]interface{}{"_error": err.Error()}) - return "", err - } + var httpResponse *http.Response + + err = retry.Do( + func() error { + httpResponse, err = g.HttpClient.Do(httpRequest) + if err != nil { + g.Log.Error("Unable to send the query", map[string]interface{}{"_error": err.Error()}) + return err + } + if httpResponse.StatusCode >= 200 && httpResponse.StatusCode <= 204 { + return errors.New(fmt.Sprintf("%v", httpResponse.StatusCode)) + } + return nil + }, + retry.Attempts(uint(g.RetriesNumber)), + retry.OnRetry( + func(n uint, err error) { + g.Log.Warning("Retrying the query", map[string]interface{}{"_attempt": n, "_error": err.Error()}) + time.Sleep(time.Duration(g.RetriesDelay) * time.Millisecond) + }, + ), + ) + defer io.Copy(ioutil.Discard, httpResponse.Body) defer httpResponse.Body.Close() diff --git a/query_test.go b/query_test.go index 82f4f3d..ec11c58 100644 --- a/query_test.go +++ b/query_test.go @@ -77,14 +77,16 @@ func (suite *TestSuite) Test_GraphQL_Query() { isLocal: true, cache_enabled: false, args: args{ - queryContent: `query listUserBots { - tbl_bots(limit: 1) { - bot_name + queryContent: `query packages_prices { + sub_group_packages_aggregate(where: {enabled: {_eq: true}}) { + aggregate { + count + } } }`, queryVariables: nil, }, - wantResult: `{"tbl_bots":[{"bot_name":"ThetaGuardianBot"}]}`, + wantResult: `{"sub_group_packages_aggregate":{"aggregate":{"count":4}}}`, wantErr: false, }, { @@ -93,14 +95,16 @@ func (suite *TestSuite) Test_GraphQL_Query() { isLocal: true, cache_enabled: true, args: args{ - queryContent: `query listUserBots { - tbl_bots(limit: 1) { - bot_name + queryContent: `query packages_prices { + sub_group_packages_aggregate(where: {enabled: {_eq: true}}) { + aggregate { + count + } } }`, queryVariables: nil, }, - wantResult: `{"tbl_bots":[{"bot_name":"ThetaGuardianBot"}]}`, + wantResult: `{"sub_group_packages_aggregate":{"aggregate":{"count":4}}}`, wantErr: false, }, { @@ -109,14 +113,16 @@ func (suite *TestSuite) Test_GraphQL_Query() { isLocal: true, cache_enabled: true, args: args{ - queryContent: `query listUserBots { - tbl_bots(limit: 1) { - bot_name + queryContent: `query packages_prices { + sub_group_packages_aggregate(where: {enabled: {_eq: true}}) { + aggregate { + count + } } }`, queryVariables: nil, }, - wantResult: `{"tbl_bots":[{"bot_name":"ThetaGuardianBot"}]}`, + wantResult: `{"sub_group_packages_aggregate":{"aggregate":{"count":4}}}`, wantErr: false, }, { @@ -124,9 +130,10 @@ func (suite *TestSuite) Test_GraphQL_Query() { endpoint: "https://hasura.local/v1/graphql", isLocal: true, args: args{ - queryContent: `query listUserBots { - tbl_botz(limit: 1) { - bot_name + queryContent: `query packages_pricez { + sub_group_packages_aggregate(where: {enabled: {_eq: true}}) { + aggregate { + count } }`, queryVariables: nil, @@ -136,21 +143,19 @@ func (suite *TestSuite) Test_GraphQL_Query() { }, { name: "Valid query to https endpoint", - endpoint: "https://web-dev.telegram-bot.app/v1/graphql", + endpoint: "https://telegram-bot.app/v1/graphql", isLocal: false, args: args{ queryContent: `query packages_prices { - available_packages: tbl_available_packages(where: {enabled: {_eq: true}}) { - package_name - package_discount - package_price - package_size - package_type + sub_group_packages_aggregate(where: {enabled: {_eq: true}}) { + aggregate { + count + } } }`, queryVariables: nil, }, - wantResult: `{"available_packages":[{"package_discount":0,"package_name":"media_1000","package_price":750,"package_size":1000,"package_type":"media"},{"package_discount":5,"package_name":"media_5000","package_price":3563,"package_size":5000,"package_type":"media"},{"package_discount":10,"package_name":"media_10000","package_price":6750,"package_size":10000,"package_type":"media"},{"package_discount":15,"package_name":"media_25000","package_price":15938,"package_size":25000,"package_type":"media"},{"package_discount":0,"package_name":"voice_500","package_price":1000,"package_size":500,"package_type":"voice"},{"package_discount":10,"package_name":"voice_1000","package_price":1800,"package_size":1000,"package_type":"voice"},{"package_discount":15,"package_name":"voice_2500","package_price":4250,"package_size":2500,"package_type":"voice"}]}`, + wantResult: `{"sub_group_packages_aggregate":{"aggregate":{"count":4}}}`, wantErr: false, }, {