Skip to content

pingcap/errcode

Repository files navigation

About Error codes

Error codes reliably communicate the error type, particularly across program (network) boundaries. A program can reliably respond to an error if it can be sure to understand its type. Error monitoring systems can reliably understand what errors are occurring.

status

This library is in a completed state. It should be updated now though for some of the changes to golang for error wrapping.

errcode overview

This package extends go errors via interfaces to have error codes. The two main goals are

  • The clients can reliably understand errors by checking against error codes.
  • Structure information is sent to the client

See the go docs for extensive API documentation.

There are other packages that add error code capabilities to errors. However, they almost universally use a single underlying error struct. A code or other annotation is a field on that struct. In most cases a structured error response is only possible to create dynamically via tags and labels.

errcode instead follows the model of pkg/errors to use wrapping and interfaces. You are encouraged to make your own error structures and then fulfill the ErrorCode interface by adding a function. Additional features (for example annotating the operation) are done via wrapping.

This design makes errcode highly extensible, inter-operable, and structure preserving (of the original error). It is easy to gradually introduce it into a project that is using pkg/errors (or the Cause interface).

Features

  • structured error representation
  • Follows the pkg/errors model where error enhancements are annotations via the Causer interface.
  • Internal errors show a stack trace but others don't.
  • Operation annotation. This concept is explained here.
  • Works for multiple errors when the Errors() interface is used. See the Combine function for constructing multiple error codes.
  • Extensible metadata. See how SetHTTPCode is implemented.
  • Integration with existing error codes
    • HTTP
    • GRPC (provided by separate grpc package)

Example

// First define a normal error type
type PathBlocked struct {
	start     uint64 `json:"start"`
	end       uint64 `json:"end"`
	obstacle  uint64 `json:"end"`
}

func (e PathBlocked) Error() string {
	return fmt.Sprintf("The path %d -> %d has obstacle %d", e.start, e.end, e.obstacle)
}

// Define a code. These often get re-used between different errors.
// Note that codes use a hierarchy to share metadata.
// This code is a child of the "state" code.
var PathBlockedCode = errcode.StateCode.Child("state.blocked")

// Now attach the code to your custom error type.
func (e PathBlocked) Code() Code {
	return PathBlockedCode
}

var _ ErrorCode = (*PathBlocked)(nil)  // assert implements the ErrorCode interface

Now lets see how you can send the error code to a client in a way that works with your exising code.

// Given just a type of error, give an error code to a client if it is present
if errCode := errcode.CodeChain(err); errCode != nil {
	// If it helps, you can set the code a header
	w.Header().Set("X-Error-Code", errCode.Code().CodeStr().String())

	// But the code will also be in the JSON body
	// Codes have HTTP codes attached as meta data.
	// You can also attach other kinds of meta data to codes.
	// Our error code inherits StatusBadRequest from its parent code "state"
	rd.JSON(w, errCode.Code().HTTPCode(), errcode.NewJSONFormat(errCode))
}

Let see a usage site. This example will include an annotation concept of "operation".

func moveX(start, end, obstacle) error {}
	op := errcode.Op("path.move.x")

	if start < obstable && obstacle < end  {
		return op.AddTo(PathBlocked{start, end, obstacle})
	}
}

Development

./scripts/build
./scripts/test
./scripts/check