This is an automated email from the ASF dual-hosted git repository.

vinci pushed a commit to branch refactor
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git

commit dda1cb9e14ac0eefe17af1d29e0677596752d061
Author: ShiningRush <[email protected]>
AuthorDate: Fri Sep 18 21:51:52 2020 +0800

    feat: add validator for generic store; add demo error
---
 api/go.mod                                |  6 ++-
 api/go.sum                                |  8 ++++
 api/internal/core/store/store.go          | 19 +++++++--
 api/internal/core/store/store_test.go     | 71 +++++++++++++++++++++++--------
 api/internal/core/store/test_case.json    | 19 +++++++++
 api/internal/core/store/validate.go       | 49 +++++++++++++++++++++
 api/internal/core/store/validate_test.go  | 51 ++++++++++++++++++++++
 api/internal/core/store/validator_mock.go | 24 +++++++++++
 api/internal/utils/consts/error.go        | 12 ++++++
 9 files changed, 237 insertions(+), 22 deletions(-)

diff --git a/api/go.mod b/api/go.mod
index 9d230c1..af5cb74 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -15,16 +15,18 @@ require (
        github.com/gogo/protobuf v1.3.1 // indirect
        github.com/google/uuid v1.1.2 // indirect
        github.com/jinzhu/gorm v1.9.12
+       github.com/magiconair/properties v1.8.1
        github.com/satori/go.uuid v1.2.0
-       github.com/shiningrush/droplet v0.1.0
+       github.com/shiningrush/droplet v0.1.1
        github.com/shiningrush/droplet/wrapper/gin v0.1.0
        github.com/sirupsen/logrus v1.6.0
        github.com/spf13/viper v1.7.1
        github.com/steinfletcher/apitest v1.4.10 // indirect
        github.com/stretchr/testify v1.6.1
        github.com/tidwall/gjson v1.6.0
+       github.com/xeipuuv/gojsonschema v1.2.0
        go.etcd.io/etcd v3.3.25+incompatible
-       go.uber.org/zap v1.16.0 // indirect
+       go.uber.org/zap v1.16.0
        golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
        golang.org/x/sys v0.0.0-20200915084602-288bc346aa39 // indirect
        golang.org/x/text v0.3.3 // indirect
diff --git a/api/go.sum b/api/go.sum
index e99f700..7f9aa90 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -276,6 +276,8 @@ github.com/shiningrush/droplet 
v0.0.0-20191118073048-00b06fe19ce4 h1:p2mP/ZZegqn
 github.com/shiningrush/droplet v0.0.0-20191118073048-00b06fe19ce4/go.mod 
h1:E/th13n/wtPi+Cj2f0hAAEFeT3gb5xsS6Ob4WRrdxdM=
 github.com/shiningrush/droplet v0.1.0 
h1:Lk/nzfouI8Xqv9VtzNZZISwTVxWeXmEd9IlgIZwU9IA=
 github.com/shiningrush/droplet v0.1.0/go.mod 
h1:akW2vIeamvMD6zj6wIBfzYn6StGXBxwlW3gA+hcHu5M=
+github.com/shiningrush/droplet v0.1.1 
h1:x+69JP60jzq6ROmsDooNhVSX8jhwFEmjBnryVvCHgjc=
+github.com/shiningrush/droplet v0.1.1/go.mod 
h1:akW2vIeamvMD6zj6wIBfzYn6StGXBxwlW3gA+hcHu5M=
 github.com/shiningrush/droplet/wrapper/gin v0.1.0 
h1:eKUtuInaz8BH9dwjDnpdnP29iH7bhaB0NIOF9tL7nFM=
 github.com/shiningrush/droplet/wrapper/gin v0.1.0/go.mod 
h1:ZJu+sCRrVXn5Pg618c1KK3Ob2UiXGuPM1ROx5uMM9YQ=
 github.com/sirupsen/logrus v1.2.0/go.mod 
h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -324,6 +326,12 @@ github.com/ugorji/go v1.1.7 
h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
 github.com/ugorji/go v1.1.7/go.mod 
h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
 github.com/ugorji/go/codec v1.1.7 
h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
 github.com/ugorji/go/codec v1.1.7/go.mod 
h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f 
h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod 
h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 
h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod 
h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0 
h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod 
h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 
h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod 
h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
diff --git a/api/internal/core/store/store.go b/api/internal/core/store/store.go
index f9d6a47..2d199d7 100644
--- a/api/internal/core/store/store.go
+++ b/api/internal/core/store/store.go
@@ -20,9 +20,10 @@ type GenericStore struct {
 }
 
 type GenericStoreOption struct {
-       BasePath string
-       ObjType  reflect.Type
-       KeyFunc  func(obj interface{}) string
+       BasePath  string
+       ObjType   reflect.Type
+       KeyFunc   func(obj interface{}) string
+       Validator Validator
 }
 
 func NewGenericStore(opt GenericStoreOption) (*GenericStore, error) {
@@ -146,6 +147,12 @@ func (s *GenericStore) List(input ListInput) (*ListOutput, 
error) {
 }
 
 func (s *GenericStore) Create(ctx context.Context, obj interface{}) error {
+       if s.opt.Validator != nil {
+               if err := s.opt.Validator.Validate(obj); err != nil {
+                       return err
+               }
+       }
+
        key := s.opt.KeyFunc(obj)
        if key == "" {
                return fmt.Errorf("key is required")
@@ -167,6 +174,12 @@ func (s *GenericStore) Create(ctx context.Context, obj 
interface{}) error {
 }
 
 func (s *GenericStore) Update(ctx context.Context, obj interface{}) error {
+       if s.opt.Validator != nil {
+               if err := s.opt.Validator.Validate(obj); err != nil {
+                       return err
+               }
+       }
+
        key := s.opt.KeyFunc(obj)
        if key == "" {
                return fmt.Errorf("key is required")
diff --git a/api/internal/core/store/store_test.go 
b/api/internal/core/store/store_test.go
index 5d295a1..4895258 100644
--- a/api/internal/core/store/store_test.go
+++ b/api/internal/core/store/store_test.go
@@ -399,13 +399,14 @@ func TestGenericStore_List(t *testing.T) {
 
 func TestGenericStore_Create(t *testing.T) {
        tests := []struct {
-               caseDesc  string
-               giveStore *GenericStore
-               giveObj   *TestStruct
-               giveErr   error
-               wantKey   string
-               wantStr   string
-               wantErr   error
+               caseDesc        string
+               giveStore       *GenericStore
+               giveObj         *TestStruct
+               giveErr         error
+               giveValidateErr error
+               wantKey         string
+               wantStr         string
+               wantErr         error
        }{
                {
                        caseDesc: "sanity",
@@ -462,10 +463,25 @@ func TestGenericStore_Create(t *testing.T) {
                        },
                        wantErr: fmt.Errorf("key: test1 is conflicted"),
                },
+               {
+                       caseDesc: "validate failed",
+                       giveObj: &TestStruct{
+                               Field1: "test1",
+                               Field2: "test2",
+                       },
+                       giveStore: &GenericStore{
+                               cache: map[string]interface{}{
+                                       "test1": struct{}{},
+                               },
+                               opt: GenericStoreOption{},
+                       },
+                       giveValidateErr: fmt.Errorf("validate failed"),
+                       wantErr:         fmt.Errorf("validate failed"),
+               },
        }
 
        for _, tc := range tests {
-               createCalled := false
+               createCalled, validateCalled := false, false
                mStorage := &storage.MockInterface{}
                mStorage.On("Create", mock.Anything, mock.Anything, 
mock.Anything).Run(func(args mock.Arguments) {
                        createCalled = true
@@ -473,22 +489,34 @@ func TestGenericStore_Create(t *testing.T) {
                        assert.Equal(t, tc.wantStr, args[2], tc.caseDesc)
                }).Return(tc.giveErr)
 
+               mValidator := &MockValidator{}
+               mValidator.On("Validate", mock.Anything).Run(func(args 
mock.Arguments) {
+                       validateCalled = true
+                       assert.Equal(t, tc.giveObj, args.Get(0), tc.caseDesc)
+               }).Return(tc.giveValidateErr)
+
                tc.giveStore.Stg = mStorage
+               tc.giveStore.opt.Validator = mValidator
                err := tc.giveStore.Create(context.TODO(), tc.giveObj)
+               assert.True(t, validateCalled, tc.caseDesc)
+               if err != nil {
+                       assert.Equal(t, tc.wantErr, err, tc.caseDesc)
+                       continue
+               }
                assert.True(t, createCalled, tc.caseDesc)
-               assert.Equal(t, tc.wantErr, err, tc.caseDesc)
        }
 }
 
 func TestGenericStore_Update(t *testing.T) {
        tests := []struct {
-               caseDesc  string
-               giveStore *GenericStore
-               giveObj   *TestStruct
-               giveErr   error
-               wantKey   string
-               wantStr   string
-               wantErr   error
+               caseDesc        string
+               giveStore       *GenericStore
+               giveObj         *TestStruct
+               giveErr         error
+               giveValidateErr error
+               wantKey         string
+               wantStr         string
+               wantErr         error
        }{
                {
                        caseDesc: "sanity",
@@ -554,7 +582,7 @@ func TestGenericStore_Update(t *testing.T) {
        }
 
        for _, tc := range tests {
-               createCalled := false
+               createCalled, validateCalled := false, false
                mStorage := &storage.MockInterface{}
                mStorage.On("Update", mock.Anything, mock.Anything, 
mock.Anything).Run(func(args mock.Arguments) {
                        createCalled = true
@@ -562,8 +590,17 @@ func TestGenericStore_Update(t *testing.T) {
                        assert.Equal(t, tc.wantStr, args[2], tc.caseDesc)
                }).Return(tc.giveErr)
 
+               mValidator := &MockValidator{}
+               mValidator.On("Validate", mock.Anything).Run(func(args 
mock.Arguments) {
+                       validateCalled = true
+                       assert.Equal(t, tc.giveObj, args.Get(0), tc.caseDesc)
+               }).Return(tc.giveValidateErr)
+
                tc.giveStore.Stg = mStorage
+               tc.giveStore.opt.Validator = mValidator
+
                err := tc.giveStore.Update(context.TODO(), tc.giveObj)
+               assert.True(t, validateCalled, tc.caseDesc)
                if err != nil {
                        assert.Equal(t, tc.wantErr, err, tc.caseDesc)
                        continue
diff --git a/api/internal/core/store/test_case.json 
b/api/internal/core/store/test_case.json
new file mode 100644
index 0000000..77f524b
--- /dev/null
+++ b/api/internal/core/store/test_case.json
@@ -0,0 +1,19 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#";,
+  "type": "object",
+  "properties": {
+    "name":  {
+      "type": "string",
+      "minLength": 10
+    },
+    "email": {
+      "type": "string",
+      "maxLength": 10
+    },
+    "age": {
+      "type": "integer",
+      "minimum": 0
+    }
+  },
+  "additionalProperties": false
+}
diff --git a/api/internal/core/store/validate.go 
b/api/internal/core/store/validate.go
new file mode 100644
index 0000000..1ee0960
--- /dev/null
+++ b/api/internal/core/store/validate.go
@@ -0,0 +1,49 @@
+package store
+
+import (
+       "errors"
+       "fmt"
+       "github.com/xeipuuv/gojsonschema"
+       "go.uber.org/zap/buffer"
+       "io/ioutil"
+)
+
+type Validator interface {
+       Validate(obj interface{}) error
+}
+type JsonSchemaValidator struct {
+       schema *gojsonschema.Schema
+}
+
+func NewJsonSchemaValidator(jsonPath string) (Validator, error) {
+       bs, err := ioutil.ReadFile(jsonPath)
+       if err != nil {
+               return nil, fmt.Errorf("get abs path failed: %w", err)
+       }
+       s, err := 
gojsonschema.NewSchema(gojsonschema.NewStringLoader(string(bs)))
+       if err != nil {
+               return nil, fmt.Errorf("new schema failed: %w", err)
+       }
+       return &JsonSchemaValidator{
+               schema: s,
+       }, nil
+}
+
+func (v *JsonSchemaValidator) Validate(obj interface{}) error {
+       ret, err := v.schema.Validate(gojsonschema.NewGoLoader(obj))
+       if err != nil {
+               return fmt.Errorf("validate failed: %w", err)
+       }
+
+       if !ret.Valid() {
+               errString := buffer.Buffer{}
+               for i, vErr := range ret.Errors() {
+                       if i != 0 {
+                               errString.AppendString("\n")
+                       }
+                       errString.AppendString(vErr.String())
+               }
+               return errors.New(errString.String())
+       }
+       return nil
+}
diff --git a/api/internal/core/store/validate_test.go 
b/api/internal/core/store/validate_test.go
new file mode 100644
index 0000000..f7d1ac9
--- /dev/null
+++ b/api/internal/core/store/validate_test.go
@@ -0,0 +1,51 @@
+package store
+
+import (
+       "fmt"
+       "github.com/stretchr/testify/assert"
+       "testing"
+)
+
+type TestOjb struct {
+       Name  string `json:"name"`
+       Email string `json:"email"`
+       Age   int    `json:"age"`
+}
+
+func TestJsonSchemaValidator_Validate(t *testing.T) {
+       tests := []struct {
+               givePath        string
+               giveObj         interface{}
+               wantNewErr      error
+               wantValidateErr []error
+       }{
+               {
+                       givePath: "./test_case.json",
+                       giveObj: TestOjb{
+                               Name:  "lessName",
+                               Email: "too long name greater than 10",
+                               Age:   12,
+                       },
+                       wantValidateErr: []error{
+                               fmt.Errorf("name: String length must be greater 
than or equal to 10\nemail: String length must be less than or equal to 10"),
+                               fmt.Errorf("email: String length must be less 
than or equal to 10\nname: String length must be greater than or equal to 10"),
+                       },
+               },
+       }
+
+       for _, tc := range tests {
+               v, err := NewJsonSchemaValidator(tc.givePath)
+               if err != nil {
+                       assert.Equal(t, tc.wantNewErr, err)
+                       continue
+               }
+               err = v.Validate(tc.giveObj)
+               ret := false
+               for _, wantErr := range tc.wantValidateErr {
+                       if wantErr.Error() == err.Error() {
+                               ret = true
+                       }
+               }
+               assert.True(t, ret)
+       }
+}
diff --git a/api/internal/core/store/validator_mock.go 
b/api/internal/core/store/validator_mock.go
new file mode 100644
index 0000000..643ba78
--- /dev/null
+++ b/api/internal/core/store/validator_mock.go
@@ -0,0 +1,24 @@
+// Code generated by mockery v1.0.0. DO NOT EDIT.
+
+package store
+
+import mock "github.com/stretchr/testify/mock"
+
+// MockValidator is an autogenerated mock type for the Validator type
+type MockValidator struct {
+       mock.Mock
+}
+
+// Validate provides a mock function with given fields: obj
+func (_m *MockValidator) Validate(obj interface{}) error {
+       ret := _m.Called(obj)
+
+       var r0 error
+       if rf, ok := ret.Get(0).(func(interface{}) error); ok {
+               r0 = rf(obj)
+       } else {
+               r0 = ret.Error(0)
+       }
+
+       return r0
+}
diff --git a/api/internal/utils/consts/error.go 
b/api/internal/utils/consts/error.go
new file mode 100644
index 0000000..7abee58
--- /dev/null
+++ b/api/internal/utils/consts/error.go
@@ -0,0 +1,12 @@
+package consts
+
+import "github.com/shiningrush/droplet/data"
+
+const (
+       ErrCodeDemoBiz = 20000
+)
+
+var (
+  // base error please refer to github.com/shiningrush/droplet/data, such as 
data.ErrNotFound, data.ErrConflicted
+       ErrDemoBiz = data.BaseError{Code: ErrCodeDemoBiz, Message: "this is 
just a demo error"}
+)

Reply via email to