diff --git a/.env b/.env new file mode 100644 index 0000000..740b5c0 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +APP_KEY=JzLRl2YHe1Ec7MCGqkAJ4byaF08uKLfs +APP_DEBUG=true \ No newline at end of file diff --git a/debug/stack_trace_brispot.go b/debug/stack_trace_brispot.go index ceb2a2f..de56b05 100644 --- a/debug/stack_trace_brispot.go +++ b/debug/stack_trace_brispot.go @@ -3,6 +3,7 @@ package debug import ( "fmt" "runtime" + "slices" "strings" ) @@ -22,34 +23,32 @@ func GetStackTraceOnDebug(pick ...int) string { // May be used when just want to get the stack trace without caring the debug // state. func GetStackTraceInString(pick ...int) string { - stack := make([]uintptr, 2<<6) // 128 - length := runtime.Callers(0, stack) // skip no frames + stack := make([]uintptr, 2<<6) + length := runtime.Callers(0, stack) var pickAll bool - // set default to capture the first found line if len(pick) < 1 { pickAll = true } trackPicked := 1 - var allStackTrace strings.Builder + var traces []string + var seenFiles []string for i := 0; i < length; i++ { funcPtr := runtime.FuncForPC(stack[i]) file, line := funcPtr.FileLine(stack[i]) if strings.Contains(file, "/app/") { - - s := fmt.Sprintf("%s:%d\n", file, line) - // capture the matched pick - if !pickAll && trackPicked == pick[0] { - return s + s := fmt.Sprintf("[%s:%d]", file, line) + if !slices.Contains(seenFiles, file) { + seenFiles = append(seenFiles, file) + if !pickAll && trackPicked == pick[0] { + return s + } + traces = append(traces, s) + trackPicked++ } - - allStackTrace.WriteString(s) - - trackPicked++ - } } - return allStackTrace.String() + return strings.Join(traces, " ") } diff --git a/mocking/errors.go b/mocking/errors.go new file mode 100644 index 0000000..ee8e706 --- /dev/null +++ b/mocking/errors.go @@ -0,0 +1,19 @@ +package mocking + +import "errors" + +const ( + ErrExpectedNetwork = "called error expected network" + ErrExpectedInfra = "called error expected infra" +) + +func ProduceSampleError(errs ...error) error { + var errMsg string + if len(errs) > 0 { + for _, err := range errs { + errMsg += err.Error() + "\n" + } + return errors.New(errMsg) + } + return errors.New(ErrExpectedNetwork) +} diff --git a/stderr/debug.go b/stderr/debug.go index 1e96861..ad29a7e 100644 --- a/stderr/debug.go +++ b/stderr/debug.go @@ -14,14 +14,16 @@ var debugDepthLevel atomic.Int32 func errWithDebug(code string, msg string, httpCode int, metadata ...string) error { e := err{code: code, msg: msg, httpCode: httpCode, metadata: metadata} - switch debugDepthLevel.Load() { - case -1: - e.stackTrc = debug.GetStackTraceOnDebug() - case 0: // the default, set to 1 so that at least it can print one line - e.stackTrc = debug.GetStackTraceOnDebug(1) - default: - e.stackTrc = debug.GetStackTraceOnDebug(int(debugDepthLevel.Load())) - } + //switch debugDepthLevel.Load() { + //case -1: + // e.stackTrc = debug.GetStackTraceOnDebug() + //case 0: // the default, set to 1 so that at least it can print one line + // e.stackTrc = debug.GetStackTraceOnDebug(1) + //default: + // e.stackTrc = debug.GetStackTraceOnDebug(int(debugDepthLevel.Load())) + //} + + e.stackTrc = debug.GetStackTraceOnDebug() return e } diff --git a/stderr/error.go b/stderr/error.go index eaabc1f..75627f6 100644 --- a/stderr/error.go +++ b/stderr/error.go @@ -30,7 +30,28 @@ func (e err) Error() string { // // Each value can be retrieved by helper func such as GetCode, GetMsg, GetMeta. func Err(code string, msg string, httpCode int) error { - return errWithDebug(code, msg, httpCode) + switch code { + case ERROR_CODE_ACCESS_PERMISSION: + return ErrPermission(msg) + case ERROR_CODE_DATA_NOT_FOUND: + return ErrDataNotFound() + case ERROR_CODE_INVALID_RULE: + return ErrInvRule(msg) + case ERROR_CODE_PARAMETER: + return ErrParam(msg) + case ERROR_CODE_WAITING_STATUS: + return ErrWaiting(msg) + case ERROR_CODE_THIRD_PARTY: + return ErrThirdParty(msg) + case ERROR_CODE_UNSUPPORTED: + return ErrUnsupported(msg) + case ERROR_CODE_INVALID_HEADER: + return ErrInvHeader(msg) + case ERROR_CODE_SYSTEM: + return ErrRuntime(msg) + default: + return ErrRuntime(msg) + } } // ErrValidation error in validation, it's not recommended to be used directly. diff --git a/stdresp/standard.go b/stdresp/standard.go index e4f90de..fddea61 100644 --- a/stdresp/standard.go +++ b/stdresp/standard.go @@ -2,6 +2,8 @@ package stdresp import ( "github.com/goravel/framework/contracts/http" + "github.com/spotlibs/go-lib/ctx" + "github.com/spotlibs/go-lib/log" "github.com/spotlibs/go-lib/stderr" ) @@ -25,6 +27,17 @@ func Resp(c http.Context, code, desc string, opts ...StdOpt) http.Response { for _, opt := range opts { opt(&res) } + + // Auto Logging Error + if res.ResponseCode == stderr.ERROR_CODE_SYSTEM { + mt := ctx.Get(c) // Retrieve X-Request-Id + log.Runtime(c).Error(log.Map{ + "code": res.ResponseCode, + "message": res.ResponseDesc, + "requestID": mt.ReqId, + }) + } + return c.Response().Json(res.httpCode, res) } diff --git a/tests/mocking/error_test.go b/tests/mocking/error_test.go new file mode 100644 index 0000000..3b52b58 --- /dev/null +++ b/tests/mocking/error_test.go @@ -0,0 +1,90 @@ +package mocking_test + +import ( + "errors" + "strings" + "testing" + + "github.com/spotlibs/go-lib/mocking" + "github.com/stretchr/testify/assert" +) + +func TestProduceSampleError_NoArgs(t *testing.T) { + err := mocking.ProduceSampleError() + + assert.Error(t, err) + assert.Equal(t, mocking.ErrExpectedNetwork, err.Error()) +} + +func TestProduceSampleError_SingleError(t *testing.T) { + inputErr := errors.New("single error") + err := mocking.ProduceSampleError(inputErr) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "single error") +} + +func TestProduceSampleError_MultipleErrors(t *testing.T) { + err1 := errors.New("error one") + err2 := errors.New("error two") + err3 := errors.New("error three") + + err := mocking.ProduceSampleError(err1, err2, err3) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "error one") + assert.Contains(t, err.Error(), "error two") + assert.Contains(t, err.Error(), "error three") +} + +func TestProduceSampleError_WithNewlines(t *testing.T) { + err1 := errors.New("first") + err2 := errors.New("second") + + err := mocking.ProduceSampleError(err1, err2) + + lines := strings.Split(err.Error(), "\n") + assert.True(t, len(lines) >= 2) +} + +func TestProduceSampleError_InfraError(t *testing.T) { + infraErr := errors.New(mocking.ErrExpectedInfra) + err := mocking.ProduceSampleError(infraErr) + + assert.Error(t, err) + assert.Contains(t, err.Error(), mocking.ErrExpectedInfra) +} + +func TestProduceSampleError_NetworkError(t *testing.T) { + networkErr := errors.New(mocking.ErrExpectedNetwork) + err := mocking.ProduceSampleError(networkErr) + + assert.Error(t, err) + assert.Contains(t, err.Error(), mocking.ErrExpectedNetwork) +} + +func TestProduceSampleError_EmptySlice(t *testing.T) { + err := mocking.ProduceSampleError([]error{}...) + + assert.Error(t, err) + assert.Equal(t, mocking.ErrExpectedNetwork, err.Error()) +} + +func TestProduceSampleError_MixedErrors(t *testing.T) { + err1 := errors.New("database error") + err2 := errors.New("network timeout") + err3 := errors.New("invalid input") + + err := mocking.ProduceSampleError(err1, err2, err3) + + assert.Error(t, err) + errStr := err.Error() + assert.Contains(t, errStr, "database error") + assert.Contains(t, errStr, "network timeout") + assert.Contains(t, errStr, "invalid input") +} + +func TestConstants(t *testing.T) { + assert.Equal(t, "called error expected network", mocking.ErrExpectedNetwork) + assert.Equal(t, "called error expected infra", mocking.ErrExpectedInfra) +} diff --git a/tests/stderr/error_test.go b/tests/stderr/error_test.go index c7d964d..6e9c4e7 100644 --- a/tests/stderr/error_test.go +++ b/tests/stderr/error_test.go @@ -10,7 +10,7 @@ func TestErr(t *testing.T) { err := stderr.Err("77", "oops", 200) // assert the code - if stderr.GetCode(err) != "77" { + if stderr.GetCode(err) != "99" { t.Errorf("expect %s, got %s", "77", stderr.GetCode(err)) } // assert the message @@ -25,7 +25,7 @@ func TestErr(t *testing.T) { func TestErr_Error(t *testing.T) { err := stderr.Err("77", "oops", 200) - if err.Error() != "77 oops" { + if err.Error() != "99 oops" { // -> Map to ErrRuntime, for code except stdCode t.Errorf("expect %s, got %s", "77 oops", err.Error()) } }