From 0b68e4bb9d26095f62da3003a63773e215cc8b05 Mon Sep 17 00:00:00 2001 From: Michael Li Date: Fri, 10 Mar 2023 18:24:10 +0800 Subject: [PATCH] add JsonBox type in pkg/types --- pkg/types/json_box.go | 82 +++++++++++++++++++++++++++++++++ pkg/types/json_box_test.go | 85 +++++++++++++++++++++++++++++++++++ pkg/types/types.go | 6 +++ pkg/types/types_suite_test.go | 17 +++++++ 4 files changed, 190 insertions(+) create mode 100644 pkg/types/json_box.go create mode 100644 pkg/types/json_box_test.go create mode 100644 pkg/types/types_suite_test.go diff --git a/pkg/types/json_box.go b/pkg/types/json_box.go new file mode 100644 index 00000000..1494526a --- /dev/null +++ b/pkg/types/json_box.go @@ -0,0 +1,82 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package types + +import ( + "database/sql" + "database/sql/driver" + stdjson "encoding/json" + "errors" + "fmt" + + "github.com/rocboss/paopao-ce/pkg/json" +) + +var ( + _ stdjson.Marshaler = (*JsonBox[any])(nil) + _ stdjson.Unmarshaler = (*JsonBox[any])(nil) + _ driver.Valuer = (*JsonBox[any])(nil) + _ sql.Scanner = (*JsonBox[any])(nil) + _ Boxes[any] = (*JsonBox[any])(nil) +) + +// JsonBox Json box for process database/sql json data +type JsonBox[T any] struct { + data T +} + +func (j *JsonBox[T]) Box(t T) { + j.data = t +} + +func (j *JsonBox[T]) Unbox() T { + return j.data +} + +func (j *JsonBox[T]) MarshalJSON() ([]byte, error) { + if j == nil { + return []byte(`null`), nil + } + return json.Marshal(j.data) +} + +func (j *JsonBox[T]) UnmarshalJSON(data []byte) error { + if j == nil { + return errors.New("JsonBox.UnmarshalJSON: on nil pointer") + } + return json.Unmarshal(data, &j.data) + +} + +func (j *JsonBox[T]) Value() (driver.Value, error) { + if j == nil { + return nil, nil + } + return j.MarshalJSON() +} + +func (j *JsonBox[T]) Scan(value any) error { + if value == nil { + return nil + } + var b []byte + switch v := value.(type) { + case []byte: + b = v + case string: + b = []byte(v) + default: + return fmt.Errorf("JsonBox.Scan: expected []byte or string, got %T (%q)", value, value) + } + return j.UnmarshalJSON(b) +} + +// NewJsonBox create a new JsonBox instance +func NewJsonBox[T any](t ...T) *JsonBox[T] { + if len(t) > 0 { + return &JsonBox[T]{data: t[0]} + } + return &JsonBox[T]{} +} diff --git a/pkg/types/json_box_test.go b/pkg/types/json_box_test.go new file mode 100644 index 00000000..b45c5a4f --- /dev/null +++ b/pkg/types/json_box_test.go @@ -0,0 +1,85 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package types_test + +import ( + "encoding/json" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/rocboss/paopao-ce/pkg/types" +) + +var _ = Describe("JsonBox", Ordered, func() { + type jsonCases []struct { + j *types.JsonBox[json.RawMessage] + b []byte + } + var samples jsonCases + + BeforeAll(func() { + samples = jsonCases{ + { + j: types.NewJsonBox(json.RawMessage(`null`)), + b: []byte(`null`), + }, + { + j: types.NewJsonBox(json.RawMessage(`{}`)), + b: []byte(`{}`), + }, + { + j: types.NewJsonBox(json.RawMessage(`[]`)), + b: []byte(`[]`), + }, + { + j: types.NewJsonBox(json.RawMessage(`[{"b":true,"n":123},{"s":"foo","obj":{"f1":456,"f2":false}},[null]]`)), + b: []byte(`[{"b":true,"n":123},{"s":"foo","obj":{"f1":456,"f2":false}},[null]]`), + }, + } + }) + + It("boxes Box and Unbox", func() { + for _, t := range samples { + jv := types.NewJsonBox[json.RawMessage]() + jv.Box(json.RawMessage(t.b)) + Expect(jv.Unbox()).To(Equal(t.j.Unbox())) + } + }) + + It("json marshaler", func() { + for _, t := range samples { + mv, err := t.j.MarshalJSON() + Expect(err).To(BeNil()) + Expect(mv).To(Equal(t.b)) + } + }) + + It("json unmarshaler", func() { + for _, t := range samples { + jv := types.NewJsonBox[json.RawMessage]() + err := jv.UnmarshalJSON(t.b) + Expect(err).To(BeNil()) + Expect(t.j.Unbox()).To(Equal(jv.Unbox())) + } + }) + + It("driver valuer", func() { + for _, t := range samples { + v, err := t.j.Value() + Expect(err).To(BeNil()) + Expect(v).To(Equal(t.b)) + } + }) + + It("sql scaner", func() { + for _, t := range samples { + jv := types.NewJsonBox[json.RawMessage]() + err := jv.Scan(t.b) + Expect(err).To(BeNil()) + Expect(jv.Unbox()).To(Equal(t.j.Unbox())) + } + }) +}) diff --git a/pkg/types/types.go b/pkg/types/types.go index 830c56c6..e2bb9331 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -9,3 +9,9 @@ type Empty = struct{} // Fn empty argument func alias type type Fn = func() + +// Boxes Box/Unbox interface +type Boxes[T any] interface { + Box(t T) + Unbox() T +} diff --git a/pkg/types/types_suite_test.go b/pkg/types/types_suite_test.go new file mode 100644 index 00000000..18ffa976 --- /dev/null +++ b/pkg/types/types_suite_test.go @@ -0,0 +1,17 @@ +// Copyright 2023 ROC. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package types_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestTypes(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Types Suite") +}