You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by vi...@apache.org on 2020/09/18 14:04:53 UTC
[apisix-dashboard] 01/02: feat: add validator for generic store;
add demo error
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 <27...@qq.com>
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"}
+)