errors

package module
v0.11.1 Latest Latest
Warning

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

Go to latest
Published: May 31, 2023 License: MIT Imports: 8 Imported by: 42

README

errors gopher

codecov Go Report Card Maintainability

Errors v0.10.0

Errors package is a drop-in replacement of the built-in Go errors package. It lets you create errors of 11 different types, which should handle most of the use cases. Some of them are a bit too specific for web applications, but useful nonetheless.

Features of this package:

  1. Multiple (11) error types
  2. Easy handling of User friendly message(s)
  3. Stacktrace - formatted, unfromatted, custom format (refer tests in errors_test.go)
  4. Retrieve the Program Counters for the stacktrace
  5. Retrieve runtime.Frames using errors.RuntimeFrames(err error) for the stacktrace
  6. HTTP status code and user friendly message (wrapped messages are concatenated) for all error types
  7. Helper functions to generate each error type
  8. Helper function to get error Type, error type as int, check if error type is wrapped anywhere in chain
  9. fmt.Formatter support

In case of nested errors, the messages & errors are also looped through the full chain of errors.

Prerequisites

Go 1.13+

Available error types
  1. TypeInternal - For internal system error. e.g. Database errors
  2. TypeValidation - For validation error. e.g. invalid email address
  3. TypeInputBody - For invalid input data. e.g. invalid JSON
  4. TypeDuplicate - For duplicate content error. e.g. user with email already exists (when trying to register a new user)
  5. TypeUnauthenticated - For not authenticated error
  6. TypeUnauthorized - For unauthorized access error
  7. TypeEmpty - For when an expected non-empty resource, is empty
  8. TypeNotFound - For expected resource not found. e.g. user ID not found
  9. TypeMaximumAttempts - For attempting the same action more than an allowed threshold
  10. TypeSubscriptionExpired - For when a user's 'paid' account has expired
  11. TypeDownstreamDependencyTimedout - For when a request to a downstream dependent service times out

Helper functions are available for all the error types. Each of them have 2 helper functions, one which accepts only a string, and the other which accepts an original error as well as a user friendly message.

All the dedicated error type functions are documented here. Names are consistent with the error type, e.g. errors.Internal(string) and errors.InternalErr(error, string)

User friendly messages

More often than not when writing APIs, we'd want to respond with an easier to undersand user friendly message. Instead of returning the raw error and log the raw error.

There are helper functions for all the error types. When in need of setting a friendly message, there are helper functions with the suffix 'Err'. All such helper functions accept the original error and a string.

package main

import (
	"fmt"

	"github.com/bnkamalesh/errors"
)

func Bar() error {
	return fmt.Errorf("hello %s", "world!")
}

func Foo() error {
	err := Bar()
	if err != nil {
		return errors.InternalErr(err, "bar is not happy")
	}
	return nil
}

func main() {
	err := Foo()

	fmt.Println("err:", err)
	fmt.Println("\nerr.Error():", err.Error())

	fmt.Printf("\nformatted +v: %+v\n", err)
	fmt.Printf("\nformatted v: %v\n", err)
	fmt.Printf("\nformatted +s: %+s\n", err)
	fmt.Printf("\nformatted s: %s\n", err)

	_, msg, _ := errors.HTTPStatusCodeMessage(err)
	fmt.Println("\nmsg:", msg)
}

Output

err: bar is not happy

err.Error(): /Users/k.balakumaran/go/src/github.com/bnkamalesh/errors/cmd/main.go:16: bar is not happy
hello world!bar is not happy

formatted +v: /Users/k.balakumaran/go/src/github.com/bnkamalesh/errors/cmd/main.go:16: bar is not happy
hello world!bar is not happy

formatted v: bar is not happy

formatted +s: bar is not happy: hello world!

formatted s: bar is not happy

msg: bar is not happy

Playground link

File & line number prefixed to errors

A common annoyance with Go errors which most people are aware of is, figuring out the origin of the error, especially when there are nested function calls. Ever since error annotation was introduced in Go, a lot of people have tried using it to trace out an errors origin by giving function names, contextual message etc in it. e.g. fmt.Errorf("database query returned error %w", err). However this errors package, whenever you call the Go error interface's Error() string function, prints the error prefixed by the filepath and line number. It'd look like ../Users/JohnDoe/apps/main.go:50 hello world where 'hello world' is the error message.

HTTP status code & message

The function errors.HTTPStatusCodeMessage(error) (int, string, bool) returns the HTTP status code, message, and a boolean value. The boolean is true, if the error is of type *Error from this package. If error is nested, it unwraps and returns a single concatenated message. Sample described in the 'How to use?' section

How to use?

A sample was already shown in the user friendly message section, following one would show a few more scenarios.

package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/bnkamalesh/errors"
	"github.com/bnkamalesh/webgo/v6"
	"github.com/bnkamalesh/webgo/v6/middleware/accesslog"
)

func bar() error {
	return fmt.Errorf("%s %s", "sinking", "bar")
}

func bar2() error {
	err := bar()
	if err != nil {
		return errors.InternalErr(err, "bar2 was deceived by bar1 :(")
	}
	return nil
}

func foo() error {
	err := bar2()
	if err != nil {
		return errors.InternalErr(err, "we lost bar2!")
	}
	return nil
}

func handler(w http.ResponseWriter, r *http.Request) {
	err := foo()
	if err != nil {
		// log the error on your server for troubleshooting
		fmt.Println(err.Error())
		// respond to request with friendly msg
		status, msg, _ := errors.HTTPStatusCodeMessage(err)
		webgo.SendError(w, msg, status)
		return
	}

	webgo.R200(w, "yay!")
}

func routes() []*webgo.Route {
	return []*webgo.Route{
		{
			Name:    "home",
			Method:  http.MethodGet,
			Pattern: "/",
			Handlers: []http.HandlerFunc{
				handler,
			},
		},
	}
}

func main() {
	router := webgo.NewRouter(&webgo.Config{
		Host:         "",
		Port:         "8080",
		ReadTimeout:  15 * time.Second,
		WriteTimeout: 60 * time.Second,
	}, routes()...)

	router.UseOnSpecialHandlers(accesslog.AccessLog)
	router.Use(accesslog.AccessLog)
	router.Start()
}

webgo was used to illustrate the usage of the function, errors.HTTPStatusCodeMessage. It returns the appropriate http status code, user friendly message stored within, and a boolean value. Boolean value is true if the returned error of type *Error. Since we get the status code and message separately, when using any web framework, you can set values according to the respective framework's native functions. In case of Webgo, it wraps errors in a struct of its own. Otherwise, you could directly respond to the HTTP request by calling errors.WriteHTTP(error,http.ResponseWriter).

Once the app is running, you can check the response by opening http://localhost:8080 on your browser. Or on terminal

$ curl http://localhost:8080
{"errors":"we lost bar2!. bar2 was deceived by bar1 :(","status":500} // output

And the fmt.Println(err.Error()) generated output on stdout would be:

/Users/username/go/src/errorscheck/main.go:28 /Users/username/go/src/errorscheck/main.go:20 sinking bar

Benchmark [2022-01-12]

MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports), 32 GB 3733 MHz LPDDR4X

$ go version
go version go1.19.5 darwin/amd64

$ go test -benchmem -bench .
goos: darwin
goarch: amd64
pkg: github.com/bnkamalesh/errors
cpu: Intel(R) Core(TM) i7-1068NG7 CPU @ 2.30GHz
Benchmark_Internal-8                            	 1526194	       748.8 ns/op	    1104 B/op	       2 allocs/op
Benchmark_Internalf-8                           	 1281465	       944.0 ns/op	    1128 B/op	       3 allocs/op
Benchmark_InternalErr-8                         	 1494351	       806.7 ns/op	    1104 B/op	       2 allocs/op
Benchmark_InternalGetError-8                    	  981162	      1189 ns/op	    1528 B/op	       6 allocs/op
Benchmark_InternalGetErrorWithNestedError-8     	  896322	      1267 ns/op	    1544 B/op	       6 allocs/op
Benchmark_InternalGetMessage-8                  	 1492812	       804.2 ns/op	    1104 B/op	       2 allocs/op
Benchmark_InternalGetMessageWithNestedError-8   	 1362092	       886.3 ns/op	    1128 B/op	       3 allocs/op
Benchmark_HTTPStatusCodeMessage-8               	27494096	        41.38 ns/op	      16 B/op	       1 allocs/op
BenchmarkHasType-8                              	100000000	        10.50 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/bnkamalesh/errors	15.006s

Contributing

More error types, customization, features, multi-errors; PRs & issues are welcome!

The gopher

The gopher used here was created using Gopherize.me. Show some love to Go errors like our gopher lady here!

Documentation

Overview

Package errors helps in wrapping errors with custom type as well as a user friendly message. This is particularly useful when responding to APIs

Index

Constants

View Source
const (
	// TypeInternal is error type for when there is an internal system error. e.g. Database errors
	TypeInternal errType = iota
	// TypeValidation is error type for when there is a validation error. e.g. invalid email address
	TypeValidation
	// TypeInputBody is error type for when an input data type error. e.g. invalid JSON
	TypeInputBody
	// TypeDuplicate is error type for when there's duplicate content
	TypeDuplicate
	// TypeUnauthenticated is error type when trying to access an authenticated API without authentication
	TypeUnauthenticated
	// TypeUnauthorized is error type for when there's an unauthorized access attempt
	TypeUnauthorized
	// TypeEmpty is error type for when an expected non-empty resource, is empty
	TypeEmpty
	// TypeNotFound is error type for an expected resource is not found e.g. user ID not found
	TypeNotFound
	// TypeMaximumAttempts is error type for attempting the same action more than allowed
	TypeMaximumAttempts
	// TypeSubscriptionExpired is error type for when a user's 'paid' account has expired
	TypeSubscriptionExpired
	// TypeDownstreamDependencyTimedout is error type for when a request to a downstream dependent service times out
	TypeDownstreamDependencyTimedout

	// DefaultMessage is the default user friendly message
	DefaultMessage = "unknown error occurred"
)

While adding a new Type, the respective helper functions should be added, also update the WriteHTTP method accordingly

Variables

This section is empty.

Functions

func As

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

As calls the Go builtin errors.As

func ErrWithoutTrace added in v0.3.6

func ErrWithoutTrace(err error) (string, bool)

ErrWithoutTrace is a duplicate of Message, but with clearer name. The boolean is 'true' if the provided err is of type *Error

func HTTPStatusCode added in v0.3.6

func HTTPStatusCode(err error) (int, bool)

HTTPStatusCode returns appropriate HTTP response status code based on type of the error. The boolean is 'true' if the provided error is of type *Err In case of joined errors, it'll return the status code of the last *Error

func HTTPStatusCodeMessage

func HTTPStatusCodeMessage(err error) (int, string, bool)

HTTPStatusCodeMessage returns the appropriate HTTP status code, message, boolean for the error the boolean value is true if the error was of type *Error, false otherwise.

func HasType added in v0.1.10

func HasType(err error, et errType) bool

HasType will check if the provided err type is available anywhere nested in the error

func Is

func Is(err, target error) bool

Is calls the Go builtin errors.Is

func Join added in v0.10.0

func Join(errs ...error) error

func Message added in v0.3.6

func Message(err error) (string, bool)

Message recursively concatenates all the messages set while creating/wrapping the errors. The boolean is 'true' if the provided error is of type *Err

func ProgramCounters added in v0.9.0

func ProgramCounters(err error) []uintptr

func RuntimeFrames added in v0.9.0

func RuntimeFrames(err error) *runtime.Frames

func SetDefaultType

func SetDefaultType(e errType)

SetDefaultType will set the default error type, which is used in the 'New' function

func Stacktrace added in v0.9.0

func Stacktrace(err error) string

Stacktrace returns a string representation of the stacktrace, where each trace is separated by a newline and tab '\t'

func StacktraceCustomFormat added in v0.9.0

func StacktraceCustomFormat(msgformat string, traceFormat string, err error) string

StacktraceCustomFormat lets you prepare a stacktrace in a custom format

msgformat - is used to format the line which prints message from Error.message traceFormat - is used to format the line which prints trace Supported directives: %m - message if err type is *Error, otherwise output of `.Error()` %p - file path, empty if type is not *Error %l - line, empty if type is not *Error %f - function, empty if type is not *Error

func StacktraceFromPcs added in v0.9.3

func StacktraceFromPcs(err error) string

func StacktraceNoFormat added in v0.9.0

func StacktraceNoFormat(err error) []string

Stacktrace returns a string representation of the stacktrace, as a slice of string where each element represents the error message and traces.

func Type added in v0.1.10

func Type(err error) errType

Type returns the errType if it's an instance of *Error, -1 otherwise In case of joined error, it'll return the type of the last *Error

func TypeInt added in v0.1.10

func TypeInt(err error) int

Type returns the errType as integer if it's an instance of *Error, -1 otherwise

func Unwrap

func Unwrap(err error) error

Unwrap calls the Go builtin errors.UnUnwrap

func WriteHTTP

func WriteHTTP(err error, w http.ResponseWriter)

WriteHTTP is a convenience method which will check if the error is of type *Error and respond appropriately

Types

type Error

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

Error is the struct which holds custom attributes

func DownstreamDependencyTimedout

func DownstreamDependencyTimedout(message string) *Error

DownstreamDependencyTimedout is a helper function to create a new error of type TypeDownstreamDependencyTimedout

func DownstreamDependencyTimedoutErr

func DownstreamDependencyTimedoutErr(original error, message string) *Error

DownstreamDependencyTimedoutErr is a helper function to create a new error of type TypeDownstreamDependencyTimedout which also accepts an original error

func DownstreamDependencyTimedoutErrf added in v0.3.0

func DownstreamDependencyTimedoutErrf(original error, format string, args ...interface{}) *Error

DownstreamDependencyTimedoutErrf is a helper function to create a new error of type TypeDownstreamDependencyTimedout which also accepts an original error, with formatted message

func DownstreamDependencyTimedoutf added in v0.3.0

func DownstreamDependencyTimedoutf(format string, args ...interface{}) *Error

DownstreamDependencyTimedoutf is a helper function to create a new error of type TypeDownstreamDependencyTimedout, with formatted message

func Duplicate

func Duplicate(message string) *Error

Duplicate is a helper function to create a new error of type TypeDuplicate

func DuplicateErr

func DuplicateErr(original error, message string) *Error

DuplicateErr is a helper function to create a new error of type TypeDuplicate which also accepts an original error

func DuplicateErrf added in v0.3.0

func DuplicateErrf(original error, format string, args ...interface{}) *Error

DuplicateErrf is a helper function to create a new error of type TypeDuplicate which also accepts an original error, with formatted message

func Duplicatef added in v0.3.0

func Duplicatef(format string, args ...interface{}) *Error

Duplicatef is a helper function to create a new error of type TypeDuplicate, with formatted message

func Empty

func Empty(message string) *Error

Empty is a helper function to create a new error of type TypeEmpty

func EmptyErr

func EmptyErr(original error, message string) *Error

EmptyErr is a helper function to create a new error of type TypeEmpty which also accepts an original error

func EmptyErrf added in v0.3.0

func EmptyErrf(original error, format string, args ...interface{}) *Error

EmptyErr is a helper function to create a new error of type TypeEmpty which also accepts an original error, with formatted message

func Emptyf added in v0.3.0

func Emptyf(format string, args ...interface{}) *Error

Emptyf is a helper function to create a new error of type TypeEmpty, with formatted message

func Errorf added in v0.3.4

func Errorf(fromat string, args ...interface{}) *Error

Errorf is a convenience method to create a new instance of Error with formatted message Important: %w directive is not supported, use fmt.Errorf if you're using the %w directive or use Wrap/Wrapf to wrap an error.

func InputBody

func InputBody(message string) *Error

InputBody is a helper function to create a new error of type TypeInputBody

func InputBodyErr

func InputBodyErr(original error, message string) *Error

InputBodyErr is a helper function to create a new error of type TypeInputBody which also accepts an original error

func InputBodyErrf added in v0.3.0

func InputBodyErrf(original error, format string, args ...interface{}) *Error

InputBodyErrf is a helper function to create a new error of type TypeInputBody which also accepts an original error, with formatted message

func InputBodyf added in v0.3.0

func InputBodyf(format string, args ...interface{}) *Error

InputBodyf is a helper function to create a new error of type TypeInputBody, with formatted message

func Internal added in v0.1.7

func Internal(message string) *Error

Internal helper method for creating internal errors

func InternalErr

func InternalErr(original error, message string) *Error

InternalErr helper method for creation internal errors which also accepts an original error

func InternalErrf added in v0.3.0

func InternalErrf(original error, format string, args ...interface{}) *Error

InternalErr helper method for creation internal errors which also accepts an original error, with formatted message

func Internalf added in v0.3.0

func Internalf(format string, args ...interface{}) *Error

Internalf helper method for creating internal errors with formatted message

func MaximumAttempts

func MaximumAttempts(message string) *Error

MaximumAttempts is a helper function to create a new error of type TypeMaximumAttempts

func MaximumAttemptsErr

func MaximumAttemptsErr(original error, message string) *Error

MaximumAttemptsErr is a helper function to create a new error of type TypeMaximumAttempts which also accepts an original error

func MaximumAttemptsErrf added in v0.3.0

func MaximumAttemptsErrf(original error, format string, args ...interface{}) *Error

MaximumAttemptsErr is a helper function to create a new error of type TypeMaximumAttempts which also accepts an original error, with formatted message

func MaximumAttemptsf added in v0.3.0

func MaximumAttemptsf(format string, args ...interface{}) *Error

MaximumAttemptsf is a helper function to create a new error of type TypeMaximumAttempts, with formatted message

func New

func New(msg string) *Error

New returns a new instance of Error with the relavant fields initialized

func NewWithErrMsgType

func NewWithErrMsgType(original error, message string, etype errType) *Error

NewWithErrMsgType returns an error instance with custom error type and message

func NewWithErrMsgTypef added in v0.3.0

func NewWithErrMsgTypef(original error, etype errType, format string, args ...interface{}) *Error

NewWithErrMsgTypef returns an error instance with custom error type and formatted message

func NewWithType

func NewWithType(msg string, etype errType) *Error

NewWithType returns an error instance with custom error type

func NewWithTypef added in v0.3.0

func NewWithTypef(etype errType, format string, args ...interface{}) *Error

NewWithTypef returns an error instance with custom error type. And formatted message

func Newf added in v0.3.4

func Newf(fromat string, args ...interface{}) *Error

func NotFound

func NotFound(message string) *Error

NotFound is a helper function to create a new error of type TypeNotFound

func NotFoundErr

func NotFoundErr(original error, message string) *Error

NotFoundErr is a helper function to create a new error of type TypeNotFound which also accepts an original error

func NotFoundErrf added in v0.3.0

func NotFoundErrf(original error, format string, args ...interface{}) *Error

NotFoundErrf is a helper function to create a new error of type TypeNotFound which also accepts an original error, with formatted message

func NotFoundf added in v0.3.0

func NotFoundf(format string, args ...interface{}) *Error

NotFoundf is a helper function to create a new error of type TypeNotFound, with formatted message

func SubscriptionExpired

func SubscriptionExpired(message string) *Error

SubscriptionExpired is a helper function to create a new error of type TypeSubscriptionExpired

func SubscriptionExpiredErr

func SubscriptionExpiredErr(original error, message string) *Error

SubscriptionExpiredErr is a helper function to create a new error of type TypeSubscriptionExpired which also accepts an original error

func SubscriptionExpiredErrf added in v0.3.0

func SubscriptionExpiredErrf(original error, format string, args ...interface{}) *Error

SubscriptionExpiredErrf is a helper function to create a new error of type TypeSubscriptionExpired which also accepts an original error, with formatted message

func SubscriptionExpiredf added in v0.3.0

func SubscriptionExpiredf(format string, args ...interface{}) *Error

SubscriptionExpiredf is a helper function to create a new error of type TypeSubscriptionExpired, with formatted message

func Unauthenticated

func Unauthenticated(message string) *Error

Unauthenticated is a helper function to create a new error of type TypeUnauthenticated

func UnauthenticatedErr

func UnauthenticatedErr(original error, message string) *Error

UnauthenticatedErr is a helper function to create a new error of type TypeUnauthenticated which also accepts an original error

func UnauthenticatedErrf added in v0.3.0

func UnauthenticatedErrf(original error, format string, args ...interface{}) *Error

UnauthenticatedErrf is a helper function to create a new error of type TypeUnauthenticated which also accepts an original error, with formatted message

func Unauthenticatedf added in v0.3.0

func Unauthenticatedf(format string, args ...interface{}) *Error

Unauthenticatedf is a helper function to create a new error of type TypeUnauthenticated, with formatted message

func Unauthorized

func Unauthorized(message string) *Error

Unauthorized is a helper function to create a new error of type TypeUnauthorized

func UnauthorizedErr

func UnauthorizedErr(original error, message string) *Error

UnauthorizedErr is a helper function to create a new error of type TypeUnauthorized which also accepts an original error

func UnauthorizedErrf added in v0.3.0

func UnauthorizedErrf(original error, format string, args ...interface{}) *Error

UnauthorizedErrf is a helper function to create a new error of type TypeUnauthorized which also accepts an original error, with formatted message

func Unauthorizedf added in v0.3.0

func Unauthorizedf(format string, args ...interface{}) *Error

Unauthorizedf is a helper function to create a new error of type TypeUnauthorized, with formatted message

func Validation

func Validation(message string) *Error

Validation is a helper function to create a new error of type TypeValidation

func ValidationErr

func ValidationErr(original error, message string) *Error

ValidationErr helper method for creation validation errors which also accepts an original error

func ValidationErrf added in v0.3.0

func ValidationErrf(original error, format string, args ...interface{}) *Error

ValidationErr helper method for creation validation errors which also accepts an original error, with formatted message

func Validationf added in v0.3.0

func Validationf(format string, args ...interface{}) *Error

Validationf is a helper function to create a new error of type TypeValidation, with formatted message

func Wrap added in v0.1.12

func Wrap(original error, msg ...string) *Error

Wrap is used to simply wrap an error with optional message; error type would be the default error type set using SetDefaultType; TypeInternal otherwise If the error being wrapped is already of type Error, then its respective type is used

func WrapWithMsg deprecated added in v0.1.13

func WrapWithMsg(original error, msg string) *Error

Deprecated: WrapWithMsg [deprecated, use `Wrap`] wrap error with a user friendly message

func Wrapf added in v0.3.0

func Wrapf(original error, format string, args ...interface{}) *Error

func (*Error) Error

func (e *Error) Error() string

Error is the implementation of error interface

func (*Error) ErrorWithoutFileLine added in v0.4.0

func (e *Error) ErrorWithoutFileLine() string

ErrorWithoutFileLine prints the final string without the stack trace / file+line number

func (*Error) Format added in v0.4.0

func (e *Error) Format(s fmt.State, verb rune)

Format implements the verbs/directives supported by Error to be used in fmt annotated/formatted strings

%v - the same output as Message(). i.e. recursively get all the custom messages set by user

  • if any of the wrapped error is not of type *Error, that will *not* be displayed

%+v - recursively prints all the messages along with the file & line number. Also includes output of `Error()` of non *Error types.

%s - identical to %v %+s - recursively prints all the messages without file & line number. Also includes output `Error()` of non *Error types.

func (*Error) HTTPStatusCode

func (e *Error) HTTPStatusCode() int

HTTPStatusCode is a convenience method used to get the appropriate HTTP response status code for the respective error type

func (*Error) Is added in v0.1.6

func (e *Error) Is(err error) bool

Is implements the Is interface required by Go

func (*Error) Message

func (e *Error) Message() string

Message returns the user friendly message stored in the error struct. It will ignore all errors which are not of type *Error

func (*Error) ProgramCounters added in v0.9.0

func (e *Error) ProgramCounters() []uintptr

func (*Error) RuntimeFrames added in v0.9.0

func (e *Error) RuntimeFrames() *runtime.Frames

func (*Error) StackTrace added in v0.9.0

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

func (*Error) StackTraceCustomFormat added in v0.9.0

func (e *Error) StackTraceCustomFormat(msgformat string, traceFormat string) []string

StackTraceCustomFormat lets you prepare a stacktrace in a custom format

Supported directives: %m - message %p - file path %l - line %f - function

func (*Error) StackTraceNoFormat added in v0.9.0

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

func (*Error) Type

func (e *Error) Type() errType

Type returns the error type as integer

func (*Error) Unwrap added in v0.1.6

func (e *Error) Unwrap() error

Unwrap implement's Go 1.13's Unwrap interface exposing the wrapped error

Jump to

Keyboard shortcuts

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