From 29c3d9e1582fb6e97b3eef55ba780b952be45bd3 Mon Sep 17 00:00:00 2001 From: wwwx Date: Wed, 28 Dec 2022 16:42:14 +0800 Subject: [PATCH] tonic: bind keys from gin.Context --- go.mod | 16 ++++++++-------- go.sum | 4 ++-- tonic/handler.go | 5 +++++ tonic/tonic.go | 25 +++++++++++++++++++++++++ tonic/tonic_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 2e5e1f1..5afb8f6 100644 --- a/go.mod +++ b/go.mod @@ -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 ) @@ -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 diff --git a/go.sum b/go.sum index 2839d8f..f142dec 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/tonic/handler.go b/tonic/handler.go index 1009055..15c542f 100644 --- a/tonic/handler.go +++ b/tonic/handler.go @@ -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) diff --git a/tonic/tonic.go b/tonic/tonic.go index ce30e18..feeb38f 100644 --- a/tonic/tonic.go +++ b/tonic/tonic.go @@ -27,6 +27,7 @@ const ( QueryTag = "query" PathTag = "path" HeaderTag = "header" + ContextTag = "context" EnumTag = "enum" RequiredTag = "required" DefaultTag = "default" @@ -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) { diff --git a/tonic/tonic_test.go b/tonic/tonic_test.go index 694c159..53d14d5 100644 --- a/tonic/tonic_test.go +++ b/tonic/tonic_test.go @@ -28,6 +28,23 @@ 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)) @@ -35,6 +52,7 @@ func TestMain(m *testing.M) { 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 @@ -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¶m-optional=bar", ``).Checkers(iffy.ExpectStatus(200), expectString("param-optional", "bar")) + tester.AddCall("context", "GET", "/context?param=foo¶m-optional-validated=foo", ``).Checkers(iffy.ExpectStatus(200), expectString("param-optional-validated", "foo")) + + tester.Run() +} + func errorHandler(c *gin.Context) error { return errors.New("error") } @@ -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)