mirror of
https://github.com/ggicci/httpin.git
synced 2024-11-24 08:32:45 +02:00
feat: add json & xml body decoder
This commit is contained in:
parent
f634165786
commit
2526bcff53
26
body.go
26
body.go
@ -1,6 +1,26 @@
|
||||
package httpin
|
||||
|
||||
func bodyDecoder(ctx *DirectiveContext) error {
|
||||
// TODO(ggicci): implement this
|
||||
return nil
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type JSONBody struct{}
|
||||
type XMLBody struct{}
|
||||
|
||||
var (
|
||||
typeJSONBody = reflect.TypeOf(JSONBody{})
|
||||
typeXMLBody = reflect.TypeOf(XMLBody{})
|
||||
)
|
||||
|
||||
func decodeJSONBody(req *http.Request, rv reflect.Value) error {
|
||||
obj := rv.Interface()
|
||||
return json.NewDecoder(req.Body).Decode(&obj)
|
||||
}
|
||||
|
||||
func decodeXMLBody(req *http.Request, rv reflect.Value) error {
|
||||
obj := rv.Interface()
|
||||
return xml.NewDecoder(req.Body).Decode(&obj)
|
||||
}
|
||||
|
106
body_test.go
106
body_test.go
@ -1 +1,107 @@
|
||||
package httpin
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
type LanguageLevel struct {
|
||||
Language string `json:"lang" xml:"lang"`
|
||||
Level int `json:"level" xml:"level"`
|
||||
}
|
||||
|
||||
type BodyPayload struct {
|
||||
Name string `json:"name" xml:"name"`
|
||||
Age int `json:"age" xml:"age"`
|
||||
IsNative bool `json:"is_native" xml:"is_native"`
|
||||
Hobbies []string `json:"hobbies" xml:"hobbies"`
|
||||
Languages []*LanguageLevel `json:"languages" xml:"languages"`
|
||||
}
|
||||
|
||||
type JSONBodyPayload struct {
|
||||
JSONBody
|
||||
BodyPayload
|
||||
}
|
||||
|
||||
type XMLBodyPayload struct {
|
||||
XMLBody
|
||||
BodyPayload
|
||||
}
|
||||
|
||||
func TestJSONBody(t *testing.T) {
|
||||
Convey("json: parse HTTP body in JSON", t, func() {
|
||||
resolver, err := buildResolverTree(reflect.TypeOf(JSONBodyPayload{}))
|
||||
So(err, ShouldBeNil)
|
||||
So(resolver, ShouldNotBeNil)
|
||||
r, _ := http.NewRequest("GET", "https://example.com", nil)
|
||||
|
||||
r.Body = io.NopCloser(strings.NewReader(`{
|
||||
"name": "Elia",
|
||||
"is_native": false,
|
||||
"age": 14,
|
||||
"hobbies": ["Gaming", "Drawing"],
|
||||
"languages": [
|
||||
{"lang": "English", "level": 10},
|
||||
{"lang": "Japanese", "level": 3}
|
||||
]
|
||||
}`))
|
||||
res, err := resolver.resolve(r)
|
||||
So(err, ShouldBeNil)
|
||||
So(res.Interface(), ShouldResemble, &JSONBodyPayload{
|
||||
BodyPayload: BodyPayload{
|
||||
Name: "Elia",
|
||||
Age: 14,
|
||||
IsNative: false,
|
||||
Hobbies: []string{"Gaming", "Drawing"},
|
||||
Languages: []*LanguageLevel{
|
||||
{"English", 10},
|
||||
{"Japanese", 3},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestXMLBody(t *testing.T) {
|
||||
Convey("json: parse HTTP body in XML", t, func() {
|
||||
resolver, err := buildResolverTree(reflect.TypeOf(XMLBodyPayload{}))
|
||||
So(err, ShouldBeNil)
|
||||
So(resolver, ShouldNotBeNil)
|
||||
r, _ := http.NewRequest("GET", "https://example.com", nil)
|
||||
|
||||
r.Body = io.NopCloser(strings.NewReader(`<BodyPayload>
|
||||
<name>Elia</name>
|
||||
<age>14</age>
|
||||
<is_native>false</is_native>
|
||||
<hobbies>Gaming</hobbies>
|
||||
<hobbies>Drawing</hobbies>
|
||||
<languages>
|
||||
<lang>English</lang>
|
||||
<level>10</level>
|
||||
</languages>
|
||||
<languages>
|
||||
<lang>Japanese</lang>
|
||||
<level>3</level>
|
||||
</languages>
|
||||
</BodyPayload>`))
|
||||
res, err := resolver.resolve(r)
|
||||
So(err, ShouldBeNil)
|
||||
So(res.Interface(), ShouldResemble, &XMLBodyPayload{
|
||||
BodyPayload: BodyPayload{
|
||||
Name: "Elia",
|
||||
Age: 14,
|
||||
IsNative: false,
|
||||
Hobbies: []string{"Gaming", "Drawing"},
|
||||
Languages: []*LanguageLevel{
|
||||
{"English", 10},
|
||||
{"Japanese", 3},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ func init() {
|
||||
|
||||
RegisterDirectiveExecutor("form", DirectiveExecutorFunc(formValueExtractor))
|
||||
RegisterDirectiveExecutor("header", DirectiveExecutorFunc(headerValueExtractor))
|
||||
RegisterDirectiveExecutor("body", DirectiveExecutorFunc(bodyDecoder))
|
||||
RegisterDirectiveExecutor("required", DirectiveExecutorFunc(required))
|
||||
}
|
||||
|
||||
|
@ -59,8 +59,6 @@ type ProductQuery struct {
|
||||
SortDesc []bool `in:"form=sort_desc"`
|
||||
Pagination
|
||||
Authorization
|
||||
|
||||
Patch map[string]interface{} `in:"body=json"`
|
||||
}
|
||||
|
||||
type ObjectID struct {
|
||||
|
55
resolver.go
55
resolver.go
@ -17,10 +17,42 @@ type fieldResolver struct {
|
||||
Fields []*fieldResolver
|
||||
}
|
||||
|
||||
func (r *fieldResolver) isBodyResolverAnnotation() bool {
|
||||
return r.Type == typeJSONBody || r.Type == typeXMLBody
|
||||
}
|
||||
|
||||
func (r *fieldResolver) isRoot() bool {
|
||||
return r.Field.Name == ""
|
||||
}
|
||||
|
||||
func (r *fieldResolver) resolve(req *http.Request) (reflect.Value, error) {
|
||||
rv := reflect.New(r.Type)
|
||||
|
||||
// Execute directives.
|
||||
// Resolve HTTP request body if necessary.
|
||||
if r.isRoot() {
|
||||
// Check if there's an annotation field.
|
||||
var bodyType reflect.Type
|
||||
for _, field := range r.Fields {
|
||||
if field.isBodyResolverAnnotation() {
|
||||
bodyType = field.Type
|
||||
break
|
||||
}
|
||||
}
|
||||
switch bodyType {
|
||||
case typeJSONBody:
|
||||
if err := decodeJSONBody(req, rv); err != nil {
|
||||
return rv, fmt.Errorf("decode JSON body: %w", err)
|
||||
}
|
||||
return rv, nil
|
||||
case typeXMLBody:
|
||||
if err := decodeXMLBody(req, rv); err != nil {
|
||||
return rv, fmt.Errorf("decode XML body: %w", err)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Then execute directives.
|
||||
if len(r.Directives) > 0 {
|
||||
inheritableContext := context.Background()
|
||||
for _, dir := range r.Directives {
|
||||
@ -82,22 +114,31 @@ func buildResolverTree(t reflect.Type) (*fieldResolver, error) {
|
||||
}
|
||||
|
||||
func buildFieldResolver(parent *fieldResolver, field reflect.StructField) (*fieldResolver, error) {
|
||||
directives, err := parseStructTag(field)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse struct tag failed: %w", err)
|
||||
}
|
||||
t := field.Type
|
||||
path := make([]string, len(parent.Path)+1)
|
||||
copy(path, parent.Path)
|
||||
path[len(path)-1] = field.Name
|
||||
|
||||
root := &fieldResolver{
|
||||
Type: t,
|
||||
Field: field,
|
||||
Path: path,
|
||||
Directives: directives,
|
||||
Directives: make([]*directive, 0),
|
||||
}
|
||||
|
||||
if field.Anonymous && t.Kind() == reflect.Struct && len(directives) == 0 {
|
||||
// Skip parsing struct tags if met body resolver annotation field.
|
||||
if root.isBodyResolverAnnotation() {
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// Parse the struct tag and build the directives.
|
||||
if directives, err := parseStructTag(field); err != nil {
|
||||
return nil, fmt.Errorf("parse struct tag failed: %w", err)
|
||||
} else {
|
||||
root.Directives = directives
|
||||
}
|
||||
|
||||
if field.Anonymous && t.Kind() == reflect.Struct && len(root.Directives) == 0 {
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
fieldResolver, err := buildFieldResolver(root, t.Field(i))
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user