diff --git a/crypt/aes.go b/crypt/aes.go index 334127528..1d42d967b 100644 --- a/crypt/aes.go +++ b/crypt/aes.go @@ -8,11 +8,11 @@ import ( "errors" "io" - "github.com/bytedance/sonic" "github.com/gookit/color" "github.com/goravel/framework/contracts/config" "github.com/goravel/framework/support" + "github.com/goravel/framework/support/json" ) type AES struct { @@ -60,10 +60,12 @@ func (b *AES) EncryptString(value string) (string, error) { ciphertext := aesgcm.Seal(nil, iv, plaintext, nil) - jsonEncoded, err := sonic.Marshal(map[string][]byte{ + var jsonEncoded []byte + jsonEncoded, err = json.Marshal(map[string][]byte{ "iv": iv, "value": ciphertext, }) + if err != nil { return "", err } @@ -79,7 +81,7 @@ func (b *AES) DecryptString(payload string) (string, error) { } decodeJson := make(map[string][]byte) - err = sonic.Unmarshal(decodePayload, &decodeJson) + err = json.Unmarshal(decodePayload, &decodeJson) if err != nil { return "", err } diff --git a/filesystem/local_test.go b/filesystem/local_test.go index bb3b8dd7b..147aca901 100644 --- a/filesystem/local_test.go +++ b/filesystem/local_test.go @@ -5,7 +5,6 @@ import ( "mime" "os" "path/filepath" - "runtime" "testing" "github.com/stretchr/testify/assert" @@ -13,6 +12,7 @@ import ( configmock "github.com/goravel/framework/contracts/config/mocks" "github.com/goravel/framework/support/carbon" + "github.com/goravel/framework/support/env" "github.com/goravel/framework/support/file" ) @@ -70,28 +70,28 @@ func (s *LocalTestSuite) TestAllDirectories() { s.True(s.local.Exists("AllDirectories/3/5/6/6.txt")) files, err := s.local.AllDirectories("AllDirectories") s.Nil(err) - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal([]string{"3\\", "3\\4\\", "3\\5\\", "3\\5\\6\\"}, files) } else { s.Equal([]string{"3/", "3/4/", "3/5/", "3/5/6/"}, files) } files, err = s.local.AllDirectories("./AllDirectories") s.Nil(err) - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal([]string{"3\\", "3\\4\\", "3\\5\\", "3\\5\\6\\"}, files) } else { s.Equal([]string{"3/", "3/4/", "3/5/", "3/5/6/"}, files) } files, err = s.local.AllDirectories("/AllDirectories") s.Nil(err) - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal([]string{"3\\", "3\\4\\", "3\\5\\", "3\\5\\6\\"}, files) } else { s.Equal([]string{"3/", "3/4/", "3/5/", "3/5/6/"}, files) } files, err = s.local.AllDirectories("./AllDirectories/") s.Nil(err) - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal([]string{"3\\", "3\\4\\", "3\\5\\", "3\\5\\6\\"}, files) } else { s.Equal([]string{"3/", "3/4/", "3/5/", "3/5/6/"}, files) @@ -110,28 +110,28 @@ func (s *LocalTestSuite) TestAllFiles() { s.True(s.local.Exists("AllFiles/3/4/4.txt")) files, err := s.local.AllFiles("AllFiles") s.Nil(err) - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal([]string{"1.txt", "2.txt", "3\\3.txt", "3\\4\\4.txt"}, files) } else { s.Equal([]string{"1.txt", "2.txt", "3/3.txt", "3/4/4.txt"}, files) } files, err = s.local.AllFiles("./AllFiles") s.Nil(err) - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal([]string{"1.txt", "2.txt", "3\\3.txt", "3\\4\\4.txt"}, files) } else { s.Equal([]string{"1.txt", "2.txt", "3/3.txt", "3/4/4.txt"}, files) } files, err = s.local.AllFiles("/AllFiles") s.Nil(err) - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal([]string{"1.txt", "2.txt", "3\\3.txt", "3\\4\\4.txt"}, files) } else { s.Equal([]string{"1.txt", "2.txt", "3/3.txt", "3/4/4.txt"}, files) } files, err = s.local.AllFiles("./AllFiles/") s.Nil(err) - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal([]string{"1.txt", "2.txt", "3\\3.txt", "3\\4\\4.txt"}, files) } else { s.Equal([]string{"1.txt", "2.txt", "3/3.txt", "3/4/4.txt"}, files) @@ -178,28 +178,28 @@ func (s *LocalTestSuite) TestDirectories() { s.True(s.local.Exists("Directories/3/5/5.txt")) files, err := s.local.Directories("Directories") s.Nil(err) - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal([]string{"3\\"}, files) } else { s.Equal([]string{"3/"}, files) } files, err = s.local.Directories("./Directories") s.Nil(err) - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal([]string{"3\\"}, files) } else { s.Equal([]string{"3/"}, files) } files, err = s.local.Directories("/Directories") s.Nil(err) - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal([]string{"3\\"}, files) } else { s.Equal([]string{"3/"}, files) } files, err = s.local.Directories("./Directories/") s.Nil(err) - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal([]string{"3\\"}, files) } else { s.Equal([]string{"3/"}, files) @@ -424,7 +424,7 @@ func (s *LocalTestSuite) TestWithContext() { func (s *LocalTestSuite) TestUrl() { s.Equal("https://goravel.dev/Url/1.txt", s.local.Url("Url/1.txt")) - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal("https://goravel.dev/Url/2.txt", s.local.Url(`Url\2.txt`)) } } diff --git a/log/formatter/general.go b/log/formatter/general.go index 7336824da..e5af5d5a7 100644 --- a/log/formatter/general.go +++ b/log/formatter/general.go @@ -6,10 +6,11 @@ import ( "strings" "time" - "github.com/bytedance/sonic" "github.com/sirupsen/logrus" + "github.com/spf13/cast" "github.com/goravel/framework/contracts/config" + "github.com/goravel/framework/support/json" ) type General struct { @@ -53,44 +54,38 @@ func formatData(data logrus.Fields) (string, error) { var builder strings.Builder if len(data) > 0 { - dataBytes, err := sonic.Marshal(data) - if err != nil { - return "", err - } - removedData := deleteKey(data, "root") if len(removedData) > 0 { - removedDataBytes, err := sonic.Marshal(removedData) + removedDataBytes, err := json.Marshal(removedData) if err != nil { return "", err } + builder.WriteString(fmt.Sprintf("fields: %s\n", string(removedDataBytes))) } - root, err := sonic.Get(dataBytes, "root") + root, err := cast.ToStringMapE(data["root"]) if err != nil { return "", err } for _, key := range []string{"code", "context", "domain", "hint", "owner", "request", "response", "tags", "user"} { - if value := root.Get(key); value.Valid() { - info, err := value.Raw() + if value, exists := root[key]; exists && value != nil { + v, err := json.Marshal(value) if err != nil { return "", err } - builder.WriteString(fmt.Sprintf("%s: %s\n", key, info)) + + builder.WriteString(fmt.Sprintf(`%s: %v"\n`, key, string(v))) } } - if stackTraceValue := root.Get("stacktrace"); stackTraceValue.Valid() { - stackTraces, err := stackTraceValue.Interface() - if err != nil { - return "", err - } - traces, err := formatStackTraces(stackTraces) + if stackTraceValue, exists := root["stacktrace"]; exists && stackTraceValue != nil { + traces, err := formatStackTraces(stackTraceValue) if err != nil { return "", err } + builder.WriteString(traces) } } @@ -105,6 +100,7 @@ func deleteKey(data logrus.Fields, keyToDelete string) logrus.Fields { dataCopy[key] = value } } + return dataCopy } @@ -121,12 +117,13 @@ type StackTrace struct { func formatStackTraces(stackTraces any) (string, error) { var formattedTraces strings.Builder - data, err := sonic.Marshal(stackTraces) + data, err := json.Marshal(stackTraces) + if err != nil { return "", err } var traces StackTrace - err = sonic.Unmarshal(data, &traces) + err = json.Unmarshal(data, &traces) if err != nil { return "", err } diff --git a/log/formatter/general_test.go b/log/formatter/general_test.go index b36b70f61..15d188126 100644 --- a/log/formatter/general_test.go +++ b/log/formatter/general_test.go @@ -56,7 +56,7 @@ func (s *GeneralTestSuite) TestFormat() { name: "Data is not empty", setup: func() { s.entry.Data = logrus.Fields{ - "root": map[string]interface{}{ + "root": map[string]any{ "code": "200", "domain": "example.com", "owner": "owner", @@ -119,14 +119,14 @@ func TestFormatData(t *testing.T) { name: "Invalid data type", setup: func() { data = logrus.Fields{ - "root": map[string]interface{}{ + "root": map[string]any{ "code": "123", "context": "sample", "domain": "example.com", "hint": make(chan int), // Invalid data type that will cause an error during value extraction "owner": "owner", - "request": map[string]interface{}{"method": "GET", "uri": "http://localhost"}, - "response": map[string]interface{}{"status": 200}, + "request": map[string]any{"method": "GET", "uri": "http://localhost"}, + "response": map[string]any{"status": 200}, "tags": []string{"tag1", "tag2"}, "user": "user1", }, @@ -142,7 +142,7 @@ func TestFormatData(t *testing.T) { name: "Data is not empty", setup: func() { data = logrus.Fields{ - "root": map[string]interface{}{ + "root": map[string]any{ "code": "200", "domain": "example.com", "owner": "owner", @@ -234,8 +234,8 @@ func TestFormatStackTraces(t *testing.T) { { name: "StackTraces is not nil", setup: func() { - stackTraces = map[string]interface{}{ - "root": map[string]interface{}{ + stackTraces = map[string]any{ + "root": map[string]any{ "message": "error bad request", // root cause "stack": []string{ "main.main:/dummy/examples/logging/example.go:143", // original calling method @@ -244,7 +244,7 @@ func TestFormatStackTraces(t *testing.T) { "main.(*Request).Validate:/dummy/examples/logging/example.go:28", // location of the root }, }, - "wrap": []map[string]interface{}{ + "wrap": []map[string]any{ { "message": "received a request with no ID", // additional context "stack": "main.(*Request).Validate:/dummy/examples/logging/example.go:29", // location of Wrap call diff --git a/support/carbon/json_test.go b/support/carbon/json_test.go index 77b0b793d..8809366ce 100644 --- a/support/carbon/json_test.go +++ b/support/carbon/json_test.go @@ -1,10 +1,10 @@ package carbon import ( + "encoding/json" "fmt" "testing" - "github.com/bytedance/sonic" "github.com/stretchr/testify/assert" ) @@ -44,7 +44,7 @@ func TestMarshalJSON(t *testing.T) { CreatedAt3: TimestampMicro{Parse("2025-08-05 13:14:15.999999")}, CreatedAt4: TimestampNano{Parse("2025-08-05 13:14:15.999999999")}, } - data, err := sonic.Marshal(&person) + data, err := json.Marshal(&person) assert.Nil(t, err) fmt.Printf("Person output by json:\n%s\n", data) } @@ -67,7 +67,7 @@ func TestUnmarshalJSON(t *testing.T) { "created_at4": 1596604455999999999 }` - err := sonic.Unmarshal([]byte(str), &person) + err := json.Unmarshal([]byte(str), &person) assert.Nil(t, err) fmt.Printf("Json string parse to person:\n%+v\n", person) } @@ -89,7 +89,7 @@ func TestErrorJson(t *testing.T) { "created_at3": 0, "created_at4": 0 }` - err := sonic.Unmarshal([]byte(str), &person) + err := json.Unmarshal([]byte(str), &person) assert.NotNil(t, err) fmt.Printf("Json string parse to person:\n%+v\n", person) } diff --git a/support/env/env.go b/support/env/env.go new file mode 100644 index 000000000..076f547c2 --- /dev/null +++ b/support/env/env.go @@ -0,0 +1,39 @@ +package env + +import "runtime" + +// IsWindows returns whether the current operating system is Windows. +// IsWindows 返回当前操作系统是否为 Windows。 +func IsWindows() bool { + return runtime.GOOS == "windows" +} + +// IsLinux returns whether the current operating system is Linux. +// IsLinux 返回当前操作系统是否为 Linux。 +func IsLinux() bool { + return runtime.GOOS == "linux" +} + +// IsDarwin returns whether the current operating system is Darwin. +// IsDarwin 返回当前操作系统是否为 Darwin。 +func IsDarwin() bool { + return runtime.GOOS == "darwin" +} + +// IsArm returns whether the current CPU architecture is ARM. +// IsArm 返回当前 CPU 架构是否为 ARM。 +func IsArm() bool { + return runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" +} + +// IsX86 returns whether the current CPU architecture is X86. +// IsX86 返回当前 CPU 架构是否为 X86。 +func IsX86() bool { + return runtime.GOARCH == "386" || runtime.GOARCH == "amd64" +} + +// Is64Bit returns whether the current CPU architecture is 64-bit. +// Is64Bit 返回当前 CPU 架构是否为 64 位。 +func Is64Bit() bool { + return runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" +} diff --git a/support/json/json.go b/support/json/json.go new file mode 100644 index 000000000..0f57768fc --- /dev/null +++ b/support/json/json.go @@ -0,0 +1,32 @@ +//go:build !amd64 + +package json + +import ( + "encoding/json" +) + +// Marshal is a wrapper of json.Marshal. +// Marshal 是 json.Marshal 的包装器。 +func Marshal(v any) ([]byte, error) { + return json.Marshal(v) +} + +// Unmarshal is a wrapper of json.Unmarshal. +// Unmarshal 是 json.Unmarshal 的包装器。 +func Unmarshal(data []byte, v any) error { + return json.Unmarshal(data, v) +} + +// MarshalString is a wrapper of json.Marshal. +// MarshalString 是 json.Marshal 的包装器。 +func MarshalString(v any) (string, error) { + s, err := json.Marshal(v) + return string(s), err +} + +// UnmarshalString is a wrapper of json.Unmarshal. +// UnmarshalString 是 json.Unmarshal 的包装器。 +func UnmarshalString(data string, v any) error { + return json.Unmarshal([]byte(data), v) +} diff --git a/support/json/json_test.go b/support/json/json_test.go new file mode 100644 index 000000000..a0cc218af --- /dev/null +++ b/support/json/json_test.go @@ -0,0 +1,33 @@ +package json + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMarshal(t *testing.T) { + json, err := Marshal(map[string]int{"a": 1}) + assert.Equal(t, []byte(`{"a":1}`), json) + assert.Nil(t, err) +} + +func TestUnmarshal(t *testing.T) { + var m map[string]int + err := Unmarshal([]byte(`{"a":1}`), &m) + assert.Equal(t, map[string]int{"a": 1}, m) + assert.Nil(t, err) +} + +func TestMarshalString(t *testing.T) { + json, err := MarshalString(map[string]int{"a": 1}) + assert.Equal(t, `{"a":1}`, json) + assert.Nil(t, err) +} + +func TestUnmarshalString(t *testing.T) { + var m map[string]int + err := UnmarshalString(`{"a":1}`, &m) + assert.Equal(t, map[string]int{"a": 1}, m) + assert.Nil(t, err) +} diff --git a/support/json/json_x86.go b/support/json/json_x86.go new file mode 100644 index 000000000..818a4578f --- /dev/null +++ b/support/json/json_x86.go @@ -0,0 +1,31 @@ +//go:build amd64 + +package json + +import ( + "github.com/bytedance/sonic" +) + +// Marshal is a wrapper of sonic.Marshal. +// Marshal 是 sonic.Marshal 的包装器。 +func Marshal(v any) ([]byte, error) { + return sonic.Marshal(v) +} + +// Unmarshal is a wrapper of sonic.Unmarshal. +// Unmarshal 是 sonic.Unmarshal 的包装器。 +func Unmarshal(data []byte, v any) error { + return sonic.Unmarshal(data, v) +} + +// MarshalString is a wrapper of sonic.MarshalString. +// MarshalString 是 sonic.MarshalString 的包装器。 +func MarshalString(v any) (string, error) { + return sonic.MarshalString(v) +} + +// UnmarshalString is a wrapper of sonic.UnmarshalString. +// UnmarshalString 是 sonic.UnmarshalString 的包装器。 +func UnmarshalString(data string, v any) error { + return sonic.UnmarshalString(data, v) +} diff --git a/support/str/str_test.go b/support/str/str_test.go index 87742960b..12ba19663 100644 --- a/support/str/str_test.go +++ b/support/str/str_test.go @@ -1,11 +1,12 @@ package str import ( - "runtime" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + + "github.com/goravel/framework/support/env" ) type StringTestSuite struct { @@ -49,7 +50,7 @@ func (s *StringTestSuite) TestBasename() { s.Equal("str", Of("str/").Basename().String()) str := Of("/").Basename().String() - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal("\\", str) } else { s.Equal("/", str) @@ -127,14 +128,14 @@ func (s *StringTestSuite) TestContainsAll() { func (s *StringTestSuite) TestDirname() { str := Of("/framework/support/str").Dirname().String() - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal("\\framework\\support", str) } else { s.Equal("/framework/support", str) } str = Of("/framework/support/str").Dirname(2).String() - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal("\\framework", str) } else { s.Equal("/framework", str) @@ -144,14 +145,14 @@ func (s *StringTestSuite) TestDirname() { s.Equal(".", Of(".").Dirname().String()) str = Of("/").Dirname().String() - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal("\\", str) } else { s.Equal("/", str) } str = Of("/framework/").Dirname(2).String() - if runtime.GOOS == "windows" { + if env.IsWindows() { s.Equal("\\", str) } else { s.Equal("/", str)