1
0
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:
Ggicci 2021-05-06 16:06:55 +08:00
parent 3ccdd2a4f9
commit 5be2274565
7 changed files with 54 additions and 25 deletions

View File

@ -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.

View File

@ -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
View 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...)
}
}

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -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)
}) })
} }

View File

@ -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,