105 lines
2.4 KiB
Go
105 lines
2.4 KiB
Go
package errs
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
|
|
"gateway/internal/library/errors/code"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
var grpcDisplayCodeRE = regexp.MustCompile(`^\[(\d{8})\]\s*(.*)$`)
|
|
|
|
// FromError unwraps err and returns the first *Error in the chain.
|
|
func FromError(err error) *Error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
var e *Error
|
|
if errors.As(err, &e) {
|
|
return e
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FromCode parses an 8-digit code (SSCCCDDD) into an Error without a message.
|
|
func FromCode(raw uint32) (*Error, error) {
|
|
scope := code.Scope(raw / code.ScopeMultiplier)
|
|
sub := raw % code.ScopeMultiplier
|
|
category := code.Category(sub / code.CategoryMultiplier)
|
|
detail := code.Detail(sub % code.CategoryMultiplier)
|
|
|
|
return New(scope, category, detail, "")
|
|
}
|
|
|
|
// FromGRPCError converts a gRPC status error into *Error.
|
|
// When scope is omitted, code.Unset is used for built-in gRPC status codes.
|
|
func FromGRPCError(err error, scope ...code.Scope) (*Error, error) {
|
|
if err == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
sc := code.Unset
|
|
if len(scope) > 0 {
|
|
sc = scope[0]
|
|
}
|
|
|
|
s, ok := status.FromError(err)
|
|
if !ok {
|
|
return nil, fmt.Errorf("not a gRPC status error: %w", err)
|
|
}
|
|
|
|
if parsed := parseGRPCStatusMessage(s); parsed != nil {
|
|
if parsed.Scope() == code.Unset && sc != code.Unset {
|
|
out, scopeErr := parsed.WithScope(sc)
|
|
if scopeErr != nil {
|
|
return nil, scopeErr
|
|
}
|
|
|
|
return out.WithCause(err), nil
|
|
}
|
|
|
|
return parsed.WithCause(err), nil
|
|
}
|
|
|
|
// Standard gRPC codes (0-16): map into CatGRPC with detail = grpc code number.
|
|
if isBuiltinGRPCCode(s.Code()) {
|
|
return MustNew(sc, code.CatGRPC, code.Detail(s.Code()), s.Message()).WithCause(err), nil
|
|
}
|
|
|
|
// Custom status code carrying full business code (legacy clients).
|
|
if e, convErr := FromCode(uint32(s.Code())); convErr == nil {
|
|
return e.WithMessage(s.Message()).WithCause(err), nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("unable to convert gRPC error: code=%s msg=%q", s.Code(), s.Message())
|
|
}
|
|
|
|
func parseGRPCStatusMessage(s *status.Status) *Error {
|
|
matches := grpcDisplayCodeRE.FindStringSubmatch(s.Message())
|
|
if len(matches) != 3 {
|
|
return nil
|
|
}
|
|
|
|
raw, err := strconv.ParseUint(matches[1], 10, 32)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
e, err := FromCode(uint32(raw))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
return e.WithMessage(matches[2])
|
|
}
|
|
|
|
func isBuiltinGRPCCode(c codes.Code) bool {
|
|
return uint32(c) <= uint32(codes.Unauthenticated)
|
|
}
|