mirror of
https://github.com/ggicci/httpin.git
synced 2024-11-24 08:32:45 +02:00
feat: allow registering custom body decoders
This commit is contained in:
parent
dd0ebe0d70
commit
8522ea1250
78
body.go
78
body.go
@ -7,24 +7,49 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type JSONBody struct{}
|
||||
type XMLBody struct{}
|
||||
|
||||
var (
|
||||
bodyTypeAnnotationJSON = reflect.TypeOf(JSONBody{})
|
||||
bodyTypeAnnotationXML = reflect.TypeOf(XMLBody{})
|
||||
)
|
||||
|
||||
const (
|
||||
bodyTypeJSON = "json"
|
||||
bodyTypeXML = "xml"
|
||||
)
|
||||
|
||||
type (
|
||||
JSONBody struct{}
|
||||
XMLBody struct{}
|
||||
|
||||
BodyDecoder interface {
|
||||
Decode(src io.Reader, dst interface{}) error
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
bodyTypeAnnotationJSON = reflect.TypeOf(JSONBody{})
|
||||
bodyTypeAnnotationXML = reflect.TypeOf(XMLBody{})
|
||||
|
||||
bodyDecoders = map[string]BodyDecoder{
|
||||
bodyTypeJSON: &defaultJSONBodyDecoder{},
|
||||
bodyTypeXML: &defaultXMLBodyDecoder{},
|
||||
}
|
||||
)
|
||||
|
||||
func RegisterBodyDecoder(bodyType string, decoder BodyDecoder) {
|
||||
if _, ok := bodyDecoders[bodyType]; ok {
|
||||
panic(fmt.Errorf("httpin: %w: %q", ErrDuplicateBodyDecoder, bodyType))
|
||||
}
|
||||
ReplaceBodyDecoder(bodyType, decoder)
|
||||
}
|
||||
|
||||
func ReplaceBodyDecoder(bodyType string, decoder BodyDecoder) {
|
||||
if bodyType == "" {
|
||||
panic("httpin: body type cannot be empty")
|
||||
}
|
||||
bodyDecoders[bodyType] = decoder
|
||||
}
|
||||
|
||||
func bodyDirectiveNormalizer(dir *Directive) error {
|
||||
if len(dir.Argv) == 0 {
|
||||
dir.Argv = []string{bodyTypeJSON}
|
||||
@ -32,10 +57,9 @@ func bodyDirectiveNormalizer(dir *Directive) error {
|
||||
dir.Argv[0] = strings.ToLower(dir.Argv[0])
|
||||
|
||||
var bodyType = dir.Argv[0]
|
||||
if bodyType != bodyTypeJSON && bodyType != bodyTypeXML {
|
||||
if _, ok := bodyDecoders[bodyType]; !ok {
|
||||
return fmt.Errorf("%w: %q", ErrUnknownBodyType, bodyType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -46,20 +70,22 @@ func bodyTypeString(bodyType reflect.Type) string {
|
||||
case bodyTypeAnnotationXML:
|
||||
return bodyTypeXML
|
||||
default:
|
||||
panic(ErrUnknownBodyType)
|
||||
panic(fmt.Errorf("httpin: %w: %q", ErrUnknownBodyType, bodyType))
|
||||
}
|
||||
}
|
||||
|
||||
func bodyDecoder(ctx *DirectiveContext) error {
|
||||
var err = ErrUnknownBodyType
|
||||
switch ctx.Argv[0] { // body type
|
||||
case bodyTypeJSON:
|
||||
err = decodeJSONBody(ctx.Request, ctx.Value)
|
||||
case bodyTypeXML:
|
||||
err = decodeXMLBody(ctx.Request, ctx.Value)
|
||||
var (
|
||||
bodyType = ctx.Argv[0]
|
||||
decoder = bodyDecoders[bodyType]
|
||||
)
|
||||
|
||||
if decoder == nil {
|
||||
return ErrUnknownBodyType
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
obj := ctx.Value.Interface()
|
||||
if err := decoder.Decode(ctx.Request.Body, &obj); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -67,12 +93,14 @@ func bodyDecoder(ctx *DirectiveContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeJSONBody(req *http.Request, rv reflect.Value) error {
|
||||
obj := rv.Interface()
|
||||
return json.NewDecoder(req.Body).Decode(&obj)
|
||||
type defaultJSONBodyDecoder struct{}
|
||||
|
||||
func (de *defaultJSONBodyDecoder) Decode(src io.Reader, dst interface{}) error {
|
||||
return json.NewDecoder(src).Decode(dst)
|
||||
}
|
||||
|
||||
func decodeXMLBody(req *http.Request, rv reflect.Value) error {
|
||||
obj := rv.Interface()
|
||||
return xml.NewDecoder(req.Body).Decode(&obj)
|
||||
type defaultXMLBodyDecoder struct{}
|
||||
|
||||
func (de *defaultXMLBodyDecoder) Decode(src io.Reader, dst interface{}) error {
|
||||
return xml.NewDecoder(src).Decode(dst)
|
||||
}
|
||||
|
48
body_test.go
48
body_test.go
@ -215,3 +215,51 @@ func Test_bodyTypeString(t *testing.T) {
|
||||
}, ShouldPanic)
|
||||
})
|
||||
}
|
||||
|
||||
type yamlBodyDecoder struct{}
|
||||
|
||||
func (de *yamlBodyDecoder) Decode(src io.Reader, dst interface{}) error {
|
||||
// for test only
|
||||
(*(*(dst.(*interface{}))).(*map[string]interface{})) = map[string]interface{}{
|
||||
"version": 3,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type YamlInput struct {
|
||||
Body map[string]interface{} `in:"body=yaml"`
|
||||
}
|
||||
|
||||
type ThingWithUnknownBodyDecoder struct {
|
||||
Body map[string]interface{} `in:"body=yml"`
|
||||
}
|
||||
|
||||
func TestCustomBodyDecoder(t *testing.T) {
|
||||
Convey("body: register new body decoder", t, func() {
|
||||
So(func() { RegisterBodyDecoder("yaml", &yamlBodyDecoder{}) }, ShouldNotPanic)
|
||||
|
||||
resolver, err := buildResolverTree(reflect.TypeOf(YamlInput{}))
|
||||
So(err, ShouldBeNil)
|
||||
So(resolver, ShouldNotBeNil)
|
||||
r, _ := http.NewRequest("GET", "https://example.com", nil)
|
||||
|
||||
r.Body = io.NopCloser(strings.NewReader(`version: "3"`))
|
||||
res, err := resolver.resolve(r)
|
||||
So(err, ShouldBeNil)
|
||||
So(res.Interface(), ShouldResemble, &YamlInput{
|
||||
Body: map[string]interface{}{
|
||||
"version": 3,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
Convey("body: panic on duplicate body decoder", t, func() {
|
||||
So(func() { RegisterBodyDecoder("json", &yamlBodyDecoder{}) }, ShouldPanic)
|
||||
So(func() { RegisterBodyDecoder("", &yamlBodyDecoder{}) }, ShouldPanic)
|
||||
})
|
||||
|
||||
Convey("body: unknown body decoder", t, func() {
|
||||
_, err := buildResolverTree(reflect.TypeOf(ThingWithUnknownBodyDecoder{}))
|
||||
So(errors.Is(err, ErrUnknownBodyType), ShouldBeTrue)
|
||||
})
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ func isTypeDecoder(decoder interface{}) bool {
|
||||
// RegisterTypeDecoder registers a specific type decoder. Panics on conflicts.
|
||||
func RegisterTypeDecoder(typ reflect.Type, decoder interface{}) {
|
||||
if _, ok := decoders[typ]; ok {
|
||||
panic(fmt.Errorf("%w: %q", ErrDuplicateTypeDecoder, typ))
|
||||
panic(fmt.Errorf("httpin: %w: %q", ErrDuplicateTypeDecoder, typ))
|
||||
}
|
||||
|
||||
ReplaceTypeDecoder(typ, decoder)
|
||||
@ -43,11 +43,11 @@ func RegisterTypeDecoder(typ reflect.Type, decoder interface{}) {
|
||||
// ReplaceTypeDecoder replaces a specific type decoder.
|
||||
func ReplaceTypeDecoder(typ reflect.Type, decoder interface{}) {
|
||||
if decoder == nil {
|
||||
panic(fmt.Errorf("%w: %q", ErrNilTypeDecoder, typ))
|
||||
panic(fmt.Errorf("httpin: %w: %q", ErrNilTypeDecoder, typ))
|
||||
}
|
||||
|
||||
if !isTypeDecoder(decoder) {
|
||||
panic(fmt.Errorf("%w: %q", ErrInvalidTypeDecoder, typ))
|
||||
panic(fmt.Errorf("httpin: %w: %q", ErrInvalidTypeDecoder, typ))
|
||||
}
|
||||
|
||||
decoders[typ] = decoder
|
||||
|
@ -41,7 +41,7 @@ type DirectiveNormalizer interface {
|
||||
// taken or nil executor.
|
||||
func RegisterDirectiveExecutor(name string, exe DirectiveExecutor, norm DirectiveNormalizer) {
|
||||
if _, ok := executors[name]; ok {
|
||||
panic(fmt.Errorf("%w: %q", ErrDuplicateExecutor, name))
|
||||
panic(fmt.Errorf("httpin: %w: %q", ErrDuplicateExecutor, name))
|
||||
}
|
||||
ReplaceDirectiveExecutor(name, exe, norm)
|
||||
}
|
||||
@ -50,7 +50,7 @@ func RegisterDirectiveExecutor(name string, exe DirectiveExecutor, norm Directiv
|
||||
// on duplicate names.
|
||||
func ReplaceDirectiveExecutor(name string, exe DirectiveExecutor, norm DirectiveNormalizer) {
|
||||
if exe == nil {
|
||||
panic(fmt.Errorf("%w: %q", ErrNilExecutor, name))
|
||||
panic(fmt.Errorf("httpin: %w: %q", ErrNilExecutor, name))
|
||||
}
|
||||
executors[name] = exe
|
||||
normalizers[name] = norm
|
||||
|
@ -20,6 +20,7 @@ var (
|
||||
ErrNilErrorHandler = errors.New("nil error handler")
|
||||
ErrMaxMemoryTooSmall = errors.New("max memory too small")
|
||||
ErrNilFile = errors.New("nil file")
|
||||
ErrDuplicateBodyDecoder = errors.New("duplicate body decoder")
|
||||
)
|
||||
|
||||
type UnsupportedTypeError struct {
|
||||
|
@ -44,7 +44,7 @@ func NewInput(inputStruct interface{}, opts ...Option) middleware {
|
||||
|
||||
func ReplaceDefaultErrorHandler(custom ErrorHandler) {
|
||||
if custom == nil {
|
||||
panic(fmt.Errorf("httpin: %v", ErrNilErrorHandler))
|
||||
panic(fmt.Errorf("httpin: %w", ErrNilErrorHandler))
|
||||
}
|
||||
globalCustomErrorHandler = custom
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user