Skip to content

Commit

Permalink
fix: enhance statuserror
Browse files Browse the repository at this point in the history
  • Loading branch information
morlay committed Jan 21, 2025
1 parent b8bf578 commit f5e1e02
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 179 deletions.
9 changes: 9 additions & 0 deletions devpkg/operatorgen/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,19 @@ func (*@Type) ResponseErrors() []error {
`, snippet.Args{
"Type": snippet.ID(named.Obj()),
"statusErrors": snippet.Snippets(func(yield func(snippet.Snippet) bool) {
added := map[string]bool{}

for _, statusError := range statusErrors {
x := statusError.Error()
if _, ok := added[x]; ok {
continue
}

if !yield(snippet.Sprintf("%v,\n", statusError)) {
return
}

added[x] = true
}
}),
})
Expand Down
63 changes: 31 additions & 32 deletions devpkg/operatorgen/statuserr_scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,25 @@ import (
"sort"
"strconv"

typex "github.com/octohelm/x/types"

"github.com/octohelm/courier/pkg/statuserror"
"github.com/octohelm/gengo/pkg/gengo"
gengotypes "github.com/octohelm/gengo/pkg/types"
typex "github.com/octohelm/x/types"
)

func newStatusErrScanner() *statusErrScanner {
return &statusErrScanner{
statusErrorTypes: map[*types.Named][]*statuserror.ErrorResponse{},
errorsUsed: map[*types.Func][]*statuserror.ErrorResponse{},
statusErrorTypes: map[*types.Named][]*statuserror.Descriptor{},
errorsUsed: map[*types.Func][]*statuserror.Descriptor{},
}
}

type statusErrScanner struct {
statusErrorTypes map[*types.Named][]*statuserror.ErrorResponse
errorsUsed map[*types.Func][]*statuserror.ErrorResponse
statusErrorTypes map[*types.Named][]*statuserror.Descriptor
errorsUsed map[*types.Func][]*statuserror.Descriptor
}

var statusErr = reflect.TypeOf(statuserror.ErrorResponse{})
var statusErr = reflect.TypeOf(statuserror.Descriptor{})

func isTypeStatusErr(named *types.Named) bool {
if o := named.Obj(); o != nil {
Expand All @@ -55,7 +54,7 @@ func identChainOfCallFunc(expr ast.Expr) (list []*ast.Ident) {
return
}

func (s *statusErrScanner) StatusErrorsInFunc(ctx gengo.Context, typeFunc *types.Func) []*statuserror.ErrorResponse {
func (s *statusErrScanner) StatusErrorsInFunc(ctx gengo.Context, typeFunc *types.Func) []*statuserror.Descriptor {
if typeFunc == nil {
return nil
}
Expand All @@ -64,7 +63,7 @@ func (s *statusErrScanner) StatusErrorsInFunc(ctx gengo.Context, typeFunc *types
return statusErrList
}

s.errorsUsed[typeFunc] = []*statuserror.ErrorResponse{}
s.errorsUsed[typeFunc] = []*statuserror.Descriptor{}

pkg := ctx.Package(typeFunc.Pkg().Path())

Expand Down Expand Up @@ -111,16 +110,16 @@ func (s *statusErrScanner) StatusErrorsInFunc(ctx gengo.Context, typeFunc *types
return s.errorsUsed[typeFunc]
}

func (s *statusErrScanner) appendStateErrs(typeFunc *types.Func, statusErrs ...*statuserror.ErrorResponse) {
m := map[string]*statuserror.ErrorResponse{}
func (s *statusErrScanner) appendStateErrs(typeFunc *types.Func, statusErrs ...*statuserror.Descriptor) {
m := map[string]*statuserror.Descriptor{}

errs := append(s.errorsUsed[typeFunc], statusErrs...)
for i := range errs {
s := errs[i]
m[fmt.Sprintf("%s%d", s.Key, s.Code)] = s
m[fmt.Sprintf("%s%d", s.Code, s.Status)] = s
}

next := make([]*statuserror.ErrorResponse, 0)
next := make([]*statuserror.Descriptor, 0)
for k := range m {
next = append(next, m[k])
}
Expand All @@ -134,8 +133,8 @@ func (s *statusErrScanner) appendStateErrs(typeFunc *types.Func, statusErrs ...*

func (s *statusErrScanner) scanStatusErrIsExist(typeFunc *types.Func, pkg gengotypes.Package, obj types.Object, callIdent *ast.Ident, x *ast.CallExpr) bool {
if callIdent.Name == "Wrap" && obj.Pkg().Path() == statusErr.PkgPath() {
code := 0
key := ""
statusCode := 0
code := ""
msg := ""
desc := make([]string, 0)

Expand All @@ -150,10 +149,10 @@ func (s *statusErrScanner) scanStatusErrIsExist(typeFunc *types.Func, pkg gengot
}

switch i {
case 0: // code
code, _ = strconv.Atoi(tv.Value.String())
case 1: // key
key, _ = strconv.Unquote(tv.Value.String())
case 0: // statusCode
statusCode, _ = strconv.Atoi(tv.Value.String())
case 1: // code
code, _ = strconv.Unquote(tv.Value.String())
case 2: // msg
msg, _ = strconv.Unquote(tv.Value.String())
default:
Expand All @@ -162,15 +161,15 @@ func (s *statusErrScanner) scanStatusErrIsExist(typeFunc *types.Func, pkg gengot
}
}

if code > 0 {
if statusCode > 0 {
if msg == "" {
msg = key
msg = code
}

s.appendStateErrs(typeFunc, &statuserror.ErrorResponse{
Key: key,
Code: code,
Msg: msg,
s.appendStateErrs(typeFunc, &statuserror.Descriptor{
Code: code,
Status: statusCode,
Message: msg,
})
}

Expand Down Expand Up @@ -215,19 +214,19 @@ func (s *statusErrScanner) resolveStateCode(ctx gengo.Context, named *types.Name
return 0, false
}

func (s *statusErrScanner) scanErrWithStatusCodeInterface(ctx gengo.Context, named *types.Named) (list []*statuserror.ErrorResponse) {
func (s *statusErrScanner) scanErrWithStatusCodeInterface(ctx gengo.Context, named *types.Named) (list []*statuserror.Descriptor) {
if named.Obj() == nil {
return nil
}

serr := &statuserror.ErrorResponse{
Key: filepath.Base(named.Obj().Pkg().Path()) + "." + named.Obj().Name(),
Code: http.StatusInternalServerError,
serr := &statuserror.Descriptor{
Code: filepath.Base(named.Obj().Pkg().Path()) + "." + named.Obj().Name(),
Status: http.StatusInternalServerError,
}

code, ok := s.resolveStateCode(ctx, named)
if ok {
serr.Code = code
serr.Status = code
}

method, ok := typex.FromTType(types.NewPointer(named)).MethodByName("Error")
Expand All @@ -245,14 +244,14 @@ func (s *statusErrScanner) scanErrWithStatusCodeInterface(ctx gengo.Context, nam
str, err := strconv.Unquote(x.Value)
if err == nil {
e := &(*serr)
e.Msg = str
e.Message = str
list = append(list, e)
}
case *ast.CallExpr:
if selectExpr, ok := x.Fun.(*ast.SelectorExpr); ok {
if selectExpr.Sel.Name == "Sprintf" {
e := &(*serr)
e.Msg = fmtSprintfArgsAsTemplate(x.Args)
e.Message = fmtSprintfArgsAsTemplate(x.Args)
list = append(list, e)
}
}
Expand Down
8 changes: 4 additions & 4 deletions example/apis/org/zz_generated.operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ func (*GetOrg) ResponseData() *Detail {

func (*GetOrg) ResponseErrors() []error {
return []error{
&(statuserror.ErrorResponse{
Code: 404,
Key: "org.ErrNotFound",
Msg: "{OrgName}: 组织不存在",
&(statuserror.Descriptor{
Code: "org.ErrNotFound",
Message: "{OrgName}: 组织不存在",
Status: 404,
}),
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/courierhttp/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func (r *result) Into(body any) (courier.Metadata, error) {
if r.c.NewError != nil {
body = r.c.NewError()
} else {
body = &statuserror.ErrorResponse{
body = &statuserror.Descriptor{
Source: r.Response.Request.Host,
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/courierhttp/handler/httprouter/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ HTTP/0.0 400 Bad Request
Content-Type: application/json; charset=utf-8
Server: test (CreateOrg)
{"code":400,"key":"InvalidParameter","msg":"Bad Request","source":"test","errors":[{"code":400,"key":"InvalidParameter","msg":"string value length should be less or equal than 5, but got 7","location":"body","pointer":"/name","source":"test"}]}
{"code":400,"msg":"Bad Request","errors":[{"code":"InvalidParameter","message":"string value length should be less or equal than 5, but got 7","location":"body","pointer":"/name","source":"test"}]}
`))
})
})
Expand Down
133 changes: 133 additions & 0 deletions pkg/statuserror/descriptor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package statuserror

import (
"fmt"
"github.com/go-json-experiment/json"
"go/ast"
"net/http"
"path/filepath"
"reflect"
"strconv"

"github.com/go-json-experiment/json/jsontext"
)

type WithStatusCode interface {
StatusCode() int
}

type WithErrCode interface {
ErrCode() string
}

type WithLocation interface {
Location() string
}

type WithJSONPointer interface {
JSONPointer() jsontext.Pointer
}

type IntOrString string

type Descriptor struct {
Code string `json:"code,omitzero"`
Message string `json:"message,omitzero"`
Location string `json:"location,omitzero"`
Pointer jsontext.Pointer `json:"pointer,omitzero"`
Source string `json:"source,omitzero"`

Status int `json:"-"`
Errors []*Descriptor `json:"-"`

Extra map[string]any `json:",inline"`
}

func (e *Descriptor) UnmarshalErrorResponse(statusCode int, raw []byte) error {
if err := json.Unmarshal(raw, e); err != nil {
e.Status = statusCode
e.Message = string(raw)
return nil
}

v, _ := strconv.ParseInt(e.Code, 10, 64)
if v > 0 {
e.Status = 0
}

if e.Extra != nil {
if msg, ok := e.Extra["msg"].(string); ok {
e.Message = msg
}
}

return nil
}

func (e *Descriptor) StatusCode() int {
return e.Status
}

func (e *Descriptor) Error() string {
return fmt.Sprintf("%s{message=%q}", e.Code, e.Message)
}

func asDescriptor(err error, source string, loc string) *Descriptor {
if errResp, ok := err.(*Descriptor); ok {
return errResp
}

er := &Descriptor{
Source: source,
Location: loc,
}

if w, ok := err.(interface{ Unwrap() error }); ok {
if e := w.Unwrap(); e != nil {
er.Message = e.Error()
}
}

if er.Message == "" {
er.Message = err.Error()
}

if v, ok := err.(WithStatusCode); ok {
er.Status = v.StatusCode()
}

if v, ok := err.(WithJSONPointer); ok {
er.Status = http.StatusBadRequest
er.Code = "InvalidParameter"
er.Pointer = v.JSONPointer()
}

if v, ok := err.(WithErrCode); ok {
er.Code = v.ErrCode()
}

if er.Code == "" {
rv := reflect.TypeOf(err)
for rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}

if ast.IsExported(rv.Name()) {
if p := rv.PkgPath(); p != "" {
er.Code = filepath.Base(p) + "." + rv.Name()
} else {
er.Code = rv.Name()
}
}
}

if er.Code == "" {
er.Code = "InternalServerError"
}

if er.Status == 0 {
er.Status = http.StatusInternalServerError
}

return er
}
Loading

0 comments on commit f5e1e02

Please sign in to comment.