improve error handling for gconv.Struct/ghttp.Server; add NewSkip/NewfSkip function for package gerror

This commit is contained in:
John
2020-04-28 15:04:07 +08:00
parent 9e064e2651
commit 6e7224e306
10 changed files with 180 additions and 59 deletions

View File

@ -1,25 +1,93 @@
package main
import (
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/net/ghttp"
)
func main() {
type SaveReq1 struct {
Id uint
Tags string
}
type SaveReq2 struct {
Id uint
Tags []string
}
r1 := SaveReq1{
Id: 1,
Tags: "ac",
}
var r2 *SaveReq2
err := gconv.Struct(r1, &r2)
g.Dump(err)
g.Dump(r2)
// ListsInput ListsInput
type ListsInput struct {
Page int `v:"min:1#page不能小于1"`
Limit int `v:"between:10,100#limit必须是10-100之间的整数"`
Search
}
type ListRequest struct {
ListsInput
}
// Search Search
type Search struct {
GoodsName string
GoodsSource string
FreeShipping string
MarketPrice struct {
Min int
Max int
IsInclude bool
}
GuidePrice struct {
Min int
Max int
IsInclude bool
}
AgreementPrice struct {
Min int
Max int
IsInclude bool
}
NormalProfitMargin struct {
Min int
Max int
IsInclude bool
}
ActivityProfitMargin struct {
Min int
Max int
IsInclude bool
}
Sales struct {
Cycle string
Quantity struct {
Min int
Max int
IsInclude bool
}
}
SalesReturn struct {
Cycle string
Quantity struct {
Min int
Max int
IsInclude bool
}
}
ChooseGoods struct {
Cycle string
Quantity struct {
Min int
Max int
IsInclude bool
}
}
}
// Lists Lists
func Lists(r *ghttp.Request) {
var params *ListRequest
if err := r.Parse(&params); err != nil {
r.Response.WriteExit(gerror.Stack(err))
}
r.Response.Write(params)
}
func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.POST("/test", Lists)
})
s.SetPort(8199)
s.Run()
}

View File

@ -5,6 +5,9 @@
// You can obtain one at https://github.com/gogf/gf.
// Package errors provides simple functions to manipulate errors.
//
// Very note that, this package is quite a base package, which should not import extra
// packages except standard packages, to avoid cycle imports.
package gerror
import (
@ -13,11 +16,13 @@ import (
// ApiStack is the interface for Stack feature.
type ApiStack interface {
Error() string // It should be en error.
Stack() string
}
// ApiCause is the interface for Cause feature.
type ApiCause interface {
Error() string // It should be en error.
Cause() error
}
@ -32,6 +37,18 @@ func New(text string) error {
}
}
// NewSkip creates and returns an error which is formatted from given text.
// The parameter <skip> specifies the stack callers skipped amount.
func NewSkip(skip int, text string) error {
if text == "" {
return nil
}
return &Error{
stack: callers(skip),
text: text,
}
}
// Newf returns an error that formats as the given format and args.
func Newf(format string, args ...interface{}) error {
if format == "" {
@ -43,6 +60,18 @@ func Newf(format string, args ...interface{}) error {
}
}
// NewfSkip returns an error that formats as the given format and args.
// The parameter <skip> specifies the stack callers skipped amount.
func NewfSkip(skip int, format string, args ...interface{}) error {
if format == "" {
return nil
}
return &Error{
stack: callers(skip),
text: fmt.Sprintf(format, args...),
}
}
// Wrap wraps error with text.
// It returns nil if given err is nil.
func Wrap(err error, text string) error {

View File

@ -9,7 +9,6 @@ package gerror
import (
"bytes"
"fmt"
"github.com/gogf/gf/internal/intlog"
"io"
"runtime"
"strings"
@ -131,15 +130,6 @@ func formatSubStack(st stack, buffer *bytes.Buffer) {
if strings.Contains(file, gFILTER_KEY) {
continue
}
// Avoid GF stacks if not in GF development.
if !intlog.IsEnabled() {
if strings.Contains(file, "github.com/gogf/gf/") {
continue
}
if strings.Contains(file, "github.com/gogf/gf@") {
continue
}
}
// Avoid stack string like "<autogenerated>"
if strings.Contains(file, "<") {
continue

View File

@ -15,8 +15,14 @@ const (
gMAX_STACK_DEPTH = 32
)
func callers() stack {
var pcs [gMAX_STACK_DEPTH]uintptr
n := runtime.Callers(3, pcs[:])
return pcs[0:n]
// callers returns the stack callers.
func callers(skip ...int) stack {
var (
pcs [gMAX_STACK_DEPTH]uintptr
n = 3
)
if len(skip) > 0 {
n += skip[0]
}
return pcs[:runtime.Callers(n, pcs[:])]
}

View File

@ -7,6 +7,7 @@
package ghttp
import (
"github.com/gogf/gf/errors/gerror"
"strings"
"github.com/gogf/gf/encoding/gurl"
@ -54,12 +55,16 @@ func BuildParams(params interface{}, noUrlEncode ...bool) (encodedParamStr strin
// niceCallFunc calls function <f> with exception capture logic.
func niceCallFunc(f func()) {
defer func() {
if err := recover(); err != nil {
switch err {
if e := recover(); e != nil {
switch e {
case gEXCEPTION_EXIT, gEXCEPTION_EXIT_ALL:
return
default:
panic(err)
if _, ok := e.(gerror.ApiStack); ok {
panic(e)
} else {
panic(gerror.NewfSkip(1, "%v", e))
}
}
}
}()

View File

@ -7,11 +7,10 @@
package ghttp
import (
"github.com/gogf/gf/errors/gerror"
"net/http"
"reflect"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/util/gutil"
)
@ -122,7 +121,11 @@ func (m *Middleware) Next() {
loop = false
}
}, func(exception interface{}) {
m.request.error = gerror.Newf("%v", exception)
if e, ok := exception.(gerror.ApiStack); ok {
m.request.error = e.(error)
} else {
m.request.error = gerror.NewfSkip(1, "%v", exception)
}
m.request.Response.WriteStatus(http.StatusInternalServerError, exception)
loop = false
})

View File

@ -280,7 +280,7 @@ func (r *Request) parseForm() {
// Only allow chars of: '\w', '[', ']', '-'.
if !gregex.IsMatchString(`^[\w\-\[\]]+$`, name) && len(r.PostForm) == 1 {
// It might be JSON/XML content.
if s := name + strings.Join(values, " "); len(s) > 0 {
if s := gstr.Trim(name + strings.Join(values, " ")); len(s) > 0 {
if s[0] == '{' && s[len(s)-1] == '}' || s[0] == '<' && s[len(s)-1] == '>' {
r.bodyContent = gconv.UnsafeStrToBytes(s)
params = ""

View File

@ -12,10 +12,6 @@ import (
"github.com/gogf/gf/os/glog"
)
const (
gPATH_FILTER_KEY = "github.com/gogf/gf/"
)
// Logger returns the logger of the server.
func (s *Server) Logger() *glog.Logger {
return s.config.Logger
@ -54,22 +50,22 @@ func (s *Server) handleErrorLog(err error, r *Request) {
scheme = "https"
}
content := fmt.Sprintf(
`%d, "%s %s %s %s %s" %.3f, %s, "%s", "%s"`,
`%d "%s %s %s %s %s" %.3f, %s, "%s", "%s"`,
r.Response.Status, r.Method, scheme, r.Host, r.URL.String(), r.Proto,
float64(r.LeaveTime-r.EnterTime)/1000,
r.GetClientIp(), r.Referer(), r.UserAgent(),
)
if stack := gerror.Stack(err); stack != "" {
content += "\nStack:\n" + stack
s.config.Logger.File(s.config.ErrorLogPattern).
Stack(false).
Stdout(s.config.LogStdout).
Error(content)
return
if s.config.ErrorStack {
if stack := gerror.Stack(err); stack != "" {
content += "\nStack:\n" + stack
} else {
content += ", " + err.Error()
}
} else {
content += ", " + err.Error()
}
s.Logger().File(s.config.ErrorLogPattern).
Stack(s.config.ErrorStack).
StackWithFilter(gPATH_FILTER_KEY).
s.config.Logger.
File(s.config.ErrorLogPattern).
Stdout(s.config.LogStdout).
Error(content)
Print(content)
}

View File

@ -9,6 +9,7 @@ package gconv
import (
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/empty"
"reflect"
"regexp"
@ -31,8 +32,8 @@ var (
)
// Struct maps the params key-value pairs to the corresponding struct object's properties.
// The third parameter <mapping> is unnecessary, indicating the mapping rules between the custom key name
// and the attribute name(case sensitive).
// The third parameter <mapping> is unnecessary, indicating the mapping rules between the
// custom key name and the attribute name(case sensitive).
//
// Note:
// 1. The <params> can be any type of map/struct, usually a map.
@ -42,14 +43,18 @@ var (
// It will automatically convert the first letter of the key to uppercase
// in mapping procedure to do the matching.
// It ignores the map key, if it does not match.
func Struct(params interface{}, pointer interface{}, mapping ...map[string]string) error {
func Struct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
if params == nil {
return errors.New("params cannot be nil")
}
if pointer == nil {
return errors.New("object pointer cannot be nil")
}
defer func() {
if e := recover(); e != nil {
err = gerror.NewfSkip(1, "%v", e)
}
}()
// paramsMap is the map[string]interface{} type variable for params.
paramsMap := Map(params)
if paramsMap == nil {

View File

@ -780,3 +780,22 @@ func Test_Struct_Complex(t *testing.T) {
t.Assert(model.Data.ResultDetail.CurrentReportDetail.LoansProductCount, "8")
})
}
func Test_Struct_CatchPanic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type Score struct {
Name string
Result int
}
type User struct {
Score
}
user := new(User)
scores := map[string]interface{}{
"Score": 1,
}
err := gconv.Struct(scores, user)
t.AssertNE(err, nil)
})
}