From 46f3c8e0e925cc2be527adc66c8934265b716205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Samin?= Date: Fri, 4 Aug 2023 10:56:27 +0200 Subject: [PATCH] feat: yaml binding (#96) --- go.mod | 1 + go.sum | 2 ++ tonic/tonic.go | 43 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index dfc5976..e92cfbe 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/pires/go-proxyproto v0.6.0 github.com/poy/onpar v1.1.2 // indirect github.com/ziutek/mymysql v1.5.4 // indirect + sigs.k8s.io/yaml v1.3.0 ) go 1.13 diff --git a/go.sum b/go.sum index b11291e..0707411 100644 --- a/go.sum +++ b/go.sum @@ -274,3 +274,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= launchpad.net/xmlpath v0.0.0-20130614043138-000000000004/go.mod h1:vqyExLOM3qBx7mvYRkoxjSCF945s0mbe7YynlKYXtsA= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/tonic/tonic.go b/tonic/tonic.go index 8bc9290..ce30e18 100644 --- a/tonic/tonic.go +++ b/tonic/tonic.go @@ -1,6 +1,7 @@ package tonic import ( + "bytes" "encoding" "errors" "fmt" @@ -15,6 +16,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" validator "github.com/go-playground/validator/v10" + "sigs.k8s.io/yaml" // sigs.k8s.io/yaml is the alternative to the unmaintained lib github.com/ghodss/yaml. cf https://github.com/ghodss/yaml/issues/80 ) // DefaultMaxBodyBytes is the maximum allowed size of a request body in bytes. @@ -96,8 +98,15 @@ func DefaultBindingHookMaxBodyBytes(maxBodyBytes int64) BindHook { if c.Request.ContentLength == 0 || c.Request.Method == http.MethodGet { return nil } - if err := c.ShouldBindWith(i, binding.JSON); err != nil && err != io.EOF { - return fmt.Errorf("error parsing request body: %s", err.Error()) + switch c.Request.Header.Get("Content-Type") { + case "text/x-yaml", "text/yaml", "text/yml", "application/x-yaml", "application/x-yml", "application/yaml", "application/yml": + if err := c.ShouldBindWith(i, yamlBinding{}); err != nil && err != io.EOF { + return fmt.Errorf("error parsing request body: %s", err.Error()) + } + default: + if err := c.ShouldBindWith(i, binding.JSON); err != nil && err != io.EOF { + return fmt.Errorf("error parsing request body: %s", err.Error()) + } } return nil } @@ -443,3 +452,33 @@ func bindStringValue(s string, v reflect.Value) error { } return nil } + +// yamlBinding is an implementation of gin's binding.Binding +// we don't use official gin's yamlBinding because we prefer to use github.com/ghodss/yaml +type yamlBinding struct{} + +func (yamlBinding) Name() string { + return "yaml" +} + +func (yamlBinding) Bind(req *http.Request, obj interface{}) error { + return decodeYAML(req.Body, obj) +} + +func (yamlBinding) BindBody(body []byte, obj interface{}) error { + return decodeYAML(bytes.NewReader(body), obj) +} + +func decodeYAML(r io.Reader, obj interface{}) error { + btes, err := io.ReadAll(r) + if err != nil { + return err + } + if err := yaml.Unmarshal(btes, &obj); err != nil { + return err + } + if binding.Validator == nil { + return nil + } + return binding.Validator.ValidateStruct(obj) +}