errors

package
v0.13.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 20, 2018 License: Apache-2.0 Imports: 10 Imported by: 0

README

Atlas App Toolkit Error Handling

This document is a brief overview of facilities provided by error handling package. The rationale for implementing it are four noble reasons:

  1. Provide ability to add specific details and field information to an error.
  2. Provide ability to handle multiple errors without returning control to a callee.
  3. Ability to map errors from 3-rd party libraries (gorm, to name one).
  4. Mapping from error to container should be performed automatically in gRPC interceptor.

Error Container

Error container is a data structure that implements Error interface and GRPCStatus method, enabling passing it around as a conventional error from one side and as a protobuf Status to gRPC gateway from the other side.

There are several approaches exist to work with it:

  1. Single error mode
  2. Multiple errors mode
Single Error Return

This code snippet demonstrates the usage of error container as conventional error:

func validateNameLength(name string) error {
	if len(name) > 255 {
		return errors.NewContainer(
			codes.InvalidArgument, "Name validation error."
		).WithDetail(
			codes.InvalidArgument, "object", "Invalid name length."
		).WithField(
			"name", "Specify name with length less than 255.")
	}

	return nil
}
Gather Multiple Errors
func (svc *Service) validateName(name string) error {
	err := errors.InitContainer()
	if len(name) > 255 {
		err = err.WithDetail(codes.InvalidArgument, "object", "Invalid name length.")
		err = err.WithField("name", "Specify name with length less than 255.")
	}

	if strings.HasPrefix(name, "_") {
		err = err.WithDetail(codes.InvalidArgument, "object", "Invalid name.").WithField(
			"name", "Name cannot start with an underscore")
	}

	return err.IfSet(codes.InvalidArgument, "Invalid name.")
}

To gather multiple errors across several procedures use context functions:

func (svc *Service) globalValidate(ctx context.Context, input *pb.Input) error {
	svc.validateName(ctx, input.Name)
	svc.validateIP(ctx, input.IP)

	// in some particular cases we expect that something really bad
	// should happen, so we can analyse it and throw instead of validation errors.
	if err := validateNamePermExternal(svc.AuthInfo); err != nil {
		return errors.New(ctx, codes.Unauthorized, "Client is not authorized.").
			WithDetails(/* ... */)
	}

	return errors.IfSet(ctx, codes.InvalidArgument, "Overall validation failed.")
	// Alternatively if we want to return the latest errCode/errMessage set instead
	// of overwriting it:
	// return errors.Error(ctx)
}

func (svc *Service) validateName(ctx context.Context, name string) {
	if len(name) > 255 {
		errors.Detail(ctx, codes.InvalidArgument, "object", "Invalid name length.")
		errors.Field(ctx, "name", "Specify name with length less than 255.")
	}
}

func (svc *Service) validateIP(ctx context.Context, ip string) { /* ip validation */ }

Error Mapper

Error mapper performs conditional mapping from one error message to another. Error mapping functions are passed to a gRPC Error interceptor and called against error returned from handler.

Below we demonstrate a cases and customization techniques for mapping functions:

interceptor := errors.UnaryServerInterceptor(
	// List of mappings
	
	// Base case: simply map error to an error container.
	errors.NewMapping(fmt.Errorf("Some Error"), errors.NewContainer(/* ... */).WithDetail(/* ... */)),

	// Extended Condition, mapped if error message contains "fk_contraint" or starts with "pg_sql:"
	// MapFunc calls logger and returns Internal Error, depending on debug mode it could add details.
	errors.NewMapping(
		errors.CondOr(
			errors.CondReMatch("fk_constraint"),
			errors.CondHasPrefix("pg_sql:"),
		),
		errors.MapFunc(func (ctx context.Context, err error) (error, bool) {
			logger.FromContext(ctx).Debug(fmt.Sprintf("DB Error: %v", err))
			err := errors.NewContainer(codes.Internal, "database error")

			if InDebugMode(ctx) {
				err.WithDetail(/* ... */)
			}

			// boolean flag indicates whether the mapping succeeded
			// it can be used to emulate fallthrough behavior (setting false)
			return err, true
		})
	),

	// Skip error
	errors.NewMapping(fmt.Errorf("Error to Skip"), nil)
)

Such model allows us to define our own error classes and map them appropriately as in example below:


// service validation code.

type RequiredFieldErr string
(e RequiredFieldErr) Error() { return string(e) }

func RequiredFieldCond() errors.MapCond {
	return errors.MapCond(func(err error) bool {
		_, ok := err.(RequiredFieldErr)
		return ok
	})
}

func validateReqArgs(in *pb.Input) error {
	if in.Name == "" {
		return RequiredFieldErr("name")
	}

	return nil
}
// interceptor init code
interceptor := errors.UnaryServerInterceptor(
	errors.NewMapping(
		RequiredFieldCond(),
		errors.MapFunc(func(ctx context.Context, err error) (error, bool) {
			return errors.NewContainer(
				codes.InvalidArgument, "Required field missing: %v", err
			).WithField(string(err), "%q argument is required.", string(err)
			), true
		}),
	)
)

Documentation

Overview

The Error Container entity serves a purpose for keeping track of errors, details and fields information. This component can be used explicitly (for example when we want to sequentially fill it with details and fields), as well as implicitly (all errors that are returned from handler are transformed to an Error Container, and passed as GRPCStatus to a gRPC Gateway).

Index

Constants

View Source
const (
	// Context key for Error Container.
	DefaultErrorContainerKey = "Error-Container"
)

Variables

This section is empty.

Functions

func Error

func Error(ctx context.Context) error

Error function returns an error container if any error field, detail or message was set, else it returns nil. Use New to define and return error message in place.

func IfSet

func IfSet(ctx context.Context, code codes.Code, format string, args ...interface{}) error

IfSet function intializes general error code and error message for context stored error container if and onyl if any error was set previously by calling Set, WithField(s), WithDetails(s).

func Map

func Map(ctx context.Context, err error) error

Map function performs mapping based on context stored error container's mapping configuration.

func NewContext

func NewContext(ctx context.Context, c *Container) context.Context

NewContext function creates a context with error container saved in it.

func UnaryServerInterceptor

func UnaryServerInterceptor(mapFuncs ...MapFunc) grpc.UnaryServerInterceptor

UnaryServerInterceptor returns grpc.UnaryServerInterceptor that should be used as a middleware to generate Error Messages with Details and Field Information with Mapping given.

Types

type Container

type Container struct {

	// Mapper structure performs necessary mappings.
	Mapper
	// contains filtered or unexported fields
}

Container struct is an entity that servers a purpose of error container and consist of methods to append details, field errors and setting general error code/message.

func Detail

func Detail(ctx context.Context, code codes.Code, target string, format string, args ...interface{}) *Container

Detail function appends a new detail to a context stored error container's 'details' section.

func Details

func Details(ctx context.Context, details ...*errdetails.TargetInfo) *Container

Details function appends a list of details to a context stored error container's 'details' section.

func Field

func Field(ctx context.Context, target string, format string, args ...interface{}) *Container

Field function appends a field error detail to a context stored error container's 'fields' section.

func Fields

func Fields(ctx context.Context, fields map[string][]string) *Container

Fields function appends a multiple fields error details to a context stored error container's 'fields' section.

func FromContext

func FromContext(ctx context.Context) *Container

FromContext function retrieves an error container value from context.

func InitContainer

func InitContainer() *Container

func New

func New(ctx context.Context, code codes.Code, format string, args ...interface{}) *Container

New function resets any error that was inside context stored error container and replaces it with a new error.

func NewContainer

func NewContainer(code codes.Code, format string, args ...interface{}) *Container

NewContainer function returns a new entity of error container.

func Set

func Set(ctx context.Context, target string, code codes.Code, format string, args ...interface{}) *Container

Set function initializes a general error code and error message for context stored error container and also appends a details with the same content to an error container's 'details' section.

func (Container) Error

func (c Container) Error() string

Error function returns error message currently associated with container.

func (*Container) GRPCStatus

func (c *Container) GRPCStatus() *status.Status

GRPCStatus function returns an error container as GRPC status.

func (*Container) IfSet

func (c *Container) IfSet(code codes.Code, format string, args ...interface{}) error

IfSet function initializes general error code and error message for error container if and only if any error was set previously by calling Set, WithField(s), WithDetail(s).

func (*Container) IsSet

func (c *Container) IsSet() bool

IsSet function returns flag that determines whether the main error code and error message were set or not.

func (*Container) New

func (c *Container) New(code codes.Code, format string, args ...interface{}) *Container

New function instantinates general error code and error message for error container.

func (*Container) Set

func (c *Container) Set(target string, code codes.Code, format string, args ...interface{}) *Container

Set function initializes general error code and error message for error container and also appends a detail with the same content to a an error container's 'details' section.

func (*Container) WithDetail

func (c *Container) WithDetail(code codes.Code, target string, format string, args ...interface{}) *Container

WithDetail function appends a new Detail to an error container's 'details' section.

func (*Container) WithDetails

func (c *Container) WithDetails(details ...*errdetails.TargetInfo) *Container

WithDetails function appends a list of error details to an error container's 'details' section.

func (*Container) WithField

func (c *Container) WithField(target string, format string, args ...interface{}) *Container

WithField function appends a field error detail to an error container's 'fields' section.

func (*Container) WithFields

func (c *Container) WithFields(fields map[string][]string) *Container

WithFields function appends a several fields error details to an error container's 'fields' section.

type MapCond

type MapCond func(error) bool

MapCond function takes an error and returns flag that indicates whether the map condition was met.

func CondAnd

func CondAnd(mcs ...MapCond) MapCond

CondAnd function takes a list of condition function as an input and returns a function that asserts true if and only if all conditions are satisfied.

func CondEq

func CondEq(src string) MapCond

CondEq function takes a string as an input and returns a condition function that checks whether the error is equal to a string given.

func CondHasPrefix

func CondHasPrefix(prefix string) MapCond

CondHasPrefix function takes a string as an input and returns a condition function that checks whether the error starts with the string given.

func CondHasSuffix

func CondHasSuffix(suffix string) MapCond

CondHasSuffix function takes a string as an input and returns a condition function that checks whether the error ends with the string given.

func CondNot

func CondNot(mc MapCond) MapCond

CondNot function takes a condtion function as an input and returns a function that asserts inverse result.

func CondOr

func CondOr(mcs ...MapCond) MapCond

CondOr function takes a list of condition function as an input and returns a function that asserts true if at least one of conditions is satisfied.

func CondReMatch

func CondReMatch(pattern string) MapCond

CondReMatch function takes a string regexp pattern as an input and returns a condition function that checks whether the error matches the pattern given.

func (MapCond) Error

func (mc MapCond) Error() string

Error function ...

type MapFunc

type MapFunc func(context.Context, error) (error, bool)

MapFunc function takes an error and returns mapped error and flag that indicates whether the mapping was performed successfully.

func NewMapping

func NewMapping(src error, dst error) MapFunc

NewMapping function creates a mapping function based on error interfaces passed to it. src can be either MapCond and dst can be MapFunc.

func (MapFunc) Error

func (mc MapFunc) Error() string

Error function ...

type Mapper

type Mapper struct {
	// contains filtered or unexported fields
}

Mapper struct ...

func (*Mapper) AddMapping

func (m *Mapper) AddMapping(mf ...MapFunc) *Mapper

AddMapping function appends a list of mapping functions to a mapping chain.

func (*Mapper) Map

func (m *Mapper) Map(ctx context.Context, err error) error

Map function performs a mapping from error given following a chain of mappings that were defined prior to the Map call.

Directories

Path Synopsis
mappers

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL