{"id":26022105,"url":"https://github.com/lainio/err2","last_synced_at":"2026-03-01T12:02:50.506Z","repository":{"id":43081980,"uuid":"200030123","full_name":"lainio/err2","owner":"lainio","description":"Automatic and modern error handling package for Go","archived":false,"fork":false,"pushed_at":"2025-11-14T12:47:38.000Z","size":840,"stargazers_count":75,"open_issues_count":0,"forks_count":8,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-11-14T14:33:49.916Z","etag":null,"topics":["assertion-library","error","error-handling","errors","go","golang","stacktrace","try-catch","unit-testing"],"latest_commit_sha":null,"homepage":"","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/lainio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-08-01T10:30:18.000Z","updated_at":"2025-11-14T12:46:05.000Z","dependencies_parsed_at":"2023-02-15T22:16:05.023Z","dependency_job_id":"6a33031d-1850-4348-b2fb-6ff1241b8e82","html_url":"https://github.com/lainio/err2","commit_stats":null,"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/lainio/err2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lainio%2Ferr2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lainio%2Ferr2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lainio%2Ferr2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lainio%2Ferr2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lainio","download_url":"https://codeload.github.com/lainio/err2/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lainio%2Ferr2/sbom","scorecard":{"id":577341,"data":{"date":"2025-08-11","repo":{"name":"github.com/lainio/err2","commit":"0c559f2a487bb7ae4c5b805cf7dedd1bcd4dfe1b"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.8,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/3 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/go.yml:1","Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/lainio/err2/go.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/lainio/err2/go.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:8: update your workflow using https://app.stepsecurity.io/secureworkflow/lainio/err2/test.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:10: update your workflow using https://app.stepsecurity.io/secureworkflow/lainio/err2/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/lainio/err2/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/lainio/err2/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/lainio/err2/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:37: update your workflow using https://app.stepsecurity.io/secureworkflow/lainio/err2/test.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:41: update your workflow using https://app.stepsecurity.io/secureworkflow/lainio/err2/test.yml/master?enable=pin","Warn: goCommand not pinned by hash: scripts/functions.sh:88","Info:   0 out of   7 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned","Info:   0 out of   1 goCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T18:11:41.561Z","repository_id":43081980,"created_at":"2025-08-20T18:11:41.561Z","updated_at":"2025-08-20T18:11:41.561Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29969243,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T11:43:06.159Z","status":"ssl_error","status_checked_at":"2026-03-01T11:43:03.887Z","response_time":124,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["assertion-library","error","error-handling","errors","go","golang","stacktrace","try-catch","unit-testing"],"created_at":"2025-03-06T09:54:21.073Z","updated_at":"2026-03-01T12:02:45.490Z","avatar_url":"https://github.com/lainio.png","language":"Go","readme":"# err2\n\n[![test](https://github.com/lainio/err2/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/lainio/err2/actions/workflows/test.yml)\n![Go Version](https://img.shields.io/badge/go%20version-%3E=1.18-61CFDD.svg?style=flat-square)\n[![PkgGoDev](https://pkg.go.dev/badge/mod/github.com/lainio/err2)](https://pkg.go.dev/mod/github.com/lainio/err2)\n[![Go Report Card](https://goreportcard.com/badge/github.com/lainio/err2?style=flat-square)](https://goreportcard.com/report/github.com/lainio/err2)\n\n\u003cimg src=\"https://github.com/lainio/err2/raw/master/logo/logo.png\" width=\"100\"\u003e\n\n----\n\nThe package extends Go's error handling with **fully automatic error checking\nand propagation** like other modern programming languages: **Zig**, Rust, Swift,\netc. `err2` isn't an exception handling library, but an entirely orthogonal\npackage with Go's existing error handling mechanism.\n\n```go\nfunc CopyFile(src, dst string) (err error) {\n\tdefer err2.Handle(\u0026err)\n\n\tr := try.To1(os.Open(src))\n\tdefer r.Close()\n\n\tw := try.To1(os.Create(dst))\n\tdefer err2.Handle(\u0026err, err2.Err(func(error) {\n\t\ttry.Out(os.Remove(dst)).Logf(\"cleaning error\")\n\t}))\n\tdefer w.Close()\n\n\ttry.To1(io.Copy(w, r))\n\treturn nil\n}\n```\n\n----\n\n`go get github.com/lainio/err2`\n\n- [Structure](#structure)\n- [Performance](#performance)\n- [Automatic Error Propagation](#automatic-error-propagation)\n- [Error handling](#error-handling)\n  - [Error Stack Tracing](#error-stack-tracing)\n- [Error Checks](#error-checks)\n  - [Filters for non-errors like io.EOF](#filters-for-non-errors-like-ioeof)\n- [Assertion](#assertion)\n  - [Asserters](#asserters)\n  - [Assertion Package for Runtime Use](#assertion-package-for-runtime-use)\n  - [Assertion Package for Unit Testing](#assertion-package-for-unit-testing)\n- [Automatic Flags](#automatic-flags)\n  - [Support for Cobra Flags](#support-for-cobra-flags)\n- [Code Snippets](#code-snippets)\n- [Background](#background)\n- [Learnings by so far](#learnings-by-so-far)\n- [Support And Contributions](#support-and-contributions)\n- [History](#history)\n\n\n## Structure\n\n`err2` has the following package structure:\n- The `err2` (main) package includes declarative error handling functions.\n- The `try` package offers error checking functions.\n- The `assert` package implements assertion helpers for **both** unit-testing\n  and *design-by-contract* with the *same API and cross-usage*.\n\n## Performance\n\nAll of the listed above **without any performance penalty**! You are welcome to\nrun `benchmarks` in the project repo and see yourself.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eIt's too fast!\u003c/b\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\n\u003e Most of the benchmarks run 'too fast' according to the common Go\n\u003e benchmarking rules, i.e., compiler optimizations\n\u003e ([inlining](https://en.wikipedia.org/wiki/Inline_expansion)) are working so\n\u003e well that there are no meaningful results. But for this type of package, where\n\u003e **we compete with if-statements, that's precisely what we hope to achieve.**\n\u003e The whole package is written toward that goal. Especially with parametric\n\u003e polymorphism, it's been quite the effort.\n\n\u003c/details\u003e\n\n## Automatic Error Propagation\n\nAutomatic error propagation is crucial because it makes your *code change\ntolerant*. And, of course, it helps to make your code error-safe.\n\n![Never send a human to do a machine's job](https://www.magicalquote.com/wp-content/uploads/2013/10/Never-send-a-human-to-do-a-machines-job.jpg)\n\n\n\u003cdetails\u003e\n\u003csummary\u003eThe err2 package is your automation buddy:\u003c/summary\u003e\n\u003cbr/\u003e\n\n1. It helps to declare error handlers with `defer`. If you're familiar with [Zig\n   language](https://ziglang.org/), you can think `defer err2.Handle(\u0026err,...)`\n   line exactly similar as\n   [Zig's `errdefer`](https://ziglang.org/documentation/master/#errdefer).\n2. It helps to check and transport errors to the nearest (the defer-stack) error\n   handler.\n3. It helps us use design-by-contract type preconditions.\n4. It offers automatic stack tracing for every error, runtime error, or panic.\n   If you are familiar with Zig, the `err2` error return traces are same as\n   Zig's.\n\nYou can use all of them or just the other. However, if you use `try` for error\nchecks, you must remember to use Go's `recover()` by yourself, or your error\nisn't transformed to an `error` return value at any point.\n\n\u003c/details\u003e\n\n## Error Handling\n\nThe err2 relies on Go's declarative programming structure `defer`. The\nerr2 helps to set deferred error handlers which are only called if an error\noccurs.\n\nThis is the simplest form of an automatic error handler:\n\n```go\nfunc doSomething() (err error) {\n    defer err2.Handle(\u0026err)\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eThe explanation of the above code and its error handler:\u003c/summary\u003e\n\u003cbr/\u003e\n\nSimplest rule for err2 error handlers are:\n1. Use named error return value: `(..., err error)`\n1. Add at least one error handler at the beginning of your function (see the\n   above code block). *Handlers are called only if error ≠ nil.*\n1. Use `err2.handle` functions different calling schemes to achieve needed\n   behaviour. For example, without no extra arguments `err2.Handle`\n   automatically annotates your errors by building annotations string from the\n   function's current name: `doSomething → \"do something:\"`. Default is decamel\n   and add spaces. See `err2.SetFormatter` for more information.\n1. Every function which uses err2 for error-checking should have at least one\n   error handler. The current function panics if there are no error handlers and\n   an error occurs. However, if *any* function above in the call stack has an\n   err2 error handler, it will catch the error.\n\nSee more information from `err2.Handle`'s documentation. It supports several\nerror-handling scenarios. And remember that you can have as many error handlers\nper function as you need. You can also chain error handling functions per\n`err2.Handle` that allows you to build new error handling middleware for your\nown purposes.\n\n\u003c/details\u003e\n\n#### Error Stack Tracing\n\nThe err2 offers optional stack tracing in two different formats:\n1. Optimized call stacks (`-err2-trace`)\n1. Error return traces similar to Zig (`-err2-ret-trace`)\n\nBoth are *automatic* and fully *optimized*. \n\n\u003cdetails\u003e\n\u003csummary\u003eThe example of the optimized call stack:\u003c/summary\u003e\n\u003cbr/\u003e\n\nOptimized means that the call stack is processed before output. That means that\nstack trace *starts from where the actual error/panic is occurred*, not where\nthe error or panic is caught. You don't need to search for the line where the\npointer was nil or received an error. That line is in the first one you are\nseeing:\n\n```\n---\nruntime error: index out of range [0] with length 0\n---\ngoroutine 1 [running]:\nmain.test2({0x0, 0x0, 0x40XXXXXf00?}, 0x2?)\n\t/home/.../go/src/github.com/lainio/ic/main.go:43 +0x14c\nmain.main()\n\t/home/.../go/src/github.com/lainio/ic/main.go:77 +0x248\n```\n\n\u003c/details\u003e\n\nJust set the `err2.SetErrorTracer`, `err2.SetErrRetTracer` or\n`err2.SetPanicTracer` to the stream you want traces to be written:\n\n```go\nerr2.SetErrorTracer(os.Stderr) // write error stack trace to stderr\n// or, for example:\nerr2.SetErrRetTracer(os.Stderr) // write error return trace (like Zig)\n// or, for example:\nerr2.SetPanicTracer(log.Writer()) // stack panic trace to std logger\n```\n\nIf no `Tracer` is set no stack tracing is done. This is the default because in\nthe most cases proper error messages are enough and panics are handled\nimmediately by a programmer.\n\n\u003e [!NOTE]\n\u003e Since v0.9.5 you can set *tracers* through Go's standard flag package just by\n\u003e adding `flag.Parse()` call to your source code. See more information from\n\u003e [Automatic Flags](#automatic-flags).\n\n[Read the package documentation for more\ninformation](https://pkg.go.dev/github.com/lainio/err2).\n\n## Error Checks\n\nThe `try` package provides convenient helpers to check the errors. Since the Go\n1.18 we have been using generics to have fast and convenient error checking.\n\nFor example, instead of\n\n```go\nb, err := io.ReadAll(r)\nif err != nil {\n        return err\n}\n...\n```\nwe can call\n```go\nb := try.To1(io.ReadAll(r))\n...\n```\n\nbut not without an error handler (`err2.Handle`). However, you can put your\nerror handlers where ever you want in your call stack. That can be handy in the\ninternal packages and certain types of algorithms.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eImmediate Error Handling Options\u003c/b\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\nIn cases where you want to handle the error immediately after the function call\nyou can use Go's default `if` statement. However, we recommend you to use \n`defer err2.Handle(\u0026err)` for all of your error handling, because it keeps your\ncode modifiable, refactorable, and skimmable.\n\nNevertheless, there might be cases where you might want to:\n1. Suppress the error and use some default value. In next, use 100 if `Atoi`\n   fails:\n   ```go\n   b := try.Out1(strconv.Atoi(s)).Catch(100)\n   ```\n1. Just write logging output and continue without breaking the execution. In\n   next, add log if `Atoi` fails.\n   ```go\n   b := try.Out1(strconv.Atoi(s)).Logf(\"%s =\u003e 100\", s).Catch(100)\n   ```\n1. Annotate the specific error value even when you have a general error handler.\n   You are already familiar with `try.To` functions. There's *fast* annotation\n   versions `try.T` which can be used as shown below:\n   ```go\n   b := try.T1(io.ReadAll(r))(\"cfg file read\")\n   // where original were, for example:\n   b := try.To1(io.ReadAll(r))\n   ```\n1. You want to handle the specific error value at the same line or statement. In\n   below, the function `doSomething` returns an error value. If it returns\n   `ErrNotSoBad`, we just suppress it. All the other errors are send to the\n   current error handler and will be handled there, but are also annotated with\n   'fatal' prefix before that here.\n   ```go\n   try.Out(doSomething()).Handle(ErrNotSoBad, err2.Reset).Handle(\"fatal\")\n   ```\n\nThe `err2/try` package offers other helpers based on the error-handling\nlanguage/API. It's based on functions `try.Out`, `try.Out1`, and `try.Out2`,\nwhich return instances of types `Result`, `Result1`, and `Result2`. The\n`try.Result` is similar to other programming languages, i.e., discriminated\nunion. Please see more from its documentation.\n\nIt's easy to see that panicking about the errors at the start of the development\nis far better than not checking errors at all. But most importantly, `err2/try`\n**keeps the code readable.**\n\n\u003c/details\u003e\n\n#### Filters for non-errors like io.EOF\n\nWhen error values are used to transport some other information instead of\nactual errors we have functions like `try.Is` and even `try.IsEOF` for\nconvenience.\n\nWith these you can write code where error is translated to boolean value:\n\n```go\nnotExist := try.Is(r2.err, plugin.ErrNotExist)\n\n// real errors are cought and the returned boolean tells if value\n// dosen't exist returned as `plugin.ErrNotExist`\n```\n\n\u003e [!NOTE] \n\u003e Any other error than `plugin.ErrNotExist` is treated as an real error:\n\u003e 1. `try.Is` function first checks `if err == nil`, and if yes, it returns\n\u003e    `false`.\n\u003e 2. Then it checks if `errors.Is(err, plugin.ErrNotExist)` and if yes, it returns\n\u003e    `true`.\n\u003e 3. Finally, it calls `try.To` for the non nil error, and we already know what then\n\u003e    happens: nearest `err2.Handle` gets it first.\n\nFor more information see the examples in the documentation of both functions.\n\n## Assertion\n\nThe `assert` package is meant to be used for *design-by-contract-* type of\ndevelopment where you set pre- and post-conditions for *all* of your functions,\n*including test functions*. These asserts are as fast as if-statements when not\ntriggered.\n\n\u003e [!IMPORTANT]\n\u003e It works *both runtime and for tests.* And even better, same asserts work in\n\u003e both running modes.\n\n#### Asserters\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eFast Clean Code with Asserters\u003c/b\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\nAsserts are not meant to replace the normal error checking but speed up the\nincremental hacking cycle like TDD. The default mode is to return an `error`\nvalue that includes a formatted and detailed assertion violation message. A\ndeveloper gets immediate and proper feedback independently of the running mode,\nallowing very fast feedback cycles.\n\nThe assert package offers a few pre-build *asserters*, which are used to\nconfigure *how the assert package deals with assert violations*. The line below\nexemplifies how the default asserter is set in the package. (See the\ndocumentation for more information about asserters.)\n\n```go\nassert.SetDefault(assert.Production)\n```\n\nIf you want to suppress the caller info (source file name, line number, etc.)\nfrom certain asserts, you can do that per a goroutine or a function. You should\nset the asserter with the following line for the current function:\n\n```go\ndefer assert.PushAsserter(assert.Plain)()\n```\n\nThis is especially good if you want to use assert functions for CLI's flag\nvalidation or you want your app behave like legacy Go programs.\n\n\u003c/details\u003e\n\n\u003e [!NOTE]\n\u003e Since v0.9.5 you can set these asserters through Go's standard flag package\n\u003e just by adding `flag.Parse()` to your program. See more information from\n\u003e [Automatic Flags](#automatic-flags).\n\n#### Assertion Package for Runtime Use\n\nFollowing is example of use of the assert package:\n\n```go\nfunc marshalAttestedCredentialData(json []byte, data *protocol.AuthenticatorData) []byte {\n     assert.SLen(data.AttData.AAGUID, 16, \"wrong AAGUID length\")\n     assert.NotEmpty(data.AttData.CredentialID, \"empty credential id\")\n     assert.SNotEmpty(data.AttData.CredentialPublicKey, \"empty credential public key\")\n     ...\n```\n\nWe have now described design-by-contract for development and runtime use. What\nmakes err2's assertion packages unique, and extremely powerful, is its use for\nautomatic testing as well.\n\n#### Assertion Package for Unit Testing\n\nThe same asserts can be used **and shared** during the unit tests over module\nboundaries.\n\n\u003cdetails\u003e\n\u003csummary\u003eThe unit test code example:\u003c/summary\u003e\n\n```go\nfunc TestWebOfTrustInfo(t *testing.T) {\n\tdefer assert.PushTester(t)()\n\n\tcommon := dave.CommonChains(eve.Node)\n\tassert.SLen(common, 2)\n\n\twot := dave.WebOfTrustInfo(eve.Node) //\u003c- this includes asserts as well!!\n\t// And if there's violations during the test run they are reported as\n\t// test failures for this TestWebOfTrustInfo -test.\n\n\tassert.Equal(wot.CommonInvider, 0)\n\tassert.Equal(wot.Hops, 1)\n\n\twot = NewWebOfTrust(bob.Node, carol.Node)\n\tassert.Equal(wot.CommonInvider, hop.NotConnected)\n\tassert.Equal(wot.Hops, hop.NotConnected)\n\t...\n```\n\nA compelling feature is that even if some assertion violation happens during the\nexecution of called functions like the above `NewWebOfTrust()` function instead\nof the actual Test function, **it's reported as a standard test failure.** That\nmeans we don't need to open our internal pre- and post-conditions just for\ntesting.\n\n\u003c/details\u003e\n\n**We can share the same assertions between runtime and test execution.**\n\nThe err2 `assert` package integration to the Go `testing` package is completed at\nthe cross-module level. Suppose package A uses package B. If package B includes\nruntime asserts in any function that A calls during testing and some of B's\nasserts fail, A's current test also fails. There is no loss of information, and\neven the stack trace is parsed to test logs for easy traversal. Packages A and B\ncan be the same or different modules.\n\n**This means that where ever assertion violation happens during the test\nexecution, we will find it and can even move thru every step in the call\nstack.**\n\n## Automatic Flags\n\nWhen you are using `err2` or `assert` packages, i.e., just importing them, you\nhave an option to automatically support for err2 configuration flags through\nGo's standard `flag` package. See more information about err2 settings from\n[Error Stack Tracing](#error-stack-tracing) and [Asserters](#asserters).\n\nYou can deploy your applications and services with the simple *end-user friendly\nerror messages and no stack traces.*\n\n\u003cdetails\u003e\n\u003csummary\u003eYou can switch them on whenever you need them again.\u003c/summary\u003e\n\u003cbr/\u003e\n\nLet's say you have build CLI (`your-app`) tool with the support for Go's flag\npackage, and the app returns an error. Let's assume you're a developer. You can\nrun it again with:\n\n```\nyour-app -err2-trace stderr\n```\n\nNow you get full error trace addition to the error message. Naturally, this\nalso works with assertions. You can configure their output with the flag\n`asserter`:\n\n```\nyour-app -asserter Debug\n```\n\nThat adds more information to the assertion statement, which in default is in\nproduction (`Prod`) mode, i.e., outputs a single-line assertion message.\n\nAll you need to do is to add `flag.Parse` to your `main` function.\n\u003c/details\u003e\n\n#### Support for Cobra Flags\n\nIf you are using [cobra](https://github.com/spf13/cobra) you can still easily\nsupport packages like `err2` and `glog` and their flags.\n\n\u003cdetails\u003e\n\u003csummary\u003eAdd cobra support:\u003c/summary\u003e\n\u003cbr/\u003e\n\n1. Add std flag package to imports in `cmd/root.go`:\n\n   ```go\n   import (\n       goflag \"flag\"\n       ...\n   )\n   ```\n\n1. Add the following to (usually) `cmd/root.go`'s `init` function's end:\n\n   ```go\n   func init() {\n       ...\n       // NOTE! Very important. Adds support for std flag pkg users: glog, err2\n       pflag.CommandLine.AddGoFlagSet(goflag.CommandLine)\n   }\n   ```\n\n1. And finally modify your `PersistentPreRunE` in `cmd/root.go` to something\n   like:\n\n   ```go\n   PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {\n       defer err2.Handle(\u0026err)\n\n       // NOTE! Very important. Adds support for std flag pkg users: glog, err2\n       goflag.Parse()\n\n       try.To(goflag.Set(\"logtostderr\", \"true\"))\n       handleViperFlags(cmd) // local helper with envs\n       glog.CopyStandardLogTo(\"ERROR\") // for err2\n       return nil\n   },\n   ```\n\nAs a result you can have bunch of usable flags added to your CLI:\n\n```\nFlags:\n      --asserter asserter                 asserter: Plain, Prod, Dev, Debug (default Prod)\n      --err2-log stream                   stream for logging: nil -\u003e log pkg (default nil)\n      --err2-panic-trace stream           stream for panic tracing (default stderr)\n      --err2-trace stream                 stream for error tracing: stderr, stdout (default nil)\n      ...\n```\n\n\u003c/details\u003e\n\n## Code Snippets\n\n\u003cdetails\u003e\n\u003csummary\u003eCode snippets as learning helpers.\u003c/summary\u003e\n\u003cbr/\u003e\n\nThe snippets are in `./snippets` and in VC code format, which is well supported\ne.g. neovim, etc. They are proven to be useful tool especially when you are\nstarting to use the err2 and its sub-packages.\n\nThe snippets must be installed manually to your preferred IDE/editor. During the\ninstallation you can modify the according your style or add new ones. We would\nprefer if you could contribute some of the back to the err2 package.\n\u003c/details\u003e\n\n## Background\n\n\u003cdetails\u003e\n\u003csummary\u003eWhy this repo exists?\u003c/summary\u003e\n\u003cbr/\u003e\n\n`err2` implements similar error handling mechanism as drafted in the original\n[check/handle\nproposal](https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md).\nThe package does it by using internally `panic/recovery`, which some might think\nisn't perfect.\n\nWe have run many benchmarks try to minimise the performance penalty this kind of\nmechanism might bring. We have focused on the _happy path_ analyses. If the\nperformance of the *error path* is essential, don't use this mechanism presented\nhere. **But be aware that something is wrong if your code uses the error path as\npart of the algorithm itself.**\n\n**For happy paths** by using `try.To*` or `assert.That` error check functions\n**there are no performance penalty at all**. However, the mandatory use of the\n`defer` might prevent some code optimisations like function inlining. And still,\nwe have cases where using the `err2` and `try` package simplify the algorithm so\nthat it's faster than the return value if err != nil version. (**See the\nbenchmarks for `io.Copy` in the repo.**)\n\nIf you have a performance-critical use case, we always recommend you to write\nperformance tests to measure the effect. As a general guideline for maximum\nperformance we recommend to put error handlers as high in the call stack as\npossible, and use only error checking (`try.To()` calls) in the inner loops. And\nyes, that leads to non-local control structures, but it's the most performant\nsolution of all. (The repo has benchmarks for that as well.)\n\nThe original goal was to make it possible to write similar code that the\nproposed Go2 error handling would allow and do it right now (summer 2019). The\ngoal was well aligned with the Go2 proposal, where it would bring a `try` macro\nand let the error handling be implemented in defer blocks. The try-proposal was\ncanceled at its latest form. Nevertheless, we have learned that **using panics**\nfor early-stage **error transport isn't bad but the opposite**. It seems to\nhelp:\n- to draft algorithms much faster,\n- huge improvements for the readability,\n- helps to bring a new blood (developers with different programming language\n  background) to projects,\n- and most importantly, **it keeps your code more refactorable** because you\n  don't have to repeat yourself.\n\u003c/details\u003e\n\n## Learnings by so far\n\n\u003cdetails\u003e\n\u003csummary\u003eWe have used the err2 and assert packages in several projects. \u003c/summary\u003e\n\u003cbr/\u003e\n\nThe results have been so far very encouraging:\n\n- If you forget to use handler, but you use checks from the package, you will\nget panics on errors (and optimized stack traces that can be suppressed). That\nis much better than getting unrelated panic somewhere else in the code later.\nThere have also been cases when code reports error correctly because the 'upper'\nhandler catches it.\n\n- Because the use of `err2.Handle` is so easy, error messages are much better\nand informative. When using `err2.Handle`'s automatic annotation your error\nmessages are always up-to-date. Even when you refactor your function name error\nmessage is also updated.\n\n- **When error handling is based on the actual error handlers, code changes have\nbeen much easier.** There is an excellent [blog post](https://jesseduffield.com/Gos-Shortcomings-1/)\nabout the issues you are facing with Go's error handling without the help of\nthe err2 package.\n\n- If you don't want to bubble up error from every function, we have learned that\n`Try` prefix convention is pretty cool way to solve limitations of Go\nprogramming language help to make your code more skimmable. If your internal\nfunctions normally would be something like `func CopyFile(s, t string) (err\nerror)`, you can replace them with `func TryCopyFile(s, t string)`, where `Try`\nprefix remind you that the function throws errors. You can decide at what level\nof the call stack you will catch them with `err2.Handle` or `err2.Catch`,\ndepending your case and API.\n\n\u003c/details\u003e\n\n## Support And Contributions\n\nThe package was in experimental mode quite long time. Since the Go generics we\ndid transit to official mode. Currently we offer support by GitHub Issues and\nDiscussions. Naturally, we appreciate all feedback and contributions are\nvery welcome!\n\n## History\n\nPlease see the full version history from [CHANGELOG](./CHANGELOG.md).\n\n### Latest Release\n\n##### 1.2.1\n- Optimization and Refactoring \n- Updated documentation\n","funding_links":[],"categories":["Go"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flainio%2Ferr2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flainio%2Ferr2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flainio%2Ferr2/lists"}