mirror of
https://github.com/ggicci/httpin.git
synced 2024-11-28 08:49:05 +02:00
feat: debug switch, default UTC time and up tests
This commit is contained in:
parent
3ccdd2a4f9
commit
5be2274565
14
README.md
14
README.md
@ -14,18 +14,18 @@ Suppose that we have an RESTful API for querying users. We can define a struct t
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
type Authorization struct {
|
type Authorization struct {
|
||||||
Token string `in:"query.access_token,header.x-api-token"`
|
Token string `in:"form=access_token,header=x-api-token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pagination struct {
|
type Pagination struct {
|
||||||
Page int `in:"query.page"`
|
Page int `in:"form=page"`
|
||||||
PerPage int `in:"query.per_page"`
|
PerPage int `in:"form=per_page,page_size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserQuery struct {
|
type UserQuery struct {
|
||||||
Gender string `in:"query.gender"`
|
Gender string `in:"form=gender"`
|
||||||
AgeRange []int `in:"query.age_range"`
|
AgeRange []int `in:"form=age_range"`
|
||||||
IsMember bool `in:"query.is_member"`
|
IsMember bool `in:"form=is_member"`
|
||||||
Pagination
|
Pagination
|
||||||
Authorization
|
Authorization
|
||||||
}
|
}
|
||||||
@ -59,7 +59,7 @@ func QueryUser(rw http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Advanced Usage - Use Middleware
|
## Advanced: Use Middleware
|
||||||
|
|
||||||
Firstly setup httpin middleware for you APIs.
|
Firstly setup httpin middleware for you APIs.
|
||||||
|
|
||||||
|
@ -104,13 +104,13 @@ func DecodeString(data []byte, rv reflect.Value) error {
|
|||||||
func parseTime(value string) (time.Time, error) {
|
func parseTime(value string) (time.Time, error) {
|
||||||
// Try parsing value as RFC3339 format.
|
// Try parsing value as RFC3339 format.
|
||||||
if t, err := time.Parse(time.RFC3339Nano, value); err == nil {
|
if t, err := time.Parse(time.RFC3339Nano, value); err == nil {
|
||||||
return t, nil
|
return t.UTC(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try parsing value as int64 (timestamp).
|
// Try parsing value as int64 (timestamp).
|
||||||
// TODO(ggicci): can support float timestamp, e.g. 1618974933.284368
|
// TODO(ggicci): can support float timestamp, e.g. 1618974933.284368
|
||||||
if timestamp, err := strconv.ParseInt(value, 10, 64); err == nil {
|
if timestamp, err := strconv.ParseInt(value, 10, 64); err == nil {
|
||||||
return time.Unix(timestamp, 0), nil
|
return time.Unix(timestamp, 0).UTC(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return time.Time{}, fmt.Errorf("invalid time value, use time.RFC3339Nano format or timestamp")
|
return time.Time{}, fmt.Errorf("invalid time value, use time.RFC3339Nano format or timestamp")
|
||||||
|
23
debug.go
Normal file
23
debug.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package httpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
debugOn = false
|
||||||
|
)
|
||||||
|
|
||||||
|
func DebugOn() {
|
||||||
|
debugOn = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func DebugOff() {
|
||||||
|
debugOn = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func debug(format string, v ...interface{}) {
|
||||||
|
if debugOn {
|
||||||
|
log.Printf(format, v...)
|
||||||
|
}
|
||||||
|
}
|
@ -63,7 +63,7 @@ func RegisterDirectiveExecutor(name string, exe DirectiveExecutor) {
|
|||||||
panic(fmt.Sprintf("duplicate executor: %q", name))
|
panic(fmt.Sprintf("duplicate executor: %q", name))
|
||||||
}
|
}
|
||||||
executors[name] = exe
|
executors[name] = exe
|
||||||
fmt.Printf("directive executor registered: %q\n", name)
|
debug("directive executor registered: %q\n", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDirective(directive string) (*Directive, error) {
|
func buildDirective(directive string) (*Directive, error) {
|
||||||
@ -100,7 +100,7 @@ func extractFromKVSWithKeyForSlice(ctx *DirectiveContext, kvs map[string][]strin
|
|||||||
|
|
||||||
formValues, exists := kvs[key]
|
formValues, exists := kvs[key]
|
||||||
if !exists {
|
if !exists {
|
||||||
fmt.Printf(" > key %q not found in %s\n", key, ctx.Executor)
|
debug(" > key %q not found in %s\n", key, ctx.Executor)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ func extractFromKVSWithKeyForSlice(ctx *DirectiveContext, kvs map[string][]strin
|
|||||||
|
|
||||||
func extractFromKVSWithKey(ctx *DirectiveContext, kvs map[string][]string, key string) error {
|
func extractFromKVSWithKey(ctx *DirectiveContext, kvs map[string][]string, key string) error {
|
||||||
if ctx.Context.Value(fieldSet) == true {
|
if ctx.Context.Value(fieldSet) == true {
|
||||||
fmt.Printf(" > field already set, skip\n")
|
debug(" > field already set, skip\n")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ func extractFromKVSWithKey(ctx *DirectiveContext, kvs map[string][]string, key s
|
|||||||
|
|
||||||
formValues, exists := kvs[key]
|
formValues, exists := kvs[key]
|
||||||
if !exists {
|
if !exists {
|
||||||
fmt.Printf(" > key %q not found in %s\n", key, ctx.Executor)
|
debug(" > key %q not found in %s\n", key, ctx.Executor)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var got string
|
var got string
|
||||||
@ -159,7 +159,7 @@ func extractFromKVSWithKey(ctx *DirectiveContext, kvs map[string][]string, key s
|
|||||||
|
|
||||||
func extractFromKVS(ctx *DirectiveContext, kvs map[string][]string, headerKey bool) error {
|
func extractFromKVS(ctx *DirectiveContext, kvs map[string][]string, headerKey bool) error {
|
||||||
for _, key := range ctx.Directive.Argv {
|
for _, key := range ctx.Directive.Argv {
|
||||||
fmt.Printf(" > execute directive %q with key %q\n", ctx.Directive.Executor, key)
|
debug(" > execute directive %q with key %q\n", ctx.Directive.Executor, key)
|
||||||
if headerKey {
|
if headerKey {
|
||||||
key = http.CanonicalHeaderKey(key)
|
key = http.CanonicalHeaderKey(key)
|
||||||
}
|
}
|
||||||
|
11
errors.go
11
errors.go
@ -6,7 +6,10 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrMissingField = errors.New("field required but missing")
|
var (
|
||||||
|
ErrMissingField = errors.New("field required but missing")
|
||||||
|
ErrUnsupporetedType = errors.New("unsupported type")
|
||||||
|
)
|
||||||
|
|
||||||
type UnsupportedTypeError struct {
|
type UnsupportedTypeError struct {
|
||||||
Type reflect.Type
|
Type reflect.Type
|
||||||
@ -17,6 +20,10 @@ func (e UnsupportedTypeError) Error() string {
|
|||||||
return fmt.Sprintf("httpin: unsupported type in %s: %s", e.Where, e.Type.String())
|
return fmt.Sprintf("httpin: unsupported type in %s: %s", e.Where, e.Type.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e UnsupportedTypeError) Unwrap() error {
|
||||||
|
return ErrUnsupporetedType
|
||||||
|
}
|
||||||
|
|
||||||
type InvalidField struct {
|
type InvalidField struct {
|
||||||
// Field is the name of the field.
|
// Field is the name of the field.
|
||||||
Field string `json:"field"`
|
Field string `json:"field"`
|
||||||
@ -36,6 +43,6 @@ func (f *InvalidField) Error() string {
|
|||||||
return fmt.Sprintf("httpin: invalid field %q: %v", f.Field, f.InternalError)
|
return fmt.Sprintf("httpin: invalid field %q: %v", f.Field, f.InternalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *InvalidField) Unwarp() error {
|
func (f *InvalidField) Unwrap() error {
|
||||||
return f.InternalError
|
return f.InternalError
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -134,14 +133,14 @@ func TestCore(t *testing.T) {
|
|||||||
Complex64Value: 1 + 4i,
|
Complex64Value: 1 + 4i,
|
||||||
Complex128Value: -6 + 17i,
|
Complex128Value: -6 + 17i,
|
||||||
StringValue: "doggy",
|
StringValue: "doggy",
|
||||||
TimeValue: time.Date(1991, 11, 10, 8, 0, 0, 0, time.FixedZone("E8", 8*3600)),
|
TimeValue: time.Date(1991, 11, 10, 0, 0, 0, 0, time.UTC),
|
||||||
BoolList: []bool{true, false, false, true},
|
BoolList: []bool{true, false, false, true},
|
||||||
IntList: []int{9, 9, 6},
|
IntList: []int{9, 9, 6},
|
||||||
FloatList: []float64{0.0, 0.5, 1.0},
|
FloatList: []float64{0.0, 0.5, 1.0},
|
||||||
StringList: []string{"Life", "is", "a", "Miracle"},
|
StringList: []string{"Life", "is", "a", "Miracle"},
|
||||||
TimeList: []time.Time{
|
TimeList: []time.Time{
|
||||||
time.Date(2000, 1, 2, 15, 4, 5, 0, time.FixedZone("W7", -7*3600)),
|
time.Date(2000, 1, 2, 22, 4, 5, 0, time.UTC),
|
||||||
time.Date(1991, 6, 28, 14, 0, 0, 0, time.FixedZone("E8", 8*3600)),
|
time.Date(1991, 6, 28, 6, 0, 0, 0, time.UTC),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,11 +163,11 @@ func TestCore(t *testing.T) {
|
|||||||
"per_page": {"20"},
|
"per_page": {"20"},
|
||||||
}
|
}
|
||||||
expected := &ProductQuery{
|
expected := &ProductQuery{
|
||||||
CreatedAt: time.Date(1991, 11, 10, 8, 0, 0, 0, time.FixedZone("+08:00", 8*3600)),
|
CreatedAt: time.Date(1991, 11, 10, 0, 0, 0, 0, time.UTC),
|
||||||
Color: "red",
|
Color: "red",
|
||||||
IsSoldout: true,
|
IsSoldout: true,
|
||||||
SortBy: []string{"id", "quantity"},
|
SortBy: []string{"id", "quantity"},
|
||||||
SortDesc: []bool{true, true},
|
SortDesc: []bool{false, true},
|
||||||
Pagination: Pagination{
|
Pagination: Pagination{
|
||||||
Page: 1,
|
Page: 1,
|
||||||
PerPage: 20,
|
PerPage: 20,
|
||||||
@ -211,7 +210,7 @@ func TestCore(t *testing.T) {
|
|||||||
got, err := core.ReadRequest(r)
|
got, err := core.ReadRequest(r)
|
||||||
So(got, ShouldBeNil)
|
So(got, ShouldBeNil)
|
||||||
So(err, ShouldBeError)
|
So(err, ShouldBeError)
|
||||||
So(errors.Is(err, UnsupportedTypeError{Type: reflect.TypeOf(ObjectID{})}), ShouldBeTrue)
|
So(errors.Is(err, ErrUnsupporetedType), ShouldBeTrue)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ func (r *FieldResolver) IsRoot() bool {
|
|||||||
|
|
||||||
func (r *FieldResolver) resolve(req *http.Request) (reflect.Value, error) {
|
func (r *FieldResolver) resolve(req *http.Request) (reflect.Value, error) {
|
||||||
rv := reflect.New(r.Type)
|
rv := reflect.New(r.Type)
|
||||||
fmt.Printf("resolve: %s (of %s)\n", r.Field.Name, r.Type)
|
debug("resolve: %s (of %s)\n", r.Field.Name, r.Type)
|
||||||
|
|
||||||
// Execute directives.
|
// Execute directives.
|
||||||
if len(r.Directives) > 0 {
|
if len(r.Directives) > 0 {
|
||||||
@ -35,7 +35,7 @@ func (r *FieldResolver) resolve(req *http.Request) (reflect.Value, error) {
|
|||||||
Value: rv,
|
Value: rv,
|
||||||
Context: inheritableContext,
|
Context: inheritableContext,
|
||||||
}
|
}
|
||||||
fmt.Printf(" > execute directive: %s with %v\n", dir.Executor, dir.Argv)
|
debug(" > execute directive: %s with %v\n", dir.Executor, dir.Argv)
|
||||||
if err := dir.Execute(directiveContext); err != nil {
|
if err := dir.Execute(directiveContext); err != nil {
|
||||||
return rv, &InvalidField{
|
return rv, &InvalidField{
|
||||||
Field: r.Field.Name,
|
Field: r.Field.Name,
|
||||||
|
Loading…
Reference in New Issue
Block a user