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