diff --git a/execute_query.go b/execute_query.go index 21c06a9..b6c2d52 100644 --- a/execute_query.go +++ b/execute_query.go @@ -3,6 +3,7 @@ package gql import ( "bytes" "compress/gzip" + "errors" "fmt" "io" "net/http" @@ -13,111 +14,104 @@ import ( ) func (qe *QueryExecutor) executeQuery() ([]byte, error) { - httpRequest, err := http.NewRequest("POST", qe.endpoint, bytes.NewBuffer(qe.Query)) + httpRequest, err := http.NewRequest(http.MethodPost, qe.endpoint, bytes.NewBuffer(qe.Query)) if err != nil { - qe.Logger.Error("Can't create HTTP request;", map[string]interface{}{"error": err}) + 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) + httpRequest.Header.Set(key, fmt.Sprint(value)) } - var retries_max int - + retriesMax := 1 if qe.Retries { - qe.Logger.Debug("Retries enabled - setting max retries", map[string]interface{}{"retries": qe.retries_number}) - retries_max = qe.retries_number + qe.Logger.Debug("Retries enabled", map[string]interface{}{"retries": qe.retries_number}) + retriesMax = qe.retries_number } else { - qe.Logger.Debug("Retries disabled - setting max retries", map[string]interface{}{"retries": 1}) - retries_max = 1 + qe.Logger.Debug("Retries disabled", map[string]interface{}{"retries": 1}) } - var httpResponse *http.Response var queryResult queryResults - err = retry.Do( func() error { - httpResponse, err = qe.client.Do(httpRequest) + httpResponse, err := qe.client.Do(httpRequest) if err != nil { - qe.Logger.Debug("Error while executing http request", map[string]interface{}{"error": err.Error()}) + qe.Logger.Debug("Error 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()}) + qe.Logger.Debug("Error 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) + if httpResponse.StatusCode < http.StatusOK || httpResponse.StatusCode >= http.StatusNoContent { + return fmt.Errorf("HTTP error - status code: %s for %s", httpResponse.Status, httpRequest.URL) } var reader io.ReadCloser encoding := httpResponse.Header.Get("Content-Encoding") - if encoding == "gzip" { + switch encoding { + case "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()) + qe.Logger.Debug("Error creating gzip reader", map[string]interface{}{"error": err.Error()}) + return fmt.Errorf("error creating gzip reader: %w", err) } defer reader.Close() - } else { + default: 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()) + qe.Logger.Debug("Error reading HTTP response", map[string]interface{}{"error": err.Error()}) + return fmt.Errorf("error reading HTTP response: %w", err) } 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()) + qe.Logger.Debug("Error unmarshalling HTTP response", map[string]interface{}{"error": err.Error()}) + return fmt.Errorf("error unmarshalling HTTP response: %w", err) } + 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.Attempts(uint(retriesMax)), 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()}) + qe.Logger.Debug("Error executing HTTP request", 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) + qe.Logger.Debug("Error executing query", map[string]interface{}{"error": queryResult.Errors}) + return nil, fmt.Errorf("error 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") + qe.Logger.Debug("Error executing query", map[string]interface{}{"error": "no data"}) + return nil, errors.New("error executing query: no data") } - json_data, err := json.Marshal(queryResult.Data) + jsonData, 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) + qe.Logger.Debug("Error marshalling query result", map[string]interface{}{"error": err.Error(), "data": queryResult.Data}) + return nil, fmt.Errorf("error marshalling query result: %w. Data: %s", err, queryResult.Data) } if qe.CacheKey != "no-cache" { - qe.cache.Set(qe.CacheKey, json_data, qe.CacheTTL) + qe.cache.Set(qe.CacheKey, jsonData, qe.CacheTTL) } - return json_data, nil + return jsonData, nil } diff --git a/go.mod b/go.mod index 322b7a4..0290c4c 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21 require ( github.com/avast/retry-go/v4 v4.6.0 + github.com/buger/jsonparser v1.1.1 github.com/goccy/go-json v0.10.2 github.com/gookit/goutil v0.6.15 github.com/rs/zerolog v1.32.0 diff --git a/go.sum b/go.sum index 0a786dd..39332a7 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= +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.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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= diff --git a/helpers_test.go b/helpers_test.go index 3304b7c..f86dbd2 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -50,12 +50,12 @@ func (suite *Tests) TestBaseClient_decodeResponse() { response []byte } tests := []struct { - name string - args args want any - wantErr bool + name string setType string wantType string + args args + wantErr bool }{ { name: "TestBaseClient_decodeResponse_mapstring", diff --git a/logging/logging.go b/logging/logging.go index 5f10ff9..952e88a 100644 --- a/logging/logging.go +++ b/logging/logging.go @@ -1,9 +1,8 @@ package libpack_logging import ( + "io" "os" - "strings" - "sync" "time" "github.com/gookit/goutil/envutil" @@ -11,83 +10,40 @@ import ( ) type LogConfig struct { - baseLogger zerolog.Logger - errorLogger zerolog.Logger - nopLogger zerolog.Logger - mu sync.Mutex - minLevel zerolog.Level + logger zerolog.Logger } +var baseLogger zerolog.Logger + func init() { zerolog.TimeFieldFormat = time.RFC3339 - zerolog.MessageFieldName = "msg" - zerolog.TimestampFieldName = "ts" + zerolog.MessageFieldName = "short_message" + zerolog.TimestampFieldName = "timestamp" 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 - } + baseLogger = zerolog.New(os.Stdout).With().Timestamp().Logger() } 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(), + switch logLevel := envutil.Getenv("LOG_LEVEL", "info"); logLevel { + case "debug": + baseLogger = baseLogger.Level(zerolog.DebugLevel) + case "warn": + baseLogger = baseLogger.Level(zerolog.WarnLevel) + case "error": + baseLogger = baseLogger.Level(zerolog.ErrorLevel) + default: + baseLogger = baseLogger.Level(zerolog.InfoLevel) } + + return &LogConfig{logger: baseLogger} } -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 { +func (lw *LogConfig) log(w io.Writer, level zerolog.Level, message string, v map[string]interface{}) { + e := lw.logger.With().Logger() + e = e.Output(w) + event := e.WithLevel(level).CallerSkipFrame(3) + for k, val := range v { switch v := val.(type) { case string: event.Str(k, v) @@ -96,39 +52,45 @@ func (lc *LogConfig) Log(level zerolog.Level, message string, fields []map[strin case float64: event.Float64(k, v) default: - event.Interface(k, v) + event.Interface(k, val) } } event.Msg(message) } -func (lc *LogConfig) Info(message string, fields ...map[string]interface{}) { - lc.Log(zerolog.InfoLevel, message, fields) +func (lw *LogConfig) Debug(message string, v ...map[string]interface{}) { + if !lw.logger.Debug().Enabled() { + return + } + lw.log(os.Stdout, zerolog.DebugLevel, message, mergeMaps(v)) } -func (lc *LogConfig) Debug(message string, fields ...map[string]interface{}) { - lc.Log(zerolog.DebugLevel, message, fields) +func (lw *LogConfig) Info(message string, v ...map[string]interface{}) { + if !lw.logger.Info().Enabled() { + return + } + lw.log(os.Stdout, zerolog.InfoLevel, message, mergeMaps(v)) } -func (lc *LogConfig) Warn(message string, fields ...map[string]interface{}) { - lc.Log(zerolog.WarnLevel, message, fields) +func (lw *LogConfig) Warning(message string, v ...map[string]interface{}) { + lw.log(os.Stdout, zerolog.WarnLevel, message, mergeMaps(v)) } -// alias Warning to Warn -func (lc *LogConfig) Warning(message string, fields ...map[string]interface{}) { - lc.Warn(message, fields...) +func (lw *LogConfig) Error(message string, v ...map[string]interface{}) { + lw.log(os.Stderr, zerolog.ErrorLevel, message, mergeMaps(v)) } -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) +func (lw *LogConfig) Critical(message string, v ...map[string]interface{}) { + lw.log(os.Stderr, zerolog.FatalLevel, message, mergeMaps(v)) os.Exit(1) } -// alias Fatal to Critical -func (lc *LogConfig) Fatal(message string, fields ...map[string]interface{}) { - lc.Critical(message, fields...) +func mergeMaps(maps []map[string]interface{}) map[string]interface{} { + result := make(map[string]interface{}) + for _, m := range maps { + for k, v := range m { + result[k] = v + } + } + return result } diff --git a/logging/logging_bench_test.go b/logging/logging_bench_test.go index 68da011..06af809 100644 --- a/logging/logging_bench_test.go +++ b/logging/logging_bench_test.go @@ -25,7 +25,6 @@ func BenchmarkInfoLog(b *testing.B) { }() testsLogger := NewLogger() - b.ResetTimer() for i := 0; i < b.N; i++ { testsLogger.Info("test", map[string]interface{}{"test": "test"}) } diff --git a/logging/logging_test.go b/logging/logging_test.go new file mode 100644 index 0000000..37e2ec6 --- /dev/null +++ b/logging/logging_test.go @@ -0,0 +1,373 @@ +package libpack_logging + +import ( + "errors" + "io" + "os" + "reflect" + "testing" + + "github.com/buger/jsonparser" + "github.com/stretchr/testify/suite" +) + +type LoggingTestSuite struct { + suite.Suite +} + +var ( + testsLogger *LogConfig +) + +type stdoutCapture struct { + oldStdout *os.File + readPipe *os.File +} + +func (sc *stdoutCapture) StartCapture() { + sc.oldStdout = os.Stdout + sc.readPipe, os.Stdout, _ = os.Pipe() +} + +func (sc *stdoutCapture) StopCapture() (string, error) { + if sc.oldStdout == nil || sc.readPipe == nil { + return "", errors.New("StartCapture not called before StopCapture on Stdout") + } + os.Stdout.Close() + os.Stdout = sc.oldStdout + bytes, err := io.ReadAll(sc.readPipe) + if err != nil { + return "", err + } + return string(bytes), nil +} + +type stderrCapture struct { + oldStderr *os.File + readPipe *os.File +} + +func (sc *stderrCapture) StartCapture() { + sc.oldStderr = os.Stderr + sc.readPipe, os.Stderr, _ = os.Pipe() +} + +func (sc *stderrCapture) StopCapture() (string, error) { + if sc.oldStderr == nil || sc.readPipe == nil { + return "", errors.New("StartCapture not called before StopCapture on Stderr") + } + os.Stderr.Close() + os.Stderr = sc.oldStderr + bytes, err := io.ReadAll(sc.readPipe) + if err != nil { + return "", err + } + return string(bytes), nil +} + +func (suite *LoggingTestSuite) SetupTest() { +} + +func TestLoggingTestSuite(t *testing.T) { + suite.Run(t, new(LoggingTestSuite)) +} + +func (suite *LoggingTestSuite) TestLogConfig_AllHandlers() { + type args struct { + message string + } + tests := []struct { + name string + args args + wantLevel string + wantMessage string + envMinLogLevel string + loggerType string + stdOutExpect bool + stdErrExpect bool + }{ + { + name: "Test log: Error", + loggerType: "Error", + args: args{ + message: "This is a error message", + }, + wantLevel: "error", + wantMessage: "This is a error message", + stdErrExpect: true, + stdOutExpect: false, + }, + { + name: "Test log: Warning", + loggerType: "Warning", + args: args{ + message: "This is a warning message", + }, + wantLevel: "warn", + wantMessage: "This is a warning message", + stdErrExpect: false, + stdOutExpect: true, + envMinLogLevel: "info", + }, + { + name: "Test log: Warning | Min level: Debug", + loggerType: "Warning", + args: args{ + message: "This is a warning message", + }, + wantLevel: "warn", + wantMessage: "This is a warning message", + stdErrExpect: false, + stdOutExpect: true, + envMinLogLevel: "debug", + }, + { + name: "Test log: Info", + loggerType: "Info", + args: args{ + message: "This is a info message", + }, + wantLevel: "info", + wantMessage: "This is a info message", + stdErrExpect: false, + stdOutExpect: true, + }, + { + name: "Test log: Info | Min level: Warn", + loggerType: "Info", + args: args{ + message: "This is a info message", + }, + wantLevel: "", + wantMessage: "", + stdErrExpect: false, + stdOutExpect: false, + envMinLogLevel: "warn", + }, + { + name: "Test log: Warning | Min level: Warn", + loggerType: "Warning", + args: args{ + message: "This is a warning message", + }, + wantLevel: "warn", + wantMessage: "This is a warning message", + stdErrExpect: false, + stdOutExpect: true, + envMinLogLevel: "warn", + }, + { + name: "Test log: Warning | Min level: Error", + loggerType: "Warning", + args: args{ + message: "This is an error message", + }, + wantLevel: "", + wantMessage: "", + stdErrExpect: false, + stdOutExpect: false, + envMinLogLevel: "error", + }, + { + name: "Test log: Debug | Min level: Debug", + loggerType: "Debug", + args: args{ + message: "This is a debug message", + }, + wantLevel: "debug", + wantMessage: "This is a debug message", + stdErrExpect: false, + stdOutExpect: true, + envMinLogLevel: "debug", + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + if tt.envMinLogLevel != "" { + os.Setenv("LOG_LEVEL", tt.envMinLogLevel) + defer os.Unsetenv("LOG_LEVEL") + } + testsLogger = NewLogger() + + captureStdout := stdoutCapture{} + captureStdout.StartCapture() + captureStderr := stderrCapture{} + captureStderr.StartCapture() + + reflect.ValueOf(testsLogger).MethodByName(tt.loggerType).Call([]reflect.Value{reflect.ValueOf(tt.args.message)}) + + stdoutOut, err := captureStdout.StopCapture() + if err != nil { + suite.T().Fatal(err) + } + + stderrOut, err := captureStderr.StopCapture() + if err != nil { + suite.T().Fatal(err) + } + + if tt.stdErrExpect && !tt.stdOutExpect { + gotLvl, gotMsg, err := getResponseValues(stderrOut, "short_message") + suite.NoError(err, "Failed in [STDERR]: "+tt.name) + suite.Equal(tt.wantLevel, gotLvl, "Failed in [STDERR]: "+tt.name) + suite.Equal(tt.wantMessage, gotMsg, "Failed in [STDERR]: "+tt.name) + suite.Equal("", stdoutOut, "Failed in [STDERR]: "+tt.name) + } + if tt.stdOutExpect && !tt.stdErrExpect { + gotLvl, gotMsg, err := getResponseValues(stdoutOut, "short_message") + suite.NoError(err, "Failed in [STDOUT]: "+tt.name) + suite.Equal(tt.wantLevel, gotLvl, "Failed in [STDOUT]: "+tt.name) + suite.Equal(tt.wantMessage, gotMsg, "Failed in [STDOUT]: "+tt.name) + suite.Equal("", stderrOut, "Failed in [STDOUT]: "+tt.name) + } + if !tt.stdErrExpect && !tt.stdOutExpect { + suite.Equal("", stderrOut, "Failed in [NEITHER]: "+tt.name) + suite.Equal("", stdoutOut, "Failed in [NEITHER]: "+tt.name) + } + os.Unsetenv("LOG_LEVEL") + }) + } +} + +func (suite *LoggingTestSuite) TestFullMessage() { + type args struct { + extraFields map[string]interface{} + message string + } + extraFields := make(map[string]interface{}) + extraFields["_full_message"] = "full message" + + tests := []struct { + args args + name string + wantLevel string + wantMessage string + envMinLogLevel string + loggerType string + stdOutExpect bool + stdErrExpect bool + }{ + { + name: "Test log: Error", + loggerType: "Error", + args: args{ + message: "This is a error message", + extraFields: extraFields, + }, + wantLevel: "error", + wantMessage: extraFields["_full_message"].(string), + stdErrExpect: true, + stdOutExpect: false, + }, + { + name: "Test log: Info", + loggerType: "Info", + args: args{ + message: "This is a info message", + extraFields: extraFields, + }, + wantMessage: extraFields["_full_message"].(string), + stdErrExpect: false, + stdOutExpect: true, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + if tt.envMinLogLevel != "" { + os.Setenv("LOG_LEVEL", tt.envMinLogLevel) + defer os.Unsetenv("LOG_LEVEL") + } + testsLogger = NewLogger() + + captureStdout := stdoutCapture{} + captureStdout.StartCapture() + captureStderr := stderrCapture{} + captureStderr.StartCapture() + + reflect.ValueOf(testsLogger).MethodByName(tt.loggerType).Call([]reflect.Value{ + reflect.ValueOf(tt.args.message), + reflect.ValueOf(tt.args.extraFields), + }) + + stdoutOut, err := captureStdout.StopCapture() + if err != nil { + suite.T().Fatal(err) + } + + stderrOut, err := captureStderr.StopCapture() + if err != nil { + suite.T().Fatal(err) + } + + if tt.stdErrExpect && !tt.stdOutExpect { + _, gotMsg, err := getResponseValues(stderrOut, "_full_message") + suite.NoError(err, "Failed in [STDERR]: "+tt.name) + suite.Equal(tt.wantMessage, gotMsg, "Failed in [STDERR]: "+tt.name) + } + if tt.stdOutExpect && !tt.stdErrExpect { + _, gotMsg, err := getResponseValues(stdoutOut, "_full_message") + suite.NoError(err, "Failed in [STDOUT]: "+tt.name) + suite.Equal(tt.wantMessage, gotMsg, "Failed in [STDOUT]: "+tt.name) + } + os.Unsetenv("LOG_LEVEL") + }) + } +} + +func Test_getResponseValues(t *testing.T) { + type args struct { + sourceJson string + } + tests := []struct { + name string + args args + wantGotLvl string + wantGotMsg string + wantErr bool + }{ + { + name: "Test with json", + args: args{ + sourceJson: `{"level": "debug", "short_message": "hello world"`, + }, + wantGotLvl: "debug", + wantGotMsg: "hello world", + wantErr: false, + }, + { + name: "Test with json, wrong message field", + args: args{ + sourceJson: `{"level": "debug", "message": "hello world"`, + }, + wantGotLvl: "debug", + wantGotMsg: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotGotLvl, gotGotMsg, err := getResponseValues(tt.args.sourceJson, "short_message") + if (err != nil) != tt.wantErr { + t.Errorf("getResponseValues() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotGotLvl != tt.wantGotLvl { + t.Errorf("getResponseValues() gotGotLvl = %v, want %v", gotGotLvl, tt.wantGotLvl) + } + if gotGotMsg != tt.wantGotMsg { + t.Errorf("getResponseValues() gotGotMsg = %v, want %v", gotGotMsg, tt.wantGotMsg) + } + }) + } +} + +func getResponseValues(sourceJson string, key string) (gotLvl, gotMsg string, err error) { + gotLvl, err = jsonparser.GetString([]byte(sourceJson), "level") + if err != nil { + return + } + gotMsg, err = jsonparser.GetString([]byte(sourceJson), key) + return +} diff --git a/query.go b/query.go index 2d74702..c69f961 100644 --- a/query.go +++ b/query.go @@ -11,15 +11,15 @@ import ( func (b *BaseClient) convertToJSON(v any) []byte { jsonData, err := json.Marshal(v) if err != nil { - b.Logger.Error("Can't convert to json;", map[string]interface{}{"error": err.Error()}) + b.Logger.Error("Can't convert to JSON", map[string]interface{}{"error": err.Error()}) return nil } return jsonData } -func (b *BaseClient) compileQuery(query_partials ...any) *Query { - q := new(Query) - for _, partial := range query_partials { +func (b *BaseClient) compileQuery(queryPartials ...any) *Query { + q := &Query{} + for _, partial := range queryPartials { switch val := partial.(type) { case string: q.Query = val @@ -28,42 +28,39 @@ func (b *BaseClient) compileQuery(query_partials ...any) *Query { } } if q.Query == "" { - b.Logger.Error("Can't compile query;", map[string]interface{}{"error": "query is empty"}) + b.Logger.Error("Can't compile query", map[string]interface{}{"error": "query is empty"}) return nil } q.JsonQuery = b.convertToJSON(q) return q } -func (b *BaseClient) Query(query string, variables map[string]interface{}, headers map[string]interface{}) (returned_value any, err error) { +func (b *BaseClient) Query(query string, variables map[string]interface{}, headers map[string]interface{}) (any, error) { compiledQuery := b.compileQuery(query, variables) if compiledQuery.JsonQuery == nil { - b.Logger.Error("Can't compile query;", map[string]interface{}{"error": "query is empty"}) - return nil, fmt.Errorf("Can't compile query") + b.Logger.Error("Can't compile query", map[string]interface{}{"error": "query is empty"}) + return nil, fmt.Errorf("can't compile query") } + b.Logger.Debug("Compiled query", map[string]interface{}{"query": compiledQuery}) - b.Logger.Debug("Compiled query;", map[string]interface{}{"query": compiledQuery}) - enable_cache, enable_retries, recompile_required := compiledQuery.parseHeadersAndVariables(headers) - - if recompile_required { + enableCache, enableRetries, recompileRequired := compiledQuery.parseHeadersAndVariables(headers) + if recompileRequired { compiledQuery = b.compileQuery(query, variables) } var queryHash string - - if (enable_cache || b.cache_global) && strutil.HasPrefix(compiledQuery.Query, "query") { + if (enableCache || b.cache_global) && strutil.HasPrefix(compiledQuery.Query, "query") { b.Logger.Debug("Cache enabled") queryHash = calculateHash(compiledQuery) - cached_value := b.cacheLookup(queryHash) - if cached_value != nil { + cachedValue := b.cacheLookup(queryHash) + if cachedValue != nil { b.Logger.Debug("Cache hit", map[string]interface{}{"query": compiledQuery}) - return cached_value, nil - } else { - b.Logger.Debug("Cache miss", map[string]interface{}{"query": compiledQuery}) + return cachedValue, nil } + b.Logger.Debug("Cache miss", map[string]interface{}{"query": compiledQuery}) } - if enable_retries || b.retries_enable { + if enableRetries || b.retries_enable { b.Logger.Debug("Retries enabled") } @@ -74,33 +71,36 @@ func (b *BaseClient) Query(query string, variables map[string]interface{}, heade CacheKey: func() string { if queryHash != "" { return queryHash - } else { - return "no-cache" } + return "no-cache" }(), - Retries: enable_retries || b.retries_enable, + Retries: enableRetries || b.retries_enable, } defer func() { q = nil }() + rv, err := q.executeQuery() if err != nil { - b.Logger.Error("Error while executing query;", map[string]interface{}{"error": err.Error()}) + b.Logger.Error("Error executing query", map[string]interface{}{"error": err.Error()}) return nil, err } - returned_value, err = q.decodeResponse(rv) - return returned_value, err + + returnedValue, err := q.decodeResponse(rv) + return returnedValue, err } -func (q *Query) parseHeadersAndVariables(headers map[string]interface{}) (enable_cache bool, enable_retries bool, recompile_required bool) { - if headers != nil { - enable_cache, _ = goutil.ToBool(searchForKeysInMapStringInterface(headers, "gqlcache")) - enable_retries, _ = goutil.ToBool(searchForKeysInMapStringInterface(headers, "gqlretries")) - } +func (q *Query) parseHeadersAndVariables(headers map[string]interface{}) (enableCache, enableRetries, recompileRequired bool) { + enableCache, _ = goutil.ToBool(searchForKeysInMapStringInterface(headers, "gqlcache")) + enableRetries, _ = goutil.ToBool(searchForKeysInMapStringInterface(headers, "gqlretries")) + if q.Variables != nil { - enable_cache, _ = goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlcache")) - enable_retries, _ = goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlretries")) + varEnableCache, _ := goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlcache")) + varEnableRetries, _ := goutil.ToBool(searchForKeysInMapStringInterface(q.Variables, "gqlretries")) + enableCache = enableCache || varEnableCache + enableRetries = enableRetries || varEnableRetries delete(q.Variables, "gqlcache") delete(q.Variables, "gqlretries") - recompile_required = true + recompileRequired = true } - return + + return enableCache, enableRetries, recompileRequired }