Documentation ¶
Overview ¶
Package errdetails provides a convenient wrapping mechanism to incorporate gRPC Status details with Go's error wrapping paradigm.
Errors provided by this package are implemented by embedding protobuf errdetails messages, and themselves implement an identical interface. This allows the use of protobuf errdetails messages in the method signature of each of the Detail wrappers, any custom implementation thereof, or even another error unwrapped by `errors.As`.
Each error interface can be used in errors.As or errors.Is functions to unwrap, and some enable appending further details this way.
Index ¶
- Variables
- func FromJSON(r io.Reader, mappers ...DetailsMapper) error
- func New(code codes.Code, msg string, details ...Details) error
- func SetErrorHandler(h ErrorHandler)
- func StreamServerInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, ...) (err error)
- func ToJSON(from error) ([]byte, error)
- func UnaryServerInterceptor(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, ...) (resp interface{}, err error)
- func WithDetails(err error, details ...Details) error
- type BadRequestError
- type CausedError
- type DebugError
- type Details
- func BadRequest(violations ...details.FieldViolation) Details
- func Cause(info details.Info) Details
- func Code(code codes.Code) Details
- func Debug(info details.DebugInfo) Details
- func Help(links ...details.HelpLink) Details
- func LocalizedMessage(msg details.LocalizedMessage) Details
- func PreconditionFailure(violations ...details.PreconditionViolation) Details
- func QuotaFailure(violations ...details.QuotaViolation) Details
- func RequestInfo(info details.RequestInfo) Details
- func Resource(info details.ResourceInfo) Details
- func RetryDelay(delay time.Duration) Details
- type DetailsMapper
- type ErrorHandler
- type FailedPreconditionError
- type FailedQuotaError
- type HandlerFunc
- type HelpfulError
- type LocalizedError
- type RequestInfoError
- type ResourceInfoError
- type RetriableError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrCanceled error = &errCodeError{error: errUnknown, Code: codes.Canceled} ErrUnknown error = &errCodeError{error: errUnknown, Code: codes.Unknown} ErrInvalidArgument error = &errCodeError{error: errUnknown, Code: codes.InvalidArgument} ErrDeadlineExceeded error = &errCodeError{error: errUnknown, Code: codes.DeadlineExceeded} ErrNotFound error = &errCodeError{error: errUnknown, Code: codes.NotFound} ErrAlreadyExists error = &errCodeError{error: errUnknown, Code: codes.AlreadyExists} ErrPermissionDenied error = &errCodeError{error: errUnknown, Code: codes.PermissionDenied} ErrResourceExhausted error = &errCodeError{error: errUnknown, Code: codes.ResourceExhausted} ErrFailedPrecondition error = &errCodeError{error: errUnknown, Code: codes.FailedPrecondition} ErrAborted error = &errCodeError{error: errUnknown, Code: codes.Aborted} ErrOutOfRange error = &errCodeError{error: errUnknown, Code: codes.OutOfRange} ErrUnimplemented error = &errCodeError{error: errUnknown, Code: codes.Unimplemented} ErrInternal error = &errCodeError{error: errUnknown, Code: codes.Internal} ErrDataLoss error = &errCodeError{error: errUnknown, Code: codes.DataLoss} ErrUnauthenticated error = &errCodeError{error: errUnknown, Code: codes.Unauthenticated} )
Known Status Code errors for use as target of errors.Is().
Prefer constructing new errors with New constructor.
Functions ¶
func FromJSON ¶
func FromJSON(r io.Reader, mappers ...DetailsMapper) error
FromJSON reads JSON fom a Reader like a response Body, and makes best effort to reconstruct the wrapped error from gRPC Status such that errors.As and errors.Is may still be satisfied by the error interface types.
For any expected error not already accomodated by this package, you can provide optional DetailsMappers.
If the Map method of a DetailsMapper returns an implementation of Details wrapper, the error is further wrapped by the mapped wrapper.
func New ¶
New creates a new error from an Status Error Code. Resulting errors can be checked with errors.Is to match exported implementations.
This implementation is specific to gRPC Status codes, but the same example can be applied for any other Flag-like wrapping.
Example ¶
package main import ( "errors" "fmt" "github.com/ClaudiaJ/errdetails" "google.golang.org/grpc/codes" ) func main() { // New creates a new error with a distinct Code err := errdetails.New(codes.InvalidArgument, "fields not satisfied") fmt.Println(errors.Is(err, errdetails.ErrInvalidArgument)) }
Output: true
func SetErrorHandler ¶
func SetErrorHandler(h ErrorHandler)
SetLogger sets the package level logger that will be used when an error occurs after a Point of No Return and no error may be returned to caller (e.g. while writing to http.ResponseWriter)
By default, these errors are thrown away.
func StreamServerInterceptor ¶
func StreamServerInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error)
StreamServerInterceptor transcribes wrapped errors with details into gRPC Status.
func ToJSON ¶
ToJSON writes an error as JSON with details in-tact such that it can be mostly recovered with FromJSON.
func UnaryServerInterceptor ¶
func UnaryServerInterceptor(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error)
UnaryServerInterceptor transcribes wrapped errors with details into gRPC Status.
func WithDetails ¶
WithDetails wrap an error with additional details.
Example ¶
// an error can be enriched with many additional sources of eror details errdetails.WithDetails(testErr, errdetails.BadRequest(), errdetails.RequestInfo(&detailspb.RequestInfo{ RequestId: "123456789", }), )
Output:
Types ¶
type BadRequestError ¶
type BadRequestError interface { error WithViolation(violation ...details.FieldViolation) BadRequestError GetViolations() []details.FieldViolation }
BadRequestError is an error indicating the client had made a bad request, and includes details of each violation of the field validation rules not satisfied by the request.
func WithBadRequest ¶
func WithBadRequest(err error, violations ...details.FieldViolation) BadRequestError
WithBadRequest wraps an error with Bad Request details having optional field violations.
Example ¶
err := errdetails.WithBadRequest(testErr, &detailspb.BadRequest_FieldViolation{ Field: "username", Description: "username must not contain any part of email address.", }, &detailspb.BadRequest_FieldViolation{ Field: "password", Description: "password must be at least 5 characters long.", }, ) var badReq errdetails.BadRequestError if errors.As(err, &badReq) { fmt.Println("error is", reflect.ValueOf(&badReq).Elem().Type()) for _, violation := range badReq.GetViolations() { fmt.Printf("field violation %q: %s\n", violation.GetField(), violation.GetDescription()) } }
Output: error is errdetails.BadRequestError field violation "username": username must not contain any part of email address. field violation "password": password must be at least 5 characters long.
type CausedError ¶
CausedError is an error describing the cause of an error with structured details.
func WithCause ¶
func WithCause(err error, info details.Info) CausedError
WithCause wraps an error with information about the cause of the error.
Example ¶
const ReasonThrottle = "UPSTREAM_THROTTLE" err := errdetails.WithCause(testErr, &detailspb.ErrorInfo{ Reason: ReasonThrottle, Domain: "fake.domain.test", }, ) var causedErr errdetails.CausedError if errors.As(err, &causedErr) { fmt.Println("error is", reflect.ValueOf(&causedErr).Elem().Type()) fmt.Printf("with reason %q\n", causedErr.GetReason()) fmt.Printf("with domain %q\n", causedErr.GetDomain()) }
Output: error is errdetails.CausedError with reason "UPSTREAM_THROTTLE" with domain "fake.domain.test"
type DebugError ¶
DebugError is an error including debug information indicating where an error occurred and any additional details provided by the server.
func WithDebug ¶
func WithDebug(err error, info details.DebugInfo) DebugError
WithDebug wraps an error with additional debugging info.
Example ¶
err := errdetails.WithDebug(testErr, &detailspb.DebugInfo{ StackEntries: []string{"something", "goes", "here"}, Detail: "Server responded Internal Server Error with Message wrapping Status as string.", }, ) var debugErr errdetails.DebugError if errors.As(err, &debugErr) { fmt.Println("error is", reflect.ValueOf(&debugErr).Elem().Type()) fmt.Printf("with stack entries: %q\n", debugErr.GetStackEntries()) fmt.Printf("with detail: %q\n", debugErr.GetDetail()) }
Output: error is errdetails.DebugError with stack entries: ["something" "goes" "here"] with detail: "Server responded Internal Server Error with Message wrapping Status as string."
type Details ¶
Details are just error wrappers.
func BadRequest ¶
func BadRequest(violations ...details.FieldViolation) Details
BadRequest provides a Details wrapper to enrich errors with BadRequestError details.
Example ¶
package main import ( "github.com/ClaudiaJ/errdetails" detailspb "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/codes" ) func main() { errdetails.New(codes.InvalidArgument, "fields not satisfied", errdetails.BadRequest( // BadRequest takes optional Field Violations describing fields having failed validations. &detailspb.BadRequest_FieldViolation{ Field: "username", Description: "username must not contain any part of email address.", }, &detailspb.BadRequest_FieldViolation{ Field: "password", Description: "password must be at least 5 characters long.", }, ), ) }
Output:
func Cause ¶
Cause provides a Details wrapper to enrich errors with CausedError details.
Example ¶
package main import ( "github.com/ClaudiaJ/errdetails" detailspb "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/codes" ) func main() { errdetails.New(codes.DataLoss, "object payload not received in full", errdetails.Cause(&detailspb.ErrorInfo{ Reason: "stream body prematurely terminated by client", Domain: "bucket.platform.test", }), ) }
Output:
func Code ¶
Code wraps an external error with a specified Status Code. Note that while it is possible to wrap an error with multiple status codes, only the outer layer will be considered the resulting Status Code when unwrapped.
func Debug ¶
Debug provides a Details wrapper to enrich errors with DebugError details.
Example ¶
package main import ( "github.com/ClaudiaJ/errdetails" detailspb "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/codes" ) func main() { errdetails.New(codes.Internal, "impossible error reached", errdetails.Debug(&detailspb.DebugInfo{ StackEntries: []string{"data.Gnorm/One", "api.Thing/Something"}, Detail: "Request body was nil where it shouldn not have been", }), ) }
Output:
func Help ¶
Help wraps an error with links to documentation or FAQ pages.
Example ¶
package main import ( "github.com/ClaudiaJ/errdetails" detailspb "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/codes" ) func main() { errdetails.New(codes.PermissionDenied, "access denied", errdetails.Help(&detailspb.Help_Link{ Url: "https://login.platform.test/", Description: "Login or Register to access this page.", }), ) }
Output:
func LocalizedMessage ¶
func LocalizedMessage(msg details.LocalizedMessage) Details
LocalizedMessage provides a Details wrapper to enrich errors with LocalizedError details.
Example ¶
package main import ( "github.com/ClaudiaJ/errdetails" detailspb "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/codes" ) func main() { errdetails.New(codes.Unavailable, "service in maintenance mode", errdetails.LocalizedMessage( &detailspb.LocalizedMessage{ Locale: "en-US", Message: "Mattel Login is down for scheduled maintenance. Please try again later.", }, ), ) }
Output:
func PreconditionFailure ¶
func PreconditionFailure(violations ...details.PreconditionViolation) Details
PreconditionFailure provides a Details wrapper to enrich errors with FailedPreconditionError details.
Example ¶
package main import ( "github.com/ClaudiaJ/errdetails" detailspb "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/codes" ) func main() { errdetails.New(codes.FailedPrecondition, "Terms of Service is required", errdetails.PreconditionFailure( &detailspb.PreconditionFailure_Violation{ Type: "TOS", Description: "Please review and acknowledge Terms of Service before continuing.", }, ), ) }
Output:
func QuotaFailure ¶
func QuotaFailure(violations ...details.QuotaViolation) Details
QuotaFailure provides a Details wrapper to enrich errors with FailedQuotaError details.
Example ¶
package main import ( "github.com/ClaudiaJ/errdetails" detailspb "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/codes" ) func main() { errdetails.New(codes.ResourceExhausted, "Too many requests", errdetails.QuotaFailure(&detailspb.QuotaFailure_Violation{ Description: "Rate limit exceeded.", }), ) }
Output:
func RequestInfo ¶
func RequestInfo(info details.RequestInfo) Details
RequestInfo provides a Details wrapper to enrich errors with RequestInfoError details.
Example ¶
package main import ( "github.com/ClaudiaJ/errdetails" detailspb "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/codes" ) func main() { errdetails.New(codes.Internal, "unrecognized mime type on upload", errdetails.RequestInfo(&detailspb.RequestInfo{ RequestId: "123456789", }), ) }
Output:
func Resource ¶
func Resource(info details.ResourceInfo) Details
Resource provides a Details wrapper to enrich errors with ResourceInfoError details.
Example ¶
package main import ( "github.com/ClaudiaJ/errdetails" detailspb "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/codes" ) func main() { errdetails.New(codes.ResourceExhausted, "no more Redlines available", errdetails.Resource(&detailspb.ResourceInfo{ ResourceType: "currency", ResourceName: "Redlines", Owner: "auth0|123456789", Description: "no remaining currency in wallet", }), ) }
Output:
func RetryDelay ¶
RetryDelay provides a Details wrapper to enrich errors with RetriableError details.
Example ¶
package main import ( "time" "github.com/ClaudiaJ/errdetails" "google.golang.org/grpc/codes" ) func main() { errdetails.New(codes.Unavailable, "upstream responded with temporary failure", errdetails.RetryDelay(time.Minute)) }
Output:
type DetailsMapper ¶
type DetailsMapper interface {
Map(protoreflect.ProtoMessage) Details
}
DetailsMapper provides a mapping from arbitrary Protobuf message type to an Error wrapper that will reconstruct a fully wrapped error type from JSON data.
This enables error types built ontop protobuf messages not provided within this package to be reconstructed from http response body the same as all of the error types provided by this module.
This will go away whenever I can figure out how to acheive this with protoreflect.
type ErrorHandler ¶
type ErrorHandler interface {
Handle(error)
}
ErrorHandler handles ierremediable events, e.g. to log error occurring while writing to http.ResponseWriter.
type FailedPreconditionError ¶
type FailedPreconditionError interface { error WithViolation(...details.PreconditionViolation) FailedPreconditionError GetViolations() []details.PreconditionViolation }
FailedPreconditionError is an error describing what preconditions have failed.
An example being a Terms of Service acknowledgement that may be required before using a particular API or service, responses from the service will indicate that the precondition has not been met.
func WithPreconditionFailure ¶
func WithPreconditionFailure(err error, violations ...details.PreconditionViolation) FailedPreconditionError
WithPreconditionFailure wraps an error describing what preconditions have failed to be met.
Example ¶
err := errdetails.WithPreconditionFailure(testErr, &detailspb.PreconditionFailure_Violation{ Type: "TOS", Description: "Terms of Service not accepted.", }) var condErr errdetails.FailedPreconditionError if errors.As(err, &condErr) { fmt.Println("error is", reflect.ValueOf(&condErr).Elem().Type()) for _, violation := range condErr.GetViolations() { fmt.Printf("precondition violation %q: %s\n", violation.GetType(), violation.GetDescription()) } }
Output: error is errdetails.FailedPreconditionError precondition violation "TOS": Terms of Service not accepted.
type FailedQuotaError ¶
type FailedQuotaError interface { error WithViolation(...details.QuotaViolation) FailedQuotaError GetViolations() []details.QuotaViolation }
FailedQuotaError is an error describing a quota check failed.
func WithQuotaFailure ¶
func WithQuotaFailure(err error, violations ...details.QuotaViolation) FailedQuotaError
WithQuotaFailure wraps an error describing how a quota check has failed.
Example ¶
err := errdetails.WithQuotaFailure(testErr, &detailspb.QuotaFailure_Violation{ Subject: "auth0|123456789", Description: "Too many requests, too fast.", }) var quotaErr errdetails.FailedQuotaError if errors.As(err, "aErr) { fmt.Println("error is", reflect.ValueOf("aErr).Elem().Type()) for _, violation := range quotaErr.GetViolations() { fmt.Printf("quota violation %q: %s\n", violation.GetSubject(), violation.GetDescription()) } }
Output: error is errdetails.FailedQuotaError quota violation "auth0|123456789": Too many requests, too fast.
type HandlerFunc ¶
type HandlerFunc func(http.ResponseWriter, *http.Request) error
HandlerFunc type is an adapter to allow the use of ordinary functions as HTTP handlers.
func (HandlerFunc) ServeHTTP ¶
func (fn HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP serves a JSON error response back to client if the Handler would return an error.
Note of caution: Masking or otherwise distinguishing details safe to share to end client is an exercise left to the implementor.
type HelpfulError ¶
type HelpfulError interface { error WithLinks(...details.HelpLink) HelpfulError GetLinks() []details.HelpLink }
HelpfulError is an error including links to documentation relevant to the error or API.
func WithHelp ¶
func WithHelp(err error, links ...details.HelpLink) HelpfulError
WithHelp provides a Details wrapper to enrich errors with HelpfulError details.
Example ¶
err := errdetails.WithHelp(testErr, &detailspb.Help_Link{ Url: "https://wiki.platform.test/Why_Did_The_Error_Be", Description: "Article describing Platform standard errors and troubleshooting", }) var helpErr errdetails.HelpfulError if errors.As(err, &helpErr) { fmt.Println("error is", reflect.ValueOf(&helpErr).Elem().Type()) for _, link := range helpErr.GetLinks() { fmt.Printf("with url %q:\n%s\n", link.GetUrl(), link.GetDescription()) } }
Output: error is errdetails.HelpfulError with url "https://wiki.platform.test/Why_Did_The_Error_Be": Article describing Platform standard errors and troubleshooting
type LocalizedError ¶
type LocalizedError interface { error details.LocalizedMessage }
LocalizedError is an error including a localized error message that is safe to return to the user.
func WithLocalizedMessage ¶
func WithLocalizedMessage(err error, msg details.LocalizedMessage) LocalizedError
WithLocalizedMessage wraps an error providing a localized message that's safe to return to the end user.
Example ¶
err := errdetails.WithLocalizedMessage(testErr, &detailspb.LocalizedMessage{ Locale: "es-MX", Message: "Enviar de nuevo", }) var locErr errdetails.LocalizedError if errors.As(err, &locErr) { fmt.Println("error is", reflect.ValueOf(&locErr).Elem().Type()) fmt.Printf("[%s]: %s\n", locErr.GetLocale(), locErr.GetMessage()) }
Output: error is errdetails.LocalizedError [es-MX]: Enviar de nuevo
type RequestInfoError ¶
type RequestInfoError interface { error details.RequestInfo }
RequestInfoError is an error including metadata about the request that a client can attach when filing a bug or providing other forms of feedback.
func WithRequestInfo ¶
func WithRequestInfo(err error, info details.RequestInfo) RequestInfoError
WithRequestInfo adds RequestID and Serving Data to an error. Generally this is used to serve an ID that correlates with logging, along with encrypted stack trace or similar data relevant for serving a request. Errors then reported by testers or end users can be more readily triaged and troubleshot.
Example ¶
err := errdetails.WithRequestInfo(testErr, &detailspb.RequestInfo{ RequestId: "123456789", }) var reqErr errdetails.RequestInfoError if errors.As(err, &reqErr) { fmt.Println("error is", reflect.ValueOf(&reqErr).Elem().Type()) fmt.Printf("with request ID %q\n", reqErr.GetRequestId()) }
Output: error is errdetails.RequestInfoError with request ID "123456789"
type ResourceInfoError ¶
type ResourceInfoError interface { error details.ResourceInfo }
ResourceInfoError is an error that describes the resource that is being accessed.
func WithResource ¶
func WithResource(err error, info details.ResourceInfo) ResourceInfoError
WithResource wraps an error with information about the resource that is being accessed.
Example ¶
err := errdetails.WithResource(testErr, &detailspb.ResourceInfo{ ResourceType: "table", ResourceName: "public.shopify", Description: "No record exists in table for shopify URL", }) var resErr errdetails.ResourceInfoError if errors.As(err, &resErr) { fmt.Println("error is", reflect.ValueOf(&resErr).Elem().Type()) fmt.Printf("with resource (%s) %q: %s\n", resErr.GetResourceType(), resErr.GetResourceName(), resErr.GetDescription()) }
Output: error is errdetails.ResourceInfoError with resource (table) "public.shopify": No record exists in table for shopify URL
type RetriableError ¶
type RetriableError interface { error WithDelay(time.Duration) RetriableError GetRetryDelay() time.Duration }
RetriableError is an error that describes when a client may retry a failed request.
The retry delay represents a minimum duration in which the client is recommended to wait. It is always recommended the client should use exponential backoff when retrying.
func WithRetryDelay ¶
func WithRetryDelay(err error, delay time.Duration) RetriableError
WithRetryDelay wraps an error indicating that a client may retry a failed request after a delay recommended here.
It is always recommended that clients should use exponential backoff when retrying.
Example ¶
err := errdetails.WithRetryDelay(testErr, 10*time.Minute) var retErr errdetails.RetriableError if errors.As(err, &retErr) { fmt.Println("error is", reflect.ValueOf(&retErr).Elem().Type()) fmt.Println("with recommended delay:", retErr.GetRetryDelay()) }
Output: error is errdetails.RetriableError with recommended delay: 10m0s