Error Handling
If a container fails to boot, you need to know exactly where the wiring failed. Rays replaces standard Go error handling during the lifecycle with a richer Error interface that captures stack traces and key-value attributes automatically.
import . "github.com/BeamFoundry/rays/pkg/lang"
func (s *UserService) PostConstruct() Error {
if s.db == nil {
err := NewError("WiringError", "Database pointer is nil")
// AddAttribute does not return the Error object, so it cannot be chained.
err.AddAttribute("component", "UserService")
err.AddAttribute("action", "PostConstruct")
return err
}
return nil
}Integration with Logging
Because Rays integrates its lang package directly with the logging framework (covered in Chapter 4), an Error can report itself seamlessly.
Instead of manually unpacking the error to log it, simply call err.LogError(). This function automatically extracts the error message, the stack trace, and all attached attributes, serializing them into your configured logging backend.
import . "github.com/BeamFoundry/rays/pkg/lang"
func (s *PaymentService) Process(amount float64) Error {
if amount <= 0 {
err := NewError("ValidationError", "Payment amount must be greater than zero")
err.AddAttribute("component", "PaymentService")
err.AddAttribute("amount", amount)
// Automatically logs the error with all context using the framework logger!
err.LogError()
return err
}
return nil
}The Try/Catch Pattern
While standard Go relies heavily on if err != nil checks, complex procedural logic can sometimes benefit from a more traditional exception-handling flow. The lang package provides a robust Try/Catch mechanism designed specifically around the framework’s Error interface.
Core Functions
Try(try func(), catches ...ErrorHandler) Error: Executes thetryfunction. If a panic occurs involving anError, it evaluates the provided catches and returns the resultingError. If no catch matches, the original error is returned automatically. Additionally, if standard Go code or a sub-function triggers a regularpanic(), theTryblock will safely catch it and wrap it into a new frameworkErrorwith the name “"PanicError"”, ensuring your application does not crash unexpectedly.Catch[E Error](action func(E) Error) ErrorHandler: Catches an error based on its specific Go type.CatchName(name string, action func(Error) Error) ErrorHandler: Catches an error based on its internal string name.Finally(action func()) ErrorHandler: Executes an action after thetryblock finishes, regardless of success or failure.
Raise Helpers
To easily trigger panics within a Try block when dealing with standard Go returns, use the Raise helpers:
RaiseErr(err error) bool: Panics iferris not nil.RaiseVal[T any](t T, err error) T: Panics iferris not nil; otherwise, returns the objectT. This is perfect for wrapping standard library calls likeRaiseVal(os.ReadFile("config.yaml")).
Example Usage
Here is a complete example defining a custom exception and handling it cleanly using the Try/Catch flow.
import (
"fmt"
. "github.com/BeamFoundry/rays/pkg/core"
. "github.com/BeamFoundry/rays/pkg/lang"
)
// 1. Define a Custom Exception
type TestException struct {
*DefaultError
}
func (e TestException) New() *TestException {
e.DefaultError = NewError("TestException", "A test exception has occurred")
return &e
}
type ProcessingService struct {
Service
}
// 2. Use Try/Catch in Business Logic
func (s *ProcessingService) Process() Error {
return Try(func() {
// Simulate an action that throws an error
RaiseErr(TestException{}.New())
fmt.Println("This line will never be reached")
}, CatchName("DatabaseError", func(e Error) Error {
// Will not match "TestException"
return e
}), Catch(func(e *TestException) Error {
// This WILL catch the specific TestException type
fmt.Println("Caught specifically:", e.Message())
return e
}), Finally(func() {
fmt.Println("Cleanup resources here...")
}))
}