Skip to content

Commit

Permalink
Merge pull request #108 from yejiayu/source_add_tag
Browse files Browse the repository at this point in the history
Automatic merge from submit-queue.

feat(source): add default tag and set default value

**What this PR does / why we need it**:
- Add `default` source tag. 
- All basic types of params have a default value.
**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #

**Special notes for your reviewer**:
/assign @kdada 

**Release note**:

```release-note
NONE
```
  • Loading branch information
pendoragon authored Jan 18, 2018
2 parents f2dd6ff + df876be commit a6bbb1c
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 19 deletions.
91 changes: 91 additions & 0 deletions service/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
Expand Down Expand Up @@ -309,6 +310,96 @@ func TestHomePath(t *testing.T) {
}
}

type User struct {
Username string `source:"Header,Username"`
Password string `source:"Header,Password"`
}

var defaultParamsDesc = definition.Descriptor{
Definitions: []definition.Definition{},
Consumes: []string{definition.MIMEJSON},
Produces: []string{definition.MIMEJSON},
Children: []definition.Descriptor{
{
Path: "/default",
Definitions: []definition.Definition{
{
Method: definition.Get,
Function: defaultParamsHandler,
Parameters: []definition.Parameter{
{
Source: definition.Query,
Name: "q1",
Default: "q1",
},
{
Source: definition.Query,
Name: "q2",
},
{
Source: definition.Header,
Name: "X-Tenant",
},
{
Source: definition.Header,
Name: "X-Tenant2",
},
{
Source: definition.Auto,
Name: "user",
},
},
Results: []definition.Result{
{Destination: definition.Data},
{Destination: definition.Error},
},
},
},
},
},
}

func defaultParamsHandler(ctx context.Context, q1, q2, tenant, tenant2 string, u *User) (string, error) {
if q1 == "q1" && q2 == "" && tenant == "" && tenant2 == "tenant2" && u.Username == "name" && u.Password == "pwd" {
return "match", nil
}
return "", errors.New("not match params")

}

func TestDefaultParams(t *testing.T) {
u, _ := url.Parse("/default")

req := &http.Request{
Method: "GET",
URL: u,
Header: http.Header{
"Content-Type": []string{definition.MIMEJSON},
"Accept": []string{definition.MIMEJSON},
"X-Tenant2": []string{"tenant2"},
"Username": []string{"name"},
"Password": []string{"pwd"},
},
}
builder := NewBuilder()
builder.SetModifier(FirstContextParameter())
err := builder.AddDescriptor(defaultParamsDesc)
if err != nil {
t.Fatal(err)
}
s, err := builder.Build()
if err != nil {
t.Fatal(err)
}
req = req.WithContext(context.Background())
resp := newRW()
s.ServeHTTP(resp, req)

if resp.code != 200 && resp.buf.String() != "match" {
t.Fatalf("Response code should be 200, but got: %d", resp.code)
}
}

func BenchmarkServer(b *testing.B) {
u, _ := url.Parse("/api/v1/1222/false?target1=1&target2=false")
data := []byte(`{
Expand Down
8 changes: 6 additions & 2 deletions service/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,12 @@ func (e *executor) Execute(ctx context.Context) (err error) {
if err != nil {
return writeError(ctx, e.producers, err)
}
if result == nil && p.defaultValue != nil {
result = p.defaultValue
if result == nil {
if p.defaultValue != nil {
result = p.defaultValue
} else {
result = reflect.Zero(p.targetType).Interface()
}
}
for _, operator := range p.operators {
result, err = operator.Operate(ctx, p.name, result)
Expand Down
83 changes: 73 additions & 10 deletions service/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,21 @@ func (g *PrefabParameterGenerator) Generate(ctx context.Context, vc ValueContain
// }
type AutoParameterGenerator struct{}

type autoTagParams map[paramsKey]string
type paramsKey string

const (
keyDefault paramsKey = "default"
)

func (params autoTagParams) get(key paramsKey) string {
return params[key]
}

func (params autoTagParams) set(key paramsKey, value string) {
params[key] = value
}

func (g *AutoParameterGenerator) Source() definition.Source { return definition.Auto }
func (g *AutoParameterGenerator) Validate(name string, defaultValue interface{}, target reflect.Type) error {
err := assignable(defaultValue, target)
Expand All @@ -367,15 +382,28 @@ func (g *AutoParameterGenerator) Validate(name string, defaultValue interface{},
return invalidAutoParameter.Error(target)
}
f := func(index []int, field reflect.StructField) error {
source, name, err := g.split(field.Tag.Get("source"))
source, name, params, err := g.split(field.Tag.Get("source"))
if err != nil {
return err
}
generator := ParameterGeneratorFor(definition.Source(source))
if generator == nil {
return noParameterGenerator.Error(source)
}
return generator.Validate(name, nil, field.Type)

var value interface{}
defaultValue := params.get(keyDefault)
if defaultValue != "" {
if c := ConverterFor(field.Type); c != nil {
var err error
value, err = c(context.Background(), []string{defaultValue})
if err != nil {
return err
}
}
}

return generator.Validate(name, value, field.Type)
}
if target.Kind() == reflect.Struct {
err = g.enum([]int{}, target, f)
Expand All @@ -385,15 +413,38 @@ func (g *AutoParameterGenerator) Validate(name string, defaultValue interface{},
return err
}

func (g *AutoParameterGenerator) split(tag string) (source string, name string, err error) {
func (g *AutoParameterGenerator) split(tag string) (source, name string, atp autoTagParams, err error) {
atp = make(autoTagParams)
result := strings.Split(tag, ",")
if len(result) == 1 {
return result[0], "", nil

length := len(result)

if length < 1 {
return "", "", nil, invalidFieldTag.Error(tag)
}
if len(result) != 2 {
return "", "", invalidFieldTag.Error(tag)

if length >= 1 {
source = strings.Title(strings.ToLower(strings.TrimSpace(result[0])))
}
if length >= 2 {
name = strings.TrimSpace(result[1])
}
if length >= 3 {
params := result[2:]

for _, param := range params {
keyValue := strings.Split(param, "=")
if len(keyValue) == 2 {
key := paramsKey(strings.TrimSpace(keyValue[0]))
value := strings.TrimSpace(keyValue[1])
if key == keyDefault {
atp.set(key, value)
}
}
}
}
return result[0], result[1], nil

return
}

func (g *AutoParameterGenerator) Generate(ctx context.Context, vc ValueContainer, consumers []Consumer, name string, target reflect.Type) (interface{}, error) {
Expand All @@ -415,7 +466,7 @@ func (g *AutoParameterGenerator) Generate(ctx context.Context, vc ValueContainer

func (g *AutoParameterGenerator) generate(ctx context.Context, vc ValueContainer, consumers []Consumer, value reflect.Value) error {
f := func(index []int, field reflect.StructField) error {
source, name, err := g.split(field.Tag.Get("source"))
source, name, params, err := g.split(field.Tag.Get("source"))
if err != nil {
return err
}
Expand All @@ -427,7 +478,19 @@ func (g *AutoParameterGenerator) generate(ctx context.Context, vc ValueContainer
if err != nil {
return err
}
value.FieldByIndex(index).Set(reflect.ValueOf(ins))

defaultValue := params.get(keyDefault)
if ins == nil && defaultValue != "" {
if c := ConverterFor(field.Type); c != nil {
// After passing the validation phase, here will never return an error
ins, _ = c(ctx, []string{defaultValue}) // #nosec
}
}

if ins != nil {
value.FieldByIndex(index).Set(reflect.ValueOf(ins))
}

return nil
}
return g.enum([]int{}, value.Type(), f)
Expand Down
34 changes: 27 additions & 7 deletions service/source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,13 +233,17 @@ func TestPrefabParameterGenerator(t *testing.T) {
}

type as struct {
Path string `source:"Path,test"`
Query string `source:"Query,test"`
Header string `source:"Header,test"`
Form string `source:"Form,test"`
File io.Reader `source:"File,test"`
Body *ts `source:"Body"`
Context context.Context `source:"Prefab,context"`
Hello string `source:"path, hello, default=world"`
IsDefault bool `source:"path,isDefault, default=true,test=10"`
Age int `source:"Path,age"`
Name string `source:"Path,name"`
Path string `source:"Path,test"`
Query string `source:"query,test"`
Header string `source:"header,test"`
Form string `source:"form,test"`
File io.Reader `source:"File,test"`
Body *ts `source:"Body"`
Context context.Context `source:"Prefab,context"`
}

func TestAutoParameterGenerator(t *testing.T) {
Expand All @@ -257,6 +261,8 @@ func TestAutoParameterGenerator(t *testing.T) {
}
t.Logf("%+v", result)
if r, ok := result.(*as); !ok ||
r.Hello != "world" ||
!r.IsDefault ||
r.Path != "path" ||
r.Query != "query" ||
r.Header != "header" ||
Expand All @@ -268,3 +274,17 @@ func TestAutoParameterGenerator(t *testing.T) {
t.Fatalf("BodyParameterGenerator result is not correct: %+v", result)
}
}

func TestInvalidAutoParameter(t *testing.T) {
g := &AutoParameterGenerator{}
if g.Source() != definition.Auto {
t.Fatalf("AutoParameterGenerator has a wrong source: %s", g.Source())
}
target := reflect.TypeOf(1)
err := g.Validate("test", "test", target)
if err == nil {
t.Fatal("TestInvalidAutoParameter: Validate should return an error")
}

t.Log(err)
}

0 comments on commit a6bbb1c

Please sign in to comment.