Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions go/core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ type ReflectionError struct {

// GenkitError is the base error type for Genkit errors.
type GenkitError struct {
Message string `json:"message"` // Exclude from default JSON if embedded elsewhere
Status StatusName `json:"status"`
HTTPCode int `json:"-"` // Exclude from default JSON
Details map[string]any `json:"details"` // Use map for arbitrary details
Source *string `json:"source,omitempty"` // Pointer for optional
Message string `json:"message"` // Exclude from default JSON if embedded elsewhere
Status StatusName `json:"status"`
HTTPCode int `json:"-"` // Exclude from default JSON
Details map[string]any `json:"details"` // Use map for arbitrary details
Source *string `json:"source,omitempty"` // Pointer for optional
originalError error // The wrapped error, if any
}

// UserFacingError is the base error type for user facing errors.
Expand Down Expand Up @@ -70,14 +71,21 @@ func (e *UserFacingError) Error() string {

// NewError creates a new GenkitError with a stack trace.
func NewError(status StatusName, message string, args ...any) *GenkitError {
// Prevents a compile-time warning about non-constant message.
msg := message

ge := &GenkitError{
Status: status,
Message: fmt.Sprintf(msg, args...),
}

// scan args for the last error to wrap it (Iterate backwards)
for i := len(args) - 1; i >= 0; i-- {
if err, ok := args[i].(error); ok {
ge.originalError = err
break
}
}

errStack := string(debug.Stack())
if errStack != "" {
ge.Details = make(map[string]any)
Expand All @@ -91,14 +99,22 @@ func (e *GenkitError) Error() string {
return e.Message
}

// Unwrap implements the standard error unwrapping interface.
// This allows errors.Is and errors.As to work with GenkitError.
func (e *GenkitError) Unwrap() error {
return e.originalError
}

// ToReflectionError returns a JSON-serializable representation for reflection API responses.
func (e *GenkitError) ToReflectionError() ReflectionError {
errDetails := &ReflectionErrorDetails{}
if stackVal, ok := e.Details["stack"].(string); ok {
errDetails.Stack = &stackVal
}
if traceVal, ok := e.Details["traceId"].(string); ok {
errDetails.TraceID = &traceVal
if e.Details != nil {
if stackVal, ok := e.Details["stack"].(string); ok {
errDetails.Stack = &stackVal
}
if traceVal, ok := e.Details["traceId"].(string); ok {
errDetails.TraceID = &traceVal
}
}
return ReflectionError{
Details: errDetails,
Expand Down
23 changes: 23 additions & 0 deletions go/core/error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package core

import (
"errors"
"testing"
)

func TestGenkitErrorUnwrap(t *testing.T) {
original := errors.New("original failure")

// Use INTERNAL instead of StatusInternal
gErr := NewError(INTERNAL, "something happened: %v", original)

// Verify errors.Is works (this is the most important check)
if !errors.Is(gErr, original) {
t.Errorf("expected errors.Is to return true, but got false")
}

// Verify Unwrap works directly
if gErr.Unwrap() != original {
t.Errorf("Unwrap() returned wrong error")
}
}