{"id":15533344,"url":"https://github.com/lobocv/simplerr","last_synced_at":"2025-04-23T13:43:57.156Z","repository":{"id":37786676,"uuid":"457978358","full_name":"lobocv/simplerr","owner":"lobocv","description":"Advanced Go error handling, made simple","archived":false,"fork":false,"pushed_at":"2025-01-24T18:28:31.000Z","size":393,"stargazers_count":23,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-18T00:11:27.969Z","etag":null,"topics":["error-codes","error-handling","errors","go","golang","grpc","http","simple"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/lobocv/simplerr","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lobocv.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-02-10T23:15:13.000Z","updated_at":"2025-04-05T04:08:04.000Z","dependencies_parsed_at":"2024-06-19T20:04:22.241Z","dependency_job_id":"6b2b1b01-35fe-44e0-9f51-6efb9ecefd7a","html_url":"https://github.com/lobocv/simplerr","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lobocv%2Fsimplerr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lobocv%2Fsimplerr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lobocv%2Fsimplerr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lobocv%2Fsimplerr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lobocv","download_url":"https://codeload.github.com/lobocv/simplerr/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250441556,"owners_count":21431184,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["error-codes","error-handling","errors","go","golang","grpc","http","simple"],"created_at":"2024-10-02T11:35:58.087Z","updated_at":"2025-04-23T13:43:57.147Z","avatar_url":"https://github.com/lobocv.png","language":"Go","readme":"[![Go Reference](https://pkg.go.dev/badge/github.com/lobocv/simplerr.svg)](https://pkg.go.dev/github.com/lobocv/simplerr)\n[![Github tag](https://badgen.net/github/tag/lobocv/simplerr)](https://github.com/lobocv/simplerr/tags)\n![Go version](https://img.shields.io/github/go-mod/go-version/lobocv/simplerr)\n![Build Status](https://github.com/lobocv/simplerr/actions/workflows/build.yaml/badge.svg)\n[![Go Report Card](https://goreportcard.com/badge/github.com/lobocv/simplerr)](https://goreportcard.com/report/github.com/lobocv/simplerr)\n[\u003cimg src=\"https://img.shields.io/github/license/lobocv/simplerr\"\u003e](https://img.shields.io/github/license/lobocv/simplerr)\n\u003ca href='https://github.com/jpoles1/gopherbadger' target='_blank'\u003e![gopherbadger-tag-do-not-edit](https://img.shields.io/badge/Go%20Coverage-100%25-brightgreen.svg?longCache=true\u0026style=flat)\u003c/a\u003e\n![Lint Status](https://github.com/lobocv/simplerr/actions/workflows/golangci-lint.yaml/badge.svg)\n\n# Simplerr\n\u003cp align=\"center\"\u003e\u003cimg src=\"gopher.png\" width=\"250\"\u003e\u003c/p\u003e\n\nSimplerr provides a simple and more powerful Go error handling experience by providing an alternative `error` \nimplementation, the [SimpleError](https://pkg.go.dev/github.com/lobocv/simplerr#SimpleError). Simplerr was designed to\nbe convenient and highly configurable. The main goals of Simplerr is to reduce boilerplate and make error handling\nand debugging easier.\n\nCheck out the [blog post](https://blog.lobocv.com/posts/richer_golang_errors/?utm_source=github\u0026utm_medium=simplerr\u0026utm_id=blog) introducing why simplerr\nwas created.\n\n# Features\n\nThe `SimpleError` allows you to easily:\n\n- Apply an error code to any error. Choose from a list of standard codes or [register](https://pkg.go.dev/github.com/lobocv/simplerr#Registry) your own.\n- Automatically translate `simplerr` (including custom codes) error codes to other standardized codes such as `HTTP/gRPC` via middleware.\n- Attach key-value pairs to errors\n- Easily log key-value information from errors in structured loggers (slog)\n- Attach and check for custom attributes similar to the `context` package.\n- Automatically capture stack traces at the point the error is raised.\n- Mark errors as [`silent`](https://pkg.go.dev/github.com/lobocv/simplerr#SimpleError.Silence) so they can be skipped by logging middleware.\n- Mark errors as [`benign`](https://pkg.go.dev/github.com/lobocv/simplerr#SimpleError.Benign) so they can be logged less severely by logging middleware.\n- Mark errors as [`retriable`](https://pkg.go.dev/github.com/lobocv/simplerr#SimpleError.Retry-able) so retry mechanisms can retry transient errors. \n- Embeddable so you can extend functionality or write your own convenience wrappers\n\n# Installation\n```\ngo get -u github.com/lobocv/simplerr\n```\n\n# Error Codes\n\nThe following error codes are provided by default with `simplerr`. These can be extended by registering custom codes.\n\n\nError Code | Description\n-----------|------------\nUnknown| The default code for errors that are not classified\nAlreadyExists| An attempt to create an entity failed because it already exists\nNotFound| Means some requested entity (e.g., file or directory) was not found\nInvalidArgument| The caller specified an invalid argument\nMalformedRequest| The syntax of the request cannot be interpreted (eg JSON decoding error)\nUnauthenticated| The request does not have valid authentication credentials for the operation.\nPermissionDenied| That the identity of the user is confirmed but they do not have permission to perform the request\nConstraintViolated| A constraint in the system has been violated. Eg. a duplicate key error from a unique index\nNotSupported| The request is not supported\nNotImplemented| The request is not implemented\nMissingParameter| A required parameter is missing or empty\nDeadlineExceeded| A request exceeded it's deadline before completion\nCanceled| The request was canceled before completion\nResourceExhausted| A limited resource, such as a rate limit or disk space, has been reached\nUnavailable| The server itself is unavailable for processing requests.\n\nA complete list of standard error codes can be found [here](https://github.com/lobocv/simplerr/blob/master/codes.go).\n## Custom Error Codes\n\nCustom error codes can be registered globally with `simplerr`. The standard error codes cannot\nbe overwritten and have reserved values from 0-99. \n\n```go\nfunc main() {\n    r := NewRegistry()\n    r.RegisterErrorCode(100, \"custom error description\")\n}\n\t\n```\n\n# Basic usage\n\n## Creating errors\nErrors can be created with [`New(format string, args... interface{})`](https://pkg.go.dev/github.com/lobocv/simplerr#New),\nwhich works similar to `fmt.Errorf` but instead returns a `*SimplerError`. You can then chain mutations onto the error \nto add additional information.\n\n```go\nuserID := 123\ncompanyID := 456\nerr := simplerr.New(\"user %d does not exist in company %d\", userID, companyID).\n\tCode(CodeNotFound).\n\tAux(\"user_id\", userID, \"company_id\", companyID)\n```\n\nIn the above example, a new error is created and set to error code `CodeNotFound`. We have also attached auxiliary\nkey-value pair information to the error that we can extract later on when we decide to handle or log the error.\n\nErrors can also be wrapped with the [`Wrap(err error)`](https://pkg.go.dev/github.com/lobocv/simplerr#Wrap)) and \n[`Wrapf(err error, format string, args... []interface{})`](https://pkg.go.dev/github.com/lobocv/simplerr#Wrapf) functions:\n\n```go\nfunc GetUser(userID int) (*User, error) {\n    user, err := db.GetUser(userID)\n    if err != nil {\n        serr = simplerr.Wrapf(err, \"failed to get user with id = %d\", userID).\n\t\t\tAux(\"user_id\", userID)\n        if errors.Is(err, sql.ErrNoRows) {\n            serr.Code(CodeNotFound)   \n        }\n        return serr\n    }\n}\n```\n\n## Attaching Custom Attributes to Errors\n\nSimplerr lets you define and detect your own custom attributes on errors. This works similarly to the `context` package.\nAn attribute is attached to an error using the [`Attr()`](https://pkg.go.dev/github.com/lobocv/simplerr#SimpleError.Attr)\nmutator and can be retrieved using the [`GetAttribute()`](https://pkg.go.dev/github.com/lobocv/simplerr#GetAttribute) function, which finds the first match of the attribute key in \nthe error chain.\n\nIt is highly recommended that a custom type be used as the key in order to prevent naming collisions of attributes. \nThe following example defines a `NotRetryable` attribute and attaches it on an error where a unique constraint is violated,\nthis indicates that the error should be exempt by any retry mechanism. \n\n\n```go\n// Define a custom type so we don't get naming collisions for value == 1\ntype ErrorAttribute int\n\n// Define a specific key for the attribute\nconst NotRetryable = ErrorAttribute(1)\n\n// Attach the `NotRetryable` attribute on the error\nserr := simplerr.New(\"user with that email already exists\").\n\tCode(CodeConstraintViolated).\n\tAttr(NotRetryable, true)\n\n// Get the value of the NotRetryable attribute\nisRetryable, ok := simplerr.GetAttribute(err, NotRetryable).(bool)\n// isRetryable == true\n```\n\n## Detecting errors\n\n`SimpleError` implements the `Unwrap()` method so it can be used with the standard library\n`errors.Is()` and `errors.As()` functions. However, the ability to use error codes makes\nabstracting and detecting errors much simpler. Instead of looking for a specific error, `simplerr`\nallows you to search for the **kind** of error by looking for an error code:\n\n```go\nfunc GetUserSettings(userID int) (*Settings, error) {\n    settings, err := db.GetSettings(userID)\n    if err != nil {\n        // If the settings do not exist, return defaults\n        if simplerr.HasErrorCode(CodeNotFound) {\n            return defaultSettings(), nil\n        }\n\t\t\n        serr := simplerr.Wrapf(err, \"failed to get settings for user with id = %d\", userID).\n                         Aux(\"user_id\", userID)\n        return nil, serr\n    }\n\t\n    return settings, nil\n}\n```\n\nThe alternatives would be to use `errors.Is(err, sql.ErrNoRows)` directly and leak an implementation\ndetail of the persistence layer or to define a custom error that the persistence layer would need\nto return in place of `sql.ErrNoRows`. \n\n# Error Handling \n\n`SimpleErrors` were designed to be handled. The [ecosystem](https://github.com/lobocv/simplerr/tree/master/ecosystem)\npackage provides packages to assist with error handling for different applications. Designing your own handlers is as\nsimple as detecting the `SimpleError` and reacting to it's attributes.\n\n## Detecting Errors\n\nTo detect a specific error code, you can use [`HasErrorCode(err error, c Code)`](https://pkg.go.dev/github.com/lobocv/simplerr#HasErrorCode).\nIf you want to look for several different error codes, use [`HasErrorCodes(err error, codes... Code)`](https://pkg.go.dev/github.com/lobocv/simplerr#HasErrorCodes),\nwhich returns the first of the provided error codes that is detected, and a boolean for whether anything was detected.\n\n\n## Logging SimpleErrors\n\nOne of the objective to `simplerr` is to reduce the need to log the errors manually at the sight in which they are raised,\nand instead, log errors in a procedural way in a middleware layer. While this is possible with standard library errors,\nthere is a lack of control when dealing only with the simple `string`-backed error implementation.\n\n### Logging with Structured Loggers\n\nIt is good practice to use structured logging to improve observability. With `simplerr` you can attach key-value pairs\nto the `SimplerError` and generate a pre-populated structured logger right from the error:\n\n```go\n\nserr := simplerr.New(\"not enough credits\").Aux(\"current_credits\", 10, \"requested_credits\", 5)\nlog := serr.GetLogger()\nlog.Error(serr.Error())\n```\n\nThis outputs a log line with the structured key-value information from the SimpleError.\n\n\u003e {\"time\":\"2025-01-24T13:12:12.924564-05:00\",\"level\":\"ERROR\",\"msg\":\"not enough credits\",\"requested_credits\":50,\"current_credits\":10}\n\nYou can also attach an existing structured logger to a SimpleError:\n\n```go\nlog := slog.Default().With(\"flavour\", \"vanilla\")\nserr := simplerr.New(\"we do not sell that flavor\").Logger(log)\n```\n\nWhen calling the `GetLogger()` method, the returned logger is built by combining key-value pairs for all errors in the \nchain. This means that wrapping multiple errors that each attach key-value information will return a structured logger\npreserving all the key-value pairs.\n\n### Benign Errors\n\nBenign errors are errors that are mainly used to indicate a certain condition, rather than something going wrong in the \nsystem. An example of a benign error would be an API that returns `sql.ErrNoRows` when requesting a specific\nresource. Depending on whether the resource is expected to exist or not, this may not actually be an error. \n\nSome clients may be calling the API to just check the existence of the resource. Nonetheless, this \"error\" would flood \nthe logs at `ERROR` level and may disrupt error tracking tools such as [sentry](https://sentry.io/welcome/). \nThe server must still return the error so that it reaches the client, however on the server, it is not seen as genuine \nerror and does not need to be logged as such. With `simplerr`, it is possible to mark an error as `benign`, which allows logging middleware to detect and log\nthe error at a less severe level such as `INFO`.\n\nErrors can be marked benign by either using the [`Benign()`](https://pkg.go.dev/github.com/lobocv/simplerr#SimpleError.Benign)\nor [`BenignReason()`](https://pkg.go.dev/github.com/lobocv/simplerr#SimpleError.BenignReason) mutators. The latter also attaches a \nreason why the error was marked benign. To detect benign errors, use the [`IsBenign()`](https://pkg.go.dev/github.com/lobocv/simplerr#IsBenign) \nfunction which looks for any benign errors in the chain of errors.\n\n### Silent Errors\n\nSimilar to benign errors, an error can be marked as silent using the [`Silence()`](https://pkg.go.dev/github.com/lobocv/simplerr#SimpleError.Silence)\nmutator to indicate to logging middleware to not log this error at all. This is useful in situations where a very high \namount of benign errors are flooding the logs. To detect silent errors, use the [`IsSilent()`](https://pkg.go.dev/github.com/lobocv/simplerr#IsSilent) function which looks for \nany silent errors in the chain of errors.\n\n### Retry-able / Retriable Errors\n\nYou can mark an error as \"retriable\" using the [`Retriable()`]((https://pkg.go.dev/github.com/lobocv/simplerr#SimpleError.Retriable))\nmutator. When an error is marked as retriable, error handling mechanisms can assume that the error is transient and that they\ncan retry the operation (assuming it is indempotent). \n\n**By default, all errors are assumed to be not retriable unless explicitly marked otherwise**\n\n### Changing Error Formatting\n\nThe default formatting of the error string can be changed by modifying the [`simplerr.Formatter`](https://pkg.go.dev/github.com/lobocv/simplerr#Formatter) variable.\nFor example, to use a new line to separate the message and the wrapped error you can do:\n\n```go\nsimplerr.Formatter = func(e *simplerr.SimpleError) string {\n    parent := e.Unwrap()\n    if parent == nil {\n        return e.GetMessage()\n    }\n    return strings.Join([]string{e.GetMessage(), parent.Error()}, \"\\n\")\n}\n```\n\n## HTTP Status Codes\n\nHTTP status codes can be set automatically by using the [ecosystem/http](https://github.com/lobocv/simplerr/tree/master/ecosystem/http)\npackage to translate `simplerr` error codes to HTTP status codes and vice versa.\n\n### Converting SimpleError to HTTP status codes\nTo do so, you must use the `simplehttp.Handler` or \n`simplehttp.HandlerFunc`instead of the ones defined in the `http` package. The only difference between the two is that\nthe `simplehttp` ones return an error. \nAdapters are provided in order to interface with the `http` package. These adapters call `simplehttp.SetStatus()` on the returned\nerror in order to set the http status code on the response.\n\nGiven a server with the an endpoint `GetUser`:\n\n```go\nfunc (s *Server) GetUser(resp http.ResponseWriter, req *http.Request) error {\n\t\n    // extract userName from request...\n\t\n    err := s.db.GetUser(userName)\n\tif err != nil {\n\t\t// This returned error is translated into a response code via the http adapter\n\t    return err\n    }\n\n    resp.WriteHeader(http.StatusCreated)\n}\n```\n\nWe can mount the endpoint with the `simplehttp.NewHandlerAdapter()`:\n\n```go\ns := \u0026Server{}\nhttp.ListenAndServe(\"\", simplehttp.NewHandlerAdapter(s))\n````\nor if it was a handler function instead, using the `simplehttp.NewHandlerFuncAdapter()` method:\n\n```go\nhttp.ListenAndServe(\"\", simplehttp.NewHandlerFuncAdapter(fn))\n```\n\nSimplerr does not provide a 1:1 mapping of all HTTP status because there are too many obscure and under-utilized HTTP codes\nthat would complicate and bloat the library. Most of the prevalent HTTP status codes have representation in `simplerr`.\nAdditional translations can be added by registering a mapping:\n\n```go\nfunc main() {\n    m := simplehttp.DefaultMapping()\n    m[simplerr.CodeCanceled] = http.StatusRequestTimeout\n    simplehttp.SetMapping(m)\n    // ...\n}\n```\n\n### Converting HTTP status codes to SimpleError from HTTP Clients\n\nThe standard library `http.DefaultTransport` will return all successfully transported request/responses without error.\nHowever, most applications will react to those responses by looking at the HTTP status code. From the application's point \nof view, `4XX` and `5XX` series statuses **are errors**.\n\nIn order to get your HTTP clients to return `SimpleError` for `4XX` and `5XX` series errors, you can wrap their `http.RoundTripper`\nusing `simplehttp.EnableHTTPStatusErrors(rt http.RoundTripper)`.\n\n## GRPC Status Codes\n\ngRPC status codes can be set automatically by using the [ecosystem/grpc](https://github.com/lobocv/simplerr/tree/master/ecosystem/grpc)\npackage to translate `simplerr` error codes to gRPC status codes and vice versa.\n\n### Converting SimpleError to gRPC status codes\nSince GRPC functions return an error, it is even convenient to integrate error code translation using an interceptor (middleware).\nThe package [ecosystem/grpc](https://github.com/lobocv/simplerr/tree/master/ecosystem/grpc) defines an interceptor\nthat detects if the returned error is a `SimpleError` and then translates the error code into a GRPC status code. A mapping\nfor several codes is provided using the `DefaultMapping()` function. This can be changed by providing an alternative mapping\nwhen creating the interceptor:\n\n```go\nfunc main() {\n    // Get the default registry mapping provided by simplerr\n    reg := simplegprc.GetDefaultRegistry()\n    \n    // Add another mapping from simplerr code to GRPC code\n    m := simplegprc.DefaultMapping()\n    m[simplerr.CodeMalformedRequest] = codes.InvalidArgument\n    \n    // Update the mapping in the default registry\n    reg.SetMapping(m)\n    \n    // Create the interceptor by providing the mapping\n    interceptor := simplerr.TranslateErrorCode(m)\n    \n    // Attach the interceptor to the server \n    // ...\n}\n```\n\n### Converting gRPC status codes to SimpleError from gRPC Clients\n\nYou can get your gRPC clients to return simplerr compatible errors by using the `ReturnSimpleErrors` unary client \ninterceptor. This interceptor examines the gRPC code in errors returned by the client and wraps them in an error that\nis compatible with simplerror while also maintaining compatibility with the gprc error checking functions \n`status.FromError()` and `status.Code()`:\n\n```go\nfunc main() {\n    // Create the interceptor by providing the mapping. \n\t// The nil argument means to use the default registry.\n    interceptor := ReturnSimpleErrors(nil)\n\n    conn, err := grpc.Dial(\":5001\",\n            grpc.WithUnaryInterceptor(interceptor),\n        )\n\n    client := ping.NewPingServiceClient(conn)\n}\n```\n\nUsing this interceptor, you will be able to extract the grpc method and `*status.Status` object from the error:\n\n```go\nv, ok := simplerr.GetAttribute(err, simplegrpc.AttrGRPCStatus)\nstatus := v.(*status.Status)\n```\n\n```go\nv, ok := simplerr.GetAttribute(err, simplegrpc.AttrGRPCMethod)\nmethod := v.(string)\n```\n\n\n# Contributing\n\nContributions and pull requests to `simplerr` are welcome but must align with the goals of the package:\n- Keep it simple\n- Features should have reasonable defaults but provide flexibility with optional configuration\n- Keep dependencies to a minimum\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flobocv%2Fsimplerr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flobocv%2Fsimplerr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flobocv%2Fsimplerr/lists"}