Currently, the command bus has limited capabilities to handle errors returned by the command handler. Specifically, it can only retain the error message, which makes it hard to handle the error from the calling side. There's also no support for error codes, which are the typically the main value to check for when handling errors.
Requirements
The command handler must be able to add additional data to an error:
Error code
Human-readable, localized error messages
Debug information for development (?)
Proposal (gRPC error details)
gRPC error details
Proposal is to add support for Protocol Buffers, similar to how Connect does. An error that provides a Details() []proto.Message method can then enrich the error with arbitrary data.
For convenience, we can provide an *Error type that provides the most essential features out of the box.
Command handler
package example
func example() {
var underlyingError error // the actual error that made the command fail
var code int // the custom error code
var details []proto.Message
err := command.NewError(code, underlyingError, command.WithErrorDetail(details...)) // *command.Err
// err.Code() == code
// err.Error() == err.Error()
for i, d := range err.Details() {
// d == *command.ErrorDetail{...}
v, err := d.Value()
if err != nil {
panic(fmt.Errorf("failed to unmarshal error detail: %w", err))
}
// v == details[i] // not actually the same instance but a copy
}
}
Command dispatcher
package example
func example(bus command.Bus, cmd command.Command) {
err := bus.Dispatch(context.TODO(), cmd, dispatch.Sync())
cerr := command.Error(err) // parse the error
// cerr == *command.Err{...}
// cerr.Code() == int(...)
// cerr.Message() == "..."
// Either manually iterate the details
for i, d := range cerr.Details() {
v, err := d.Value()
if err != nil {
panic(fmt.Errorf("failed to unmarshal error detail: %w", err))
}
switch v := v.(type) {
case *errdetails.LocalizedMessage:
log.Println(v.GetLocale(), v.GetMessage())
}
}
// Or use provided methods on the *Error type
msg, ok := cerr.LocalizedMessage("en-US")
// msg == "..."
}
Problem
Currently, the command bus has limited capabilities to handle errors returned by the command handler. Specifically, it can only retain the error message, which makes it hard to handle the error from the calling side. There's also no support for error codes, which are the typically the main value to check for when handling errors.
Requirements
The command handler must be able to add additional data to an error:
Proposal (gRPC error details)
gRPC error details
Proposal is to add support for Protocol Buffers, similar to how Connect does. An error that provides a
Details() []proto.Message
method can then enrich the error with arbitrary data.For convenience, we can provide an
*Error
type that provides the most essential features out of the box.Command handler
Command dispatcher