Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tonic: bind keys from gin.Context #89

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@ require (
github.com/go-playground/validator/v10 v10.19.0
github.com/google/uuid v1.6.0
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f
github.com/mattn/go-sqlite3 v1.14.22
github.com/juju/testing v0.0.0-20210302031854-2c7ee8570c07 // indirect
github.com/lib/pq v1.9.0 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pires/go-proxyproto v0.7.0
github.com/poy/onpar v1.1.2 // indirect
github.com/ziutek/mymysql v1.5.4 // indirect
golang.org/x/sys v0.17.0 // indirect
sigs.k8s.io/yaml v1.4.0
)

Expand All @@ -21,22 +28,15 @@ require (
github.com/go-sql-driver/mysql v1.5.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/juju/testing v0.0.0-20210302031854-2c7ee8570c07 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.9.0 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/poy/onpar v1.1.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/ziutek/mymysql v1.5.4 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
Expand Down
5 changes: 5 additions & 0 deletions tonic/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ func Handler(h interface{}, status int, options ...func(*Route)) gin.HandlerFunc
handleError(c, err)
return
}
// Bind context-keys
if err := bind(c, input, ContextTag, extractContext); err != nil {
handleError(c, err)
return
}
// validating query and path inputs if they have a validate tag
initValidator()
args = append(args, input)
Expand Down
25 changes: 25 additions & 0 deletions tonic/tonic.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
QueryTag = "query"
PathTag = "path"
HeaderTag = "header"
ContextTag = "context"
EnumTag = "enum"
RequiredTag = "required"
DefaultTag = "default"
Expand Down Expand Up @@ -354,6 +355,30 @@ func extractHeader(c *gin.Context, tag string) (string, []string, error) {
return name, []string{header}, nil
}

// extractContext is an extractor that operates on the gin.Context
// of a request.
func extractContext(c *gin.Context, tag string) (string, []string, error) {
name, required, defaultVal, err := parseTagKey(tag)
if err != nil {
return "", nil, err
}
context := c.GetString(name)

// XXX: deprecated, use of "default" tag is preferred
if context == "" && defaultVal != "" {
return name, []string{defaultVal}, nil
}
// XXX: deprecated, use of "validate" tag is preferred
if required && context == "" {
return "", nil, fmt.Errorf("missing header parameter: %s", name)
}

if len(context) == 0 {
return name, []string{}, nil
}
return name, []string{context}, nil
}

// Public signature does not expose "required" and "default" because
// they are deprecated in favor of the "validate" and "default" tags
func parseTagKey(tag string) (string, bool, string, error) {
Expand Down
39 changes: 39 additions & 0 deletions tonic/tonic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,31 @@ func TestMain(m *testing.M) {
tonic.SetErrorHook(errorHook)

g := gin.Default()

// for context test
g.Use(func(c *gin.Context) {
if c.FullPath() == "/context" {
if val, ok := c.GetQuery("param"); ok {
c.Set("param", val)
}
if val, ok := c.GetQuery("param-optional"); ok {
c.Set("param-optional", val)
}
if val, ok := c.GetQuery("param-optional-validated"); ok {
c.Set("param-optional-validated", val)
}
}
c.Next()
})

g.GET("/simple", tonic.Handler(simpleHandler, 200))
g.GET("/scalar", tonic.Handler(scalarHandler, 200))
g.GET("/error", tonic.Handler(errorHandler, 200))
g.GET("/path/:param", tonic.Handler(pathHandler, 200))
g.GET("/query", tonic.Handler(queryHandler, 200))
g.GET("/query-old", tonic.Handler(queryHandlerOld, 200))
g.POST("/body", tonic.Handler(bodyHandler, 200))
g.GET("/context", tonic.Handler(contextHandler, 200))

r = g

Expand Down Expand Up @@ -130,6 +148,17 @@ func TestBody(t *testing.T) {
tester.Run()
}

func TestContext(t *testing.T) {
tester := iffy.NewTester(t, r)

tester.AddCall("context", "GET", "/context?param=foo", ``).Checkers(iffy.ExpectStatus(200), expectString("param", "foo"))
tester.AddCall("context", "GET", "/context", ``).Checkers(iffy.ExpectStatus(400))
tester.AddCall("context", "GET", "/context?param=foo&param-optional=bar", ``).Checkers(iffy.ExpectStatus(200), expectString("param-optional", "bar"))
tester.AddCall("context", "GET", "/context?param=foo&param-optional-validated=foo", ``).Checkers(iffy.ExpectStatus(200), expectString("param-optional-validated", "foo"))

tester.Run()
}

func errorHandler(c *gin.Context) error {
return errors.New("error")
}
Expand Down Expand Up @@ -199,6 +228,16 @@ func bodyHandler(c *gin.Context, in *bodyIn) (*bodyIn, error) {
return in, nil
}

type ContextIn struct {
Param string `context:"param" json:"param" validate:"required"`
ParamOptional string `context:"param-optional" json:"param-optional"`
ValidatedParamOptional string `context:"param-optional-validated" json:"param-optional-validated" validate:"eq=|eq=foo|gt=10"`
}

func contextHandler(c *gin.Context, in *ContextIn) (*ContextIn, error) {
return in, nil
}

func expectEmptyBody(r *http.Response, body string, obj interface{}) error {
if len(body) != 0 {
return fmt.Errorf("Body '%s' should be empty", body)
Expand Down