template-monorepo/internal/library/errors/convert.go

106 lines
2.4 KiB
Go
Raw Normal View History

2026-05-19 11:00:28 +00:00
package errs
import (
"errors"
"fmt"
"regexp"
"strconv"
"gateway/internal/library/errors/code"
2026-05-19 13:15:18 +00:00
2026-05-19 11:00:28 +00:00
"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)
}