Documentation ¶
Overview ¶
Package gerrors helps to have rich and cohesive error handling throughout the code base. This package helps with formatting the final error message and all the error's metadata that can be used by different parties to understand the error.
gerrors can be used by any package for simply handling and logging errors with detailed information. Furthermore, it can be used whenever a client is involved where a general message need to be shown to the end user, but more information about the error need to be shared with the client. Error types of gerror have metadata attached to them that can help with this.
Every gerror type can be easily converted to a fully fledged gRPC error type which follows Google's AIP 193 that includes gRPC error message and status, alongside more information regarding the error represented by error details protocol buffer message. In other words, this package can be used to send comprehensive structured error information to the receiver through gRPC, REST, or any other protocol.
Formatter ¶
This package has a default formatter, but there is possibility to customize every aspect of it. NewFormatter accepts a variadic number of FormatterOption which have some helper functions to customize the formatter and has been explained in their section.
Formatter uses text/template to generate the final error message. It uses a default template, which can be customized using WithTemplate helper function. More information on the available variables have been explained in helper's documentation.
gRPC ¶
gerrors defines a set of default error codes that can translate to different error messages and different gRPC error codes. These default error codes have been defined to help using the package without much customization. However, they can be easily customized using WithCustomCoreCallback helper function.
Example ¶
package main import ( "errors" "fmt" "github.com/seinshah/gerrors" "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/status" ) func main() { // Use default formatter and print error message using the default template. err := gerrors.DefaultFormatter.New(errors.New("error"), gerrors.Unknown, "key", "value") fmt.Println(err.Error()) // Use default formatter and get gRPC status error from gerrors. gerr := gerrors.DefaultFormatter.New(errors.New("error"), gerrors.Unknown, "key", "value").Grpc() st, ok := status.FromError(gerr) if !ok { fmt.Println("error converting to gRPC status error") return } fmt.Println(st.Message()) if len(st.Details()) != 1 { fmt.Println("converted error to gRPC status error was not of type gerrors.GeneralError") return } details, ok := st.Details()[0].(*errdetails.ErrorInfo) if !ok { fmt.Println("converted error to gRPC status error was not of type gerrors.GeneralError") return } fmt.Println(details.GetMetadata()) }
Output:
Index ¶
- Constants
- Variables
- func GetDefaultMapping() map[Code]CoreError
- func GrpcError(err error) error
- type Code
- type CoreError
- type CoreGRPCError
- type Formatter
- func (f *Formatter) AddLabels(keyValues ...any) *Formatter
- func (f *Formatter) Clone() *Formatter
- func (f *Formatter) LabelsMap() map[string]string
- func (f *Formatter) LabelsSlice() []string
- func (f *Formatter) MissingValueReplacement() (string, bool)
- func (f *Formatter) New(inputErr error, code Code, metadataKeyValues ...any) *GeneralError
- func (f *Formatter) NewWithLogLevel(inputErr error, code Code, level LogLevel, metadataKeyValues ...any) *GeneralError
- type FormatterOption
- func WithDisabledMissingValueReplacement() FormatterOption
- func WithLabels(keyValues ...any) FormatterOption
- func WithLogger(logger logger) FormatterOption
- func WithLookuper(l Lookuper) FormatterOption
- func WithMissingValueReplacement(replacement string) FormatterOption
- func WithTemplate(templateString string) FormatterOption
- type GeneralError
- type LogLevel
- type Lookuper
- type Mapper
Examples ¶
Constants ¶
const ( // MetadataIdentifier is the ket for accessing the error identifier. MetadataIdentifier = "_identifier" // MetadataErrorCode is the key for accessing the gerrors internal // or customized error code. MetadataErrorCode = "_error_code" // MetadataDefaultMessage is the key for accessing the default gerrors core // error message. MetadataDefaultMessage = "_default_message" // MetadataOriginalError is the key for accessing the original error // which was used during initializing GeneralError. MetadataOriginalError = "_original_error" )
Variables ¶
var ( // DefaultFormatter holds a ready to use formatter with the default options. // DefaultFormatter uses the default template with no pre-defined labels and // no logger. It replaces invalid values with the missingValueReplacement. // DefaultFormatter uses defaultCoreCallback function for translating error // codes to human and machine readable strings and information. DefaultFormatter = NewFormatter() )
Functions ¶
func GetDefaultMapping ¶
GetDefaultMapping returns a map that contains translation between package's default error codes to detailed information. These information can be customized. Check Formatter and WithLookuper for more information.
func GrpcError ¶
GrpcError accepts an error of gerrors.GeneralError type and returns a gRPC error by translating the error to gRPC error and attach all labels as the metadata. It supports Google's AIP 193. If the input is not of GeneralError type, it smply returns a gRPC error with google.golang.org/grpc/codes.Unknown error code with the input error message as the message. If the receiver is receiving the error in gRPC error format, you can check this blog post on how to parse the error and extract the information from it.
Types ¶
type Code ¶
type Code int
Code is gerrors internal error type. If a customized core call back function is used, customized error codes should be of this type as well.
const ( // Unknown generates an unhandled error and is useful whenever the error // we want to generate is unknown to us. // It translates to GRPC Unknown codes.Code. // If Grpc method is called on an error that i not of gerrors type, // this error code will be used to convert the error to gerrors, // regardless of whether a custom core callback is provided or not. Unknown Code = iota + 1 // NotFound generate a not found error and is useful whenever some database // or similar lookup fails because of no record. // It translates to GRPC NotFound codes.Code. NotFound // InvalidArgument generates an invalid argument error and is useful whenever // the provided argument by caller is not of a valid type and therefore cannot // be processed. // It translates to GRPC InvalidArgument codes.code. InvalidArgument // Marshal generates a marshaling error and is useful whenever there is an error // related to marshaling or unmarshaling of some data to some other data. // It translates to GRPC Internal codes.Code. Marshal // Storage generates a storage error and is useful for whenever there is an error // because of some storage operation which could be a file-system, database, redis, // etc. // It translates to GRPC Internal codes.Code. Storage // Threshold generates an out of range error and is useful for whenever some // received argument is out of your expected range. The value is still a valid // type and format, but out of your expected range. // It translates to GRPC OutOfRange codes.code. Threshold // Unimplemented generate an unimplemented error and is useful for whenever // there is a request from user that has not been implemented yet. // This error type is used by GRPC internal packages in some special cases as well. // It translates to GRPC Unimplemented codes.code. Unimplemented // the request is not permitted for the requester. It might be that there // is no authorized user in the context or header at all or that user is not // allowed to perform the operation in question. // It translates to grpc Unauthenticated codes.Code. Unauthorized // Internal generates an internal error and is useful for whenever there is // an error that is related to non user-facing error. System is solely responsible // for causing these kind of errors. // It translates to GRPC Internal codes.Code. Internal // the requested action is not available to the requester. // It translates to GRPC Unavailable codes.Code. Unavailable // ExternalRequest generates an internal error and is useful for whenever // system fails when calling a third-party service. The third-party service // can be within organization or outside of organization. // It translates to GRPC Internal codes.Code. ExternalRequest )
type CoreError ¶
type CoreError interface { // GetInternalCode returns the gerrors internal code of type Code. GetInternalCode() Code // GetIdentifier returns a human-readable that explains the code // in words. (one or two words) GetIdentifier() string // GetDefaultMessage returns a default longer message that explains // the error code. If a new error is being created with a nil initial // error, this default message will be used to explain the error. GetDefaultMessage() string }
CoreError is an interface that every error mapper should implement. Each mapper for an error maps that error code to the rest of error's information. That information should implement this interface to make error's data accessible.
type CoreGRPCError ¶
type CoreGRPCError interface { // GetGRPCCode returns a gRPC error code that can be matched to // an internal gerrors error code. GetGRPCCode() codes.Code }
CoreGRPCError can provide support for gRPC error messages. If the provided error mapper implements this interface, the error can be converted to a gRPC error.
type Formatter ¶
type Formatter struct {
// contains filtered or unexported fields
}
Formatter is the main component of this package which provide handlers to create new errors. Formatter controls how every error will be parsed and formatted. Errors generated by a single formatter, will have a certain set of similar behaviors. These behaviors can be customized while creating a new formatter.
func NewFormatter ¶
func NewFormatter(opts ...FormatterOption) *Formatter
NewFormatter creates a new formatter with the default options. Check DefaultFormatter for more information on the default options. It accepts a variadic number of FormatterOptions for customizing the returned formatter. Check helper functions that returns FormatterOption for more information.
Example ¶
package main import ( "errors" "fmt" "github.com/seinshah/gerrors" ) func main() { f := gerrors.NewFormatter() err := f.New(errors.New("error"), gerrors.Unknown, "key", "value") fmt.Println(err.Error()) }
Output:
Example (WithOptions) ¶
package main import ( "errors" "fmt" "github.com/seinshah/gerrors" "google.golang.org/grpc/codes" ) type CustomCoreError struct{} func main() { f := gerrors.NewFormatter( // Error method now returns an output populated based on this template. gerrors.WithTemplate( "custom: {{.Identifier}}({{.ErrorCode}}) - {{.DefaultMessage}} - {{.GrpcErrorCode}} - {{.Labels}}", ), // Any error we create on this formatter uses this lookuper. // Due to our implementation here, since the unknown error code is // 100 and no other code mapping has defined, any error you pass // will be translated to the information of error code 100. gerrors.WithLookuper( gerrors.NewMapper(gerrors.Code(100), map[gerrors.Code]gerrors.CoreError{ gerrors.Code(100): CustomCoreError{}, }), ), // If we pass key values to the formatter or during error creation, // and the value is missing (uneven number of parameters), that key // will be completely ignored and won't be added to the error's labels. gerrors.WithDisabledMissingValueReplacement(), // Any error created using this formatter or any clone from this formatter // will have this key value in its labels. // Because of previous option, ignored key will be ignored from error's labels. gerrors.WithLabels("always", true, "ignored"), ) err := f.New(errors.New("error"), gerrors.Unknown, "key", "value") fmt.Println(err.Error()) } func (CustomCoreError) GetGRPCCode() codes.Code { return codes.Internal } func (CustomCoreError) GetInternalCode() gerrors.Code { return gerrors.Code(100) } func (CustomCoreError) GetDefaultMessage() string { return "custom core error" } func (CustomCoreError) GetIdentifier() string { return "custom" }
Output: custom: custom(100) - custom core error - 13 - map[_default_message:custom core error _error_code:100 _identifier:custom _original_error:error always:true key:value]
func (*Formatter) AddLabels ¶
AddLabels adds a set of labels to the formatter. keyValues should be pairs of data, where the first element is a key and must be a string and follows [maxKeyLength] and [keyRE]. The second element is the value and will be converted to string. If value is missing [missingValueReplacement] and [allowMissingValue] are used to decide how to handle it. If the key has invalid characters or is too long, it will be modified to a valid key.
func (*Formatter) Clone ¶
Clone returns a copy of the formatter. Any change to this copy is safe since it would not change the original formatter. The global formatter can be cloned whenever you enter a new scope to be customized for that scope only.
Example ¶
package main import ( "errors" "fmt" "github.com/seinshah/gerrors" "google.golang.org/grpc/codes" ) type CustomCoreError struct{} func main() { f := gerrors.NewFormatter( gerrors.WithTemplate("{{.Labels}}"), gerrors.WithLabels("override", "main", "remains", "yes"), gerrors.WithLookuper( gerrors.NewMapper(gerrors.Code(100), map[gerrors.Code]gerrors.CoreError{ gerrors.Code(100): CustomCoreError{}, }), ), ) f2 := f.Clone() f2.AddLabels("override", "cloned", "new", true) err := f2.New(errors.New("error"), gerrors.Unknown, "key", "value") fmt.Println(err.Error()) } func (CustomCoreError) GetGRPCCode() codes.Code { return codes.Internal } func (CustomCoreError) GetInternalCode() gerrors.Code { return gerrors.Code(100) } func (CustomCoreError) GetDefaultMessage() string { return "custom core error" } func (CustomCoreError) GetIdentifier() string { return "custom" }
Output: map[_default_message:custom core error _error_code:100 _identifier:custom _original_error:error key:value new:true override:cloned remains:yes]
func (*Formatter) LabelsMap ¶
LabelsMap returns formatter's default labels as a map. To have a slice of labels, use LabelsSlice.
func (*Formatter) LabelsSlice ¶
LabelsSlice return formatter's default labels as a slice. Even indexed elements are keys and odd indexed elements are values sequentially attached to the slice. To have a map of labels, use LabelsMap.
func (*Formatter) MissingValueReplacement ¶
MissingValueReplacement returns whether replacing missing values is allowed or not. And if it is allowed, what will be replaced for missing values.
func (*Formatter) New ¶
func (f *Formatter) New(inputErr error, code Code, metadataKeyValues ...any) *GeneralError
New creates a new GeneralError instance using the provided formatter. inputErr is the error that is triggered prior to the creation of the error and it can be nil. If it's nil, the final error message will be the code's default message. Any new error can have a list of key values as the metadata. These key values will be appended to the formatter's default labels. If the formatter has a logger, it will also log the error at Error level.
func (*Formatter) NewWithLogLevel ¶
func (f *Formatter) NewWithLogLevel( inputErr error, code Code, level LogLevel, metadataKeyValues ...any, ) *GeneralError
NewWithLogLevel is the same as New, but it allows the caller to control the log level. If the logger is not provided to the formatter, this method is exactly the same as New where logging will be ignored.
type FormatterOption ¶
type FormatterOption func(*Formatter)
FormatterOption is the approach for customizing the formatter. Every option used during creation of a new formatter is of this type. Most of the functions starting with "With" are helpers to customize the formatter.
func WithDisabledMissingValueReplacement ¶
func WithDisabledMissingValueReplacement() FormatterOption
WithDisabledMissingValueReplacement allows user to disallow replacing missing values. In this case, a label with missing or invalid value will be ignored and not added to the error.
func WithLabels ¶
func WithLabels(keyValues ...any) FormatterOption
WithLabels add a set of default labels to the formatter. All these labels will be included in every error generated by the formatter. It can be used to group errors together in a function scope or a call scope.
func WithLogger ¶
func WithLogger(logger logger) FormatterOption
WithLogger attach a logger to the formatter. provided logger should at least implement the [logger] interface. If the provide logger implements other type of loggers as well (e.g [infoLogger], [traceLogger], ...), we can control how the created error should be logged by the formatter. If formatter is not configured with a logger or if the logger does not implement the provided logger, formatter simply ignore logging the error.
func WithLookuper ¶
func WithLookuper(l Lookuper) FormatterOption
WithLookuper customizes the default mapper that translates Code to CoreError. Using this formatter option, it is possible to completely revamp all the Code constants from acceptable error code and define a new set of error codes and a new mapper that can translate these new error codes to more details.
func WithMissingValueReplacement ¶
func WithMissingValueReplacement(replacement string) FormatterOption
WithMissingValueReplacement allows the formatter to accepts missing values when labels are being added to the formatter. In case a value is missing, or is invalid, formatter will replace it with this replacement string. Replacing missing values is allowed by default.
func WithTemplate ¶
func WithTemplate(templateString string) FormatterOption
WithTemplate customizes formatter defaultTemplate. This template should follow text/template syntax. Function panics if template is invalid. Supported variables are:
{{.Identifier}}: the identifier of the error. (e.g. unavailable, internal, ...)
{{.ErrorCode}}: core error code. (e.g. 1, 2, ...)
{{.GrpcErrorCode}}: grpc error code. (e.g. 2, 5, ...)
{{.Message}}: the message of the provided error or default message of the error code.
{{.DefaultMessage}}: the default message of the error code.
{{.Labels}}: formatter's label plus error-specific labels. Treat it as a map.
f := NewFormatter(WithTemplate("error: {{.Identifier}}(code {{.ErrorCode}}) - {{.Message}}"))
type GeneralError ¶
type GeneralError struct {
// contains filtered or unexported fields
}
GeneralError is the error type defined, controlled, and handled by gerrors package.
func (*GeneralError) Error ¶
func (ge *GeneralError) Error() string
Error allows GeneralError to implement the error interface. It uses the formatter template and different information of the GeneralError to generate the error message.
func (*GeneralError) Grpc ¶
func (ge *GeneralError) Grpc() error
Grpc is the method defined on GeneralError which returns the gRPC error. See Grpc function for more details.
func (*GeneralError) Metadata ¶
func (ge *GeneralError) Metadata() map[string]string
Metadata returns all the combined labels of the given GeneralError.
func (*GeneralError) MetadataSlice ¶
func (ge *GeneralError) MetadataSlice() []any
type LogLevel ¶
type LogLevel int
LogLevel is used while creating new error with NewWithLogLevel method. If the formatter is configured to have a logger and it's logger implements the proper log level interface, error will be logged at the provided level.
type Lookuper ¶
Lookuper is an interface that shows how a mapper should be implemented. Every mapper should have a lookup method to translate Code to CoreError.
type Mapper ¶
type Mapper struct {
// contains filtered or unexported fields
}
Mapper is the data type that maps gerrors error code to the core error which holds more information about the error code. This type will be used by different methods to translate the error code to the underlying error details.
func NewMapper ¶
NewMapper initiates the Mapper with all available one-to-one mapping information from an error code to error details. mapping is a map that maps the Code to CoreError. This can be customized based on your needs. unknownErrorCode will be used whenever mapper cannot translate a Code to CoreError and this unknown code is used as a fallback.