errors

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: May 6, 2023 License: BSD-3-Clause Imports: 9 Imported by: 11

README

errors PkgGoDev

General purpose error handling package with few extra bells and whistles for HTTP interop.

Table of contents

Usage

import "github.com/sudo-suhas/xgo/errors"
Creating errors

To facilitate error construction, the package provides a function, errors.E which build an *Error from its (functional) options:

func E(opt Option, opts ...Option) error

In typical use, calls to errors.E will arise multiple times within a method, so a constant called op could be defined that will be passed to all E calls within the method:

func (b *binder) Bind(r *http.Request, v interface{}) error {
	const op = "binder.Bind"

	if err := b.Decode(r, v); err != nil {
		return errors.E(errors.WithOp(op), errors.InvalidInput, errors.WithErr(err))
	}

	if err := b.Validate.Struct(v); err != nil {
		return errors.E(errors.WithOp(op), errors.InvalidInput, errors.WithErr(err))
	}

	return nil
}

The error string, for the example above, would look something like this:

binder.Bind: invalid input: json: cannot unmarshal number into Go struct field .Name of type string
Adding context to an error

A new error wrapping the original error is returned by passing it in to the constructor. Additional context can include the following (optional):

if err := svc.SaveOrder(o); err != nil {
	return errors.E(errors.WithOp(op), errors.WithErr(err))
}
Inspecting errors

The error Kind can be extracted using the function errors.WhatKind(error):

if errors.WhatKind(err) == errors.NotFound {
	// ...
}

With this, it is straightforward for the app to handle the error appropriately depending on the classification, such as a permission error or a timeout error.

There is also errors.Match which can be useful in tests to compare and check only the properties which are of interest. This allows to easily ignore the irrelevant details of the error.

func Match(template, err error) bool

The function checks whether the error is of type *errors.Error, and if so, whether the fields within equal those within the template. The key is that it checks only those fields that are non-zero in the template, ignoring the rest.

if errors.Match(errors.E(errors.WithOp("service.MakeBooking"), errors.PermissionDenied), err) {
	// ...
}

The functions errors.Is and errors.As, which were introduced in Go 1.13, can also be used. These functions are defined in the package for the sake of convenience and delegate to the standard library. This way, importing both this package and the errors package from the standard library should not be necessary and avoids having to work around the name conflict on importing both.

// For an error returned from the the database client
// 	return errors.E(errors.WithOp(op), errors.WithErr(err))
if errors.Is(err, sql.ErrNoRows) {
	// ...
}

// In the API client
var terr interface{ Timeout() bool }
if errors.As(err, &terr) && terr.Timeout() {
	// ...
}
Errors for the end user

Errors have multiple consumers, the end user being one of them. However, to the end user, the error text, root cause etc. are not relevant (and in most cases, should not be exposed as it could be a security concern).

To address this, Error has the field UserMsg which is intended to be returned/shown to the user. errors.WithUserMsg(string) stores the message into the aforementioned field. And it can be retrieved using errors.UserMsg

func UserMsg(err error) string

Example:

// CreateUser creates a new user in the system.
func (s *Service) CreateUser(ctx context.Context, user *myapp.User) error {
	const op = "svc.CreateUseer"

	// Validate username is non-blank.
	if user.Username == "" {
		msg := "Username is required"
		return errors.E(errors.WithOp(op), errors.InvalidInput, errors.WithUserMsg(msg))
	}

	// Verify user does not already exist
	if s.usernameInUse(user.Username) {
		msg := "Username is already in use. Please choose a different username."
		return errors.E(errors.WithOp(op), errors.AlreadyExists, errors.WithUserMsg(msg))
	}

	// ...
}

// Elsewhere in the application, for responding with the error to the user
if msg := errors.UserMsg(err); msg != "" {
	// ...
}

Translating the error to a meaningful HTTP response is convered in the section HTTP interop - Response body.

HTTP interop
Status code

errors is intended to be a general purpose error handling package. However, it includes minor extensions to make working with HTTP easier. The idea is that its there if you need it but doesn't get in the way if you don't.

errors.Kind is composed of the error code and the HTTP status code.

type Kind struct {
	Code   string
	Status int
}

Rationale for the design of Kind is documented in decision-log.md

The Kind variables defined in the errors package include mapping to the appropriate HTTP status code. For example, InvalidInput maps to 400: Bad Request while NotFound maps to 404: Not Found.

Therefore, provided an error has an appropriate Kind associated, it is really simple to translate the same to the HTTP status code using errors.StatusCode

func StatusCode(err error) int

Conversely, errors.KindFromStatus can be used to translate the HTTP response code from an external REST API to the Kind.

func KindFromStatus(status int) Kind

The utility function errors.WithResp(*http.Response), which uses KindFromStatus, makes it possible to construct an error from *http.Response with contextual information of the request and response.

Response body

Another concern of web applications is returning a meaningful response in case there is an error. *Error implements the xgo.JSONer interface which can be leveraged for building the error response.

var ej xgo.JSONer
if errors.As(err, &ej) {
	j := ej.JSON()
	// ...
}

For example, consider the following error:

msg := "The requested resource was not found."
errors.E(errors.WithOp("Get"), errors.NotFound, errors.WithUserMsg(msg))

This produces the following JSON representation:

{
	"code": "NOT_FOUND",
	"error": "not found",
	"msg": "The requested resource was not found."
}

If needed, this behaviour can easily be tweaked by using errors.WithToJSON(errors.JSONFunc). It expects a function of type errors.JSONFunc

type JSONFunc func(*Error) interface{}

Simple example illustrating the usage of errors.WithToJSON:

func makeErrToJSON(lang string) errors.JSONFunc {
	return func(e *errors.Error) interface{} {
		msgKey := errors.UserMsg(e)
		return map[string]interface{}{
			"code":    errors.WhatKind(e).Code,
			"title":   i18n.Title(lang, msgKey),
			"message": i18n.Message(lang, msgKey),
		}
	}
}

// In the HTTP handler, when there is an error, wrap it and pass it to the
// response formatter.
errors.E(errors.WithToJSON(makeErrToJSON(lang)), errors.WithErr(err))

It is recommended to pass in the JSONFunc to the error only once; i.e the error should not be wrapped repeatedly with virtually the same function passed into errors.WithToJSON. The reason is that functions are not comparable in Go and hence cannot be de-duplicated for keeping the error chain as short as possible. The obvious exception is when we want to override any ToJSON which might have been supplied in calls to errors.E further down the stack

Logging errors

Logs are meant for a developer or an operations person. Such a person understands the details of the system and would benefit from being to see as much information related to the error as possible. This includes the logical stack trace (to help understand the program flow), error code, error text and any other contextual information associated with the error.

The error string by default includes most of the information but would not be ideal for parsing by machines and is therefore less friendly to structured search.

*Error.Details() constructs and yields the details of the error, by traversing the error chain, in a structure which is suitable to be parsed.

type InternalDetails struct {
	Ops   []string    `json:"ops,omitempty"`
	Kind  Kind        `json:"kind,omitempty"`
	Error string      `json:"error"`
	Data  interface{} `json:"data,omitempty"`
}

With respect to stack traces, dumping a list of every function in the call stack from where an error occurred can many times be overwhelming. After all, for understanding the context of an error, only a small subset of those lines is needed. A logical stack trace contains only the layers that we as developers find to be important in describing the program flow.

Errors package objectives

  • Composability: Being able to compose an error using a different error and optionally associate additional contextual information/data.
  • Classification: Association of error type/classification and being able to test for the same.
  • Flexibility: Create errors with only the information deemed necessary, almost everything is optional.
  • Interoperability:
    • HTTP: Map errors classification to HTTP status code to encompass the response code in the error.
    • Traits: Define and use traits such as GetKind, StatusCoder to allow interoperability with custom error definitions.
  • Descriptive: Facilitate reporting the sequence of events, with relevant contextual information, that resulted in a problem.

    Carefully constructed errors that thread through the operations in the system can be more concise, more descriptive, and more helpful than a simple stack trace.

Documentation

Overview

Package errors is a general purpose error handling package, with few extra bells and whistles for HTTP interop.

Creating errors

To facilitate error construction, the package provides a function, errors.E which build an *Error from its (functional) options.

func E(opt Option, opts ...Option) error

In typical use, calls to errors.E will arise multiple times within a method, so a constant called op could be defined that will be passed to all E calls within the method:

func (b *binder) Bind(r *http.Request, v interface{}) error {
	const op = "binder.Bind"

	if err := b.Decode(r, v); err != nil {
		return errors.E(errors.WithOp(op), errors.InvalidInput, errors.WithErr(err))
	}

	if err := b.Validate.Struct(v); err != nil {
		return errors.E(errors.WithOp(op), errors.InvalidInput, errors.WithErr(err))
	}

	return nil
}

Adding context to an error

A new error wrapping the original error is returned by passing it in to the constructor. Additional context can include the following (optional):

  • errors.WithOp(string): Operation name being attempted.
  • errors.Kind: Error classification.
  • errors.WithText(string): Error string. errors.WithTextf(string, ...interface{}) can also be used to format the error string with additional arguments.
  • errors.WithData(interface{}): Arbitrary value which could be considered relevant to the error.

Example:

if err := svc.SaveOrder(o); err != nil {
	return errors.E(errors.WithOp(op), errors.WithErr(err))
}

Inspecting errors

The error Kind can be extracted using the function WhatKind(error):

if errors.WhatKind(err) == errors.NotFound {
	// ...
}

With this, it is straightforward for the app to handle the error appropriately depending on the classification, such as a permission error or a timeout error.

There is also Match which can be useful in tests to compare and check only the properties which are of interest. This allows to easily ignore the irrelevant details of the error.

The function checks whether the error is of type *Error, and if so, whether the fields within equal those within the template. The key is that it checks only those fields that are non-zero in the template, ignoring the rest.

if errors.Match(errors.E(errors.WithOp("service.MakeBooking"), errors.PermissionDenied), err) {
	// ...
}

Errors for the end user

Errors have multiple consumers, the end user being one of them. However, to the end user, the error text, root cause etc. are not relevant (and in most cases, should not be exposed as it could be a security concern).

To address this, Error has the field UserMsg which is intended to be returned/shown to the user. WithUserMsg(string) stores the message into the aforementioned field. And it can be retrieved using errors.UserMsg.

Example:

// CreateUser creates a new user in the system.
func (s *Service) CreateUser(ctx context.Context, user *myapp.User) error {
	const op = "svc.CreateUseer"

	// Validate username is non-blank.
	if user.Username == "" {
		msg := "Username is required"
		return errors.E(errors.WithOp(op), errors.InvalidInput, errors.WithUserMsg(msg))
	}

	// Verify user does not already exist
	if s.usernameInUse(user.Username) {
		msg := "Username is already in use. Please choose a different username."
		return errors.E(errors.WithOp(op), errors.AlreadyExists, errors.WithUserMsg(msg))
	}

	// ...
}

// Elsewhere in the application, for responding with the error to the user
if msg := errors.UserMsg(err); msg != "" {
	// ...
}

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// Unknown error. An example of where this error may be returned is
	// if a Status value received from another address space belongs to
	// an error-space that is not known in this address space. Also
	// errors raised by APIs that do not return enough error information
	// may be converted to this error.
	Unknown = Kind{}

	// InvalidInput indicates client specified an invalid input.
	// Note that this differs from FailedPrecondition. It indicates arguments
	// that are problematic regardless of the state of the system
	// (e.g., a malformed file name).
	InvalidInput = Kind{
		Code:   "INVALID_INPUT",
		Status: http.StatusBadRequest,
	}

	// Unauthenticated indicates the request does not have valid
	// authentication credentials for the operation.
	Unauthenticated = Kind{
		Code:   "UNAUTHENTICATED",
		Status: http.StatusUnauthorized,
	}

	// PermissionDenied indicates the caller does not have permission to
	// execute the specified operation. It must not be used for rejections
	// caused by exhausting some resource (use ResourceExhausted instead
	// for those errors). It must not be used if the caller cannot be
	// identified (use Unauthenticated instead for those errors).
	PermissionDenied = Kind{
		Code:   "PERMISSION_DENIED",
		Status: http.StatusForbidden,
	}

	// NotFound means some requested entity (e.g., file or directory) was
	// not found.
	NotFound = Kind{
		Code:   "NOT_FOUND",
		Status: http.StatusNotFound,
	}

	// Conflict indicates the request conflicts with the current state
	// of the server.
	Conflict = Kind{
		Code:   "CONFLICT",
		Status: http.StatusConflict,
	}

	// FailedPrecondition indicates operation was rejected because the
	// system is not in a state required for the operation's execution.
	// For example, directory to be deleted may be non-empty, an rmdir
	// operation is applied to a non-directory, etc.
	//
	// A litmus test that may help a service implementor in deciding
	// between FailedPrecondition and Unavailable:
	//  (a) Use Unavailable if the client can retry just the failing call.
	//  (b) Use FailedPrecondition if the client should not retry until
	//      the system state has been explicitly fixed. E.g., if an "rmdir"
	//      fails because the directory is non-empty, FailedPrecondition
	//      should be returned since the client should not retry unless
	//      they have first fixed up the directory by deleting files from it.
	//  (c) Use FailedPrecondition if the client performs conditional
	//      REST Get/Update/Delete on a resource and the resource on the
	//      server does not match the condition. E.g., conflicting
	//      read-modify-write on the same resource.
	FailedPrecondition = Kind{
		Code:   "FAILED_PRECONDITION",
		Status: http.StatusPreconditionFailed,
	}

	// ResourceExhausted indicates some resource has been exhausted, perhaps
	// a per-user quota, or perhaps the entire file system is out of space.
	ResourceExhausted = Kind{
		Code:   "RESOURCE_EXHAUSTED",
		Status: http.StatusTooManyRequests,
	}

	// Internal errors. Means some invariants expected by underlying
	// system has been broken. If you see one of these errors,
	// something is very broken.
	Internal = Kind{
		Code:   "INTERNAL",
		Status: http.StatusInternalServerError,
	}

	// Canceled indicates the operation was canceled (typically by the caller).
	Canceled = Kind{
		Code:   "CANCELED",
		Status: http.StatusInternalServerError,
	}

	// Unimplemented indicates operation is not implemented or not
	// supported/enabled in this service.
	Unimplemented = Kind{
		Code:   "UNIMPLEMENTED",
		Status: http.StatusNotImplemented,
	}

	// Unavailable indicates the service is currently unavailable.
	// This is a most likely a transient condition and may be corrected
	// by retrying with a backoff. Note that it is not always safe to retry
	// non-idempotent operations.
	//
	// See litmus test above for deciding between FailedPrecondition and
	// Unavailable.
	Unavailable = Kind{
		Code:   "UNAVAILABLE",
		Status: http.StatusServiceUnavailable,
	}

	// DeadlineExceeded means operation expired before completion.
	// For operations that change the state of the system, this error may be
	// returned even if the operation has completed successfully. For
	// example, a successful response from a server could have been delayed
	// long enough for the deadline to expire.
	DeadlineExceeded = Kind{
		Code:   "DEADLINE_EXCEEDED",
		Status: http.StatusServiceUnavailable,
	}
)

Functions

func As

func As(err error, target interface{}) bool

As finds the first error in err's chain that matches target, and if so, sets target to that error value and returns true. Otherwise, it returns false.

The chain consists of err itself followed by the sequence of errors obtained by repeatedly calling Unwrap.

An error matches target if the error's concrete value is assignable to the value pointed to by target, or if the error has a method As(interface{}) bool such that As(target) returns true. In the latter case, the As method is responsible for setting target.

An error type might provide an As method so it can be treated as if it were a a different error type.

As panics if target is not a non-nil pointer to either a type that implements error, or to any interface type.

func Diff

func Diff(template, err error) []string

Diff compares and returns the diff between two error arguments. It can be used to extract the exact details of difference between expected and got errors in tests. It returns an empty array iff both arguments are of type *Error and every non-zero field of the template error is equal to the corresponding field of the second. If the Err field is a *Error, Diff recurs on that field; otherwise it compares the strings returned by the Error methods. Elements that are in the second argument but not present in the template are ignored.

For example,

errors.Diff(errors.E(errors.WithOp("service.MakeBooking"), errors.PermissionDenied), err)

tests whether err is an Error with Kind=PermissionDenied and Op=service.MakeBooking.

func E

func E(opt Option, opts ...Option) error

E builds an error value with the provided options.

if err := svc.ProcessSomething(); err != nil {
	return errors.E(errors.WithOp(op), errors.WithErr(err))
}
Example
package main

import (
	"fmt"

	"github.com/sudo-suhas/xgo/errors"
)

func main() {
	// Simple error
	e1 := errors.E(
		errors.WithOp("xgo_test.SimpleError"),
		errors.Internal,
		errors.WithText("fail"),
	)
	fmt.Println("\nSimple error:")
	fmt.Println(e1)

	// Nested error.
	e2 := errors.E(errors.WithOp("xgo_test.NestedError"), errors.WithErr(e1))
	fmt.Println("\nNested error:")
	fmt.Println(e2)

}
Output:

Simple error:
xgo_test.SimpleError: internal error: fail

Nested error:
xgo_test.NestedError: internal error: xgo_test.SimpleError: fail

func Is

func Is(err, target error) bool

Is reports whether any error in err's chain matches target.

The chain consists of err itself followed by the sequence of errors obtained by repeatedly calling Unwrap.

An error is considered to match a target if it is equal to that target or if it implements a method Is(error) bool such that Is(target) returns true.

An error type might provide an Is method so it can be treated as equivalent to an existing error. For example, if MyError defines

func (m MyError) Is(target error) bool { return target == os.ErrExist }

then Is(MyError{}, os.ErrExist) returns true. See syscall.Errno.Is for an example in the standard library.

func Join added in v0.2.1

func Join(errs ...error) error

Join returns an error that wraps the given errors. Any nil error values are discarded. Join returns nil if errs contains no non-nil values. The error formats as the concatenation of the strings obtained by calling the Error method of each element of errs, with a newline between each string.

func Match

func Match(template, err error) bool

Match compares its two error arguments. It can be used to check for expected errors in tests. Both arguments must have underlying type *Error or Match will return false. Otherwise, it returns true iff every non-zero field of the template error is equal to the corresponding field of the error being checked. If the Err field is a *Error, Match recurs on that field; otherwise it compares the strings returned by the Error methods. Elements that are in the second argument but not present in the first are ignored.

For example,

errors.Match(errors.E(errors.WithOp("service.MakeBooking"), errors.PermissionDenied), err)

tests whether err is an Error with Kind=PermissionDenied and Op=service.MakeBooking.

Example
package main

import (
	"fmt"

	"github.com/sudo-suhas/xgo/errors"
)

func main() {
	msg := "Oops! Something went wrong. Please try again after some time."
	err := errors.New("network unreachable")

	// Construct an error, one we pretend to have received from a test.
	got := errors.E(
		errors.WithOp("Get"),
		errors.WithUserMsg(msg),
		errors.Unavailable,
		errors.WithErr(err),
	)

	// Now construct a reference error, which might not have all
	// the fields of the error from the test.
	expect := errors.E(
		errors.WithOp("Get"),
		errors.Unavailable,
		errors.WithErr(err),
	)

	fmt.Println("Match:", errors.Match(expect, got))

	// Now one that's incorrect - wrong Kind.
	got = errors.E(
		errors.WithOp("Get"),
		errors.WithUserMsg(msg),
		errors.PermissionDenied,
		errors.WithErr(err),
	)

	fmt.Println("Mismatch:", errors.Match(expect, got))

}
Output:


Match: true
Mismatch: false

func New

func New(text string) error

New returns an error that formats as the given text. Each call to New returns a distinct error value even if the text is identical.

func StatusCode

func StatusCode(err error) int

StatusCode attempts to determine the HTTP status code which is suitable for the error response. If the error does not implement StatusCoder or if it lacks type/classification info, http.StatusInternalServerError is returned. This applies for `nil` error as well and this case should be guarded with a nil check at the caller side.

Example
package main

import (
	"database/sql"
	"fmt"
	"net/http"

	"github.com/sudo-suhas/xgo/errors"
)

func main() {
	// Somewhere in the application, return the error with Kind: NotFound.
	err := errors.E(errors.WithOp("Get"), errors.NotFound, errors.WithErr(sql.ErrNoRows))

	// Use StatusCode to extract the status code associated with the error Kind.
	fmt.Println("Status code:", errors.StatusCode(err))

	// If required, define and use a custom Kind
	k := errors.Kind{Code: "CONFLICT", Status: http.StatusConflict}
	err = errors.E(errors.WithOp("UpdateEntity"), k)

	fmt.Println("Status code with custom kind:", errors.StatusCode(err))
}
Output:

Status code: 404
Status code with custom kind: 409

func Unwrap

func Unwrap(err error) error

Unwrap returns the result of calling the Unwrap method on err, if err's type contains an Unwrap method returning error. Otherwise, Unwrap returns nil.

func UserMsg

func UserMsg(err error) string

UserMsg returns the first message suitable to be shown to the end-user in the error chain.

Example
package main

import (
	"fmt"

	"github.com/sudo-suhas/xgo/errors"
)

func main() {
	// Along with the error Kind, associate the appropriate message for the
	// specific error scenario.
	msg := "Username is required"
	e1 := errors.E(errors.WithOp("svc.CreateUser"), errors.InvalidInput, errors.WithUserMsg(msg))

	// Use UserMsg to extract the message to be shown to the user.
	fmt.Println("User message:", errors.UserMsg(e1))

	// Override the message by wrapping it in a new error with a different
	// message.
	e2 := errors.E(errors.WithErr(e1), errors.WithUserMsg("Deal with it!"))
	fmt.Println("Overidden user message:", errors.UserMsg(e2))
}
Output:

User message: Username is required
Overidden user message: Deal with it!

Types

type Error

type Error struct {
	// Op describes an operation, usually as the package and method,
	// such as "key/server.Lookup".
	Op string

	// Kind is the class of error, such as permission failure,
	// or "Unknown" if its class is unknown or irrelevant.
	Kind Kind

	// Text is the error string. Text is not expected to be suitable to
	// be shown to the end user.
	Text string

	// UserMsg is the error message suitable to be shown to the end
	// user.
	UserMsg string

	// Data is arbitrary value associated with the error. Data is not
	// expected to be suitable to be shown to the end user.
	Data interface{}

	// Err is the underlying error that triggered this one, if any.
	Err error

	// ToJSON is used to override the default implementation of
	// converting the Error instance into a JSON value. Optional.
	ToJSON JSONFunc
}

Error is the type that implements the error interface. An Error value may leave some values unset.

If the error is printed, only those items that have been set to non-zero values will appear in the result.

If Kind is not specified or Unknown, we try to set it to the Kind of the underlying error.

func (*Error) Details

func (e *Error) Details() InternalDetails

Details constructs and yields the details of the error by traversing the error chain.

func (*Error) Error

func (e *Error) Error() string

func (*Error) GetKind

func (e *Error) GetKind() Kind

GetKind implements the GetKind interface.

func (*Error) JSON

func (e *Error) JSON() interface{}

JSON is the default implementation of representing the error as a JSON value.

func (*Error) Ops

func (e *Error) Ops() []string

Ops returns the "stack" of operations for the error.

func (*Error) StatusCode

func (e *Error) StatusCode() int

StatusCode attempts to determine the HTTP status code which is suitable for the error Kind.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap unpacks wrapped errors. It is used by functions errors.Is and errors.As.

type Fields

type Fields Error

Fields sets the fields specified on the Error instance. All fields are optional but at least 1 must be specified. Zero values are ignored.

func (Fields) Apply

func (f Fields) Apply(e *Error)

Apply implements Option interface. This allows Fields to be passed as a constructor option.

type GetKind

type GetKind interface {
	GetKind() Kind
}

GetKind is implemented by any value that has a GetKind method. The method is used to determine the type or classification of error.

type InternalDetails

type InternalDetails struct {
	Ops   []string    `json:"ops,omitempty"`
	Kind  Kind        `json:"kind,omitempty"`
	Error string      `json:"error"`
	Data  interface{} `json:"data,omitempty"`
}

InternalDetails is the internal details populated from the error instance.

type JSONFunc

type JSONFunc func(*Error) interface{}

JSONFunc converts the Error instance to a JSON value. It is recommended to return one of the following:

  • map[string]interface{}
  • []map[string]inteface{}
  • CustomType

In certain cases, such as validation errors, it is reasonable to expand a single error instance into multiple 'Objects'.

Care must be taken not to expose details internal to the application. This is for two reasons, namely security and user experience.

type Kind

type Kind struct {
	Code   string
	Status int
}

Kind is the type of error. It is the tuple of the error code and HTTP status code. Defining custom Kinds in application domain is recommended if the predeclared Kinds are not suitable.

func KindFromCode

func KindFromCode(code string) Kind

KindFromCode returns the error kind based on the given error code.

It is not aware of any Kind defined in the application domain.

func KindFromStatus

func KindFromStatus(status int) Kind

KindFromStatus returns the Kind based on the given HTTP status code.

It is not aware of any Kind defined in the application domain.

func WhatKind

func WhatKind(err error) Kind

WhatKind returns the Kind associated with the given error. If the error is nil or does not implement GetKind interface, Unknown is returned.

Example
package main

import (
	"database/sql"
	"fmt"

	"github.com/sudo-suhas/xgo/errors"
)

func main() {
	// Somewhere in the application, return the error with Kind: NotFound.
	err := errors.E(errors.WithOp("Get"), errors.NotFound, errors.WithErr(sql.ErrNoRows))

	// Use WhatKind to extract the Kind associated with the error.
	fmt.Println("Kind:", errors.WhatKind(err))

	// If required, define and use a custom Kind
	k := errors.Kind{Code: "CONFLICT"}
	err = errors.E(errors.WithOp("UpdateEntity"), k)

	fmt.Println("Custom kind:", errors.WhatKind(err))
}
Output:

Kind: not found
Custom kind: conflict

func (Kind) Apply

func (k Kind) Apply(e *Error)

Apply implements Option interface. This allows Kind to be passed as a constructor option.

func (Kind) String

func (k Kind) String() string

type Option

type Option interface {
	Apply(*Error)
}

Option is a type of constructor option for E(...)

func Options

func Options(opts ...Option) Option

Options turns a list of Option instances into an Option.

func WithData

func WithData(data interface{}) Option

WithData sets the Data on the Error instance. It can be any arbitrary value associated with the error.

func WithErr

func WithErr(err error) Option

WithErr sets the Err on the Error instance.

func WithOp

func WithOp(op string) Option

WithOp sets the Op on the Error instance.

op describes an operation, usually as the package and method, such as "key/server.Lookup".

func WithResp

func WithResp(resp *http.Response) Option

WithResp sets the Text, Kind, Data on the Error instance.

HTTP method combined with the request path and the response status is set as the Text. It is not recommended to set the request path as the Op since this URL can include an ID for some entity.

The response status code is interpolated to the Kind using KindFromStatus.

The response body is set as the Data. Special handling is included for detecting and preserving JSON response.

func WithText

func WithText(text string) Option

WithText sets the Text on the Error instance. It is treated as an error message and should not be exposed to the end user.

func WithTextf

func WithTextf(format string, args ...interface{}) Option

WithTextf sets the formatted Text on the Error instance. It is treated as an error message and should not be exposed to the end user.

func WithToJSON

func WithToJSON(f JSONFunc) Option

WithToJSON sets ToJSON on the Error instance. It defines the conversion of Error instance to a JSON value.

func WithUserMsg

func WithUserMsg(msg string) Option

WithUserMsg sets the UserMsg on the Error instance. msg should be suitable to be exposed to the end user.

type OptionFunc

type OptionFunc func(*Error)

OptionFunc type is an adapter to allow the use of ordinary functions as error constructor options. If f is a function with the appropriate signature, OptionFunc(f) is an Option that calls f.

func (OptionFunc) Apply

func (f OptionFunc) Apply(e *Error)

Apply calls f(e).

type StatusCoder

type StatusCoder interface {
	StatusCode() int
}

StatusCoder is implemented by any value that has a StatusCode method. The method is used to map the type or classification of error to the HTTP status code for the response from server.

Jump to

Keyboard shortcuts

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