{"id":21199180,"url":"https://github.com/r167/erreql","last_synced_at":"2025-03-14T22:22:18.672Z","repository":{"id":207897719,"uuid":"720307246","full_name":"R167/erreql","owner":"R167","description":"golang analysis for checking error types by equality instead of errors.Is","archived":false,"fork":false,"pushed_at":"2024-06-27T17:25:24.000Z","size":17,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-21T14:46:50.143Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/R167.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}},"created_at":"2023-11-18T04:31:48.000Z","updated_at":"2024-06-27T17:25:27.000Z","dependencies_parsed_at":"2025-01-21T14:43:39.599Z","dependency_job_id":"e7c633a6-d895-4213-994c-ec688a8be8cd","html_url":"https://github.com/R167/erreql","commit_stats":null,"previous_names":["r167/erreql"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/R167%2Ferreql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/R167%2Ferreql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/R167%2Ferreql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/R167%2Ferreql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/R167","download_url":"https://codeload.github.com/R167/erreql/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243653481,"owners_count":20325748,"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":[],"created_at":"2024-11-20T19:59:35.498Z","updated_at":"2025-03-14T22:22:18.641Z","avatar_url":"https://github.com/R167.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# erreql\n\n**NOTE:** This linter is funcitonal and generally works quite well, though after writing it, I learned about https://github.com/polyfloyd/go-errorlint. For production systems, I would generally recommend using that analyzer instead.\n\nAn anaylsis to check for usages of checking error types using `==` equality instead of `errors.Is` introduced in [go1.13](https://go.dev/doc/go1.13#error_wrapping).\n\nWhen passing errors in an application, it's useful to capture stack traces of where an error was encountered by wrapping the original error. However, this breaks equality checks for statically defined errors. For example:\n\n```go\npackage main\n\nimport (\n  \"errors\"\n  \"runtime/debug\"\n)\n\nvar ErrNotFound = errors.New(\"not found\")\n\nfunc database() error {\n  return ErrNotFound\n}\n\nfunc helper() error {\n  if err := database(); err != nil {\n    return WithTrace(err)\n  }\n  return nil\n}\n\nfunc controllerB() error {\n  return helper()\n}\n\nfunc main() {\n  err := controllberB()\n  if err == nil {\n    // untyped nil is fine to compare against\n  } else if err == ErrNotFound {\n    // Oh no! b/c helper() wraps the database error, we never hit this path\n  } else if errors.Is(err, ErrNotFound) {\n    // This is the correct way to check the underlying type of an error\n  }\n}\n\ntype TraceError struct {\n  err   error\n  stack []byte\n}\n\nfunc (e TraceError) Error() string { return e.Error() }\nfunc (e TraceError) Trace() []byte { return e.stack }\nfunc (e TraceError) Unwrap() error { return e.err }\n\nfunc WithTrace(err error) error { return TraceError{err, debug.Stack()} }\n```\n\nHowever, this package makes an exception for special \"sentinel error value\" types which generally indicate an end of normal operations when calling the function directly. For example, `io.EOF` is returned to indicate the end of an input stream\n\n```go\n// ReadLength reads upto n bytes from r\nfunc ReadLength(n int, r io.Reader) ([]byte, error) {\n\tb := make([]byte, 0, n)\n\tfor {\n\t\tc, err := r.Read(b[len(b):cap(b)])\n\t\tb = b[:len(b)+c]\n\t\tif err != nil {\n      // allow sentinel values to use `==` checks\n\t\t\tif err == io.EOF {\n\t\t\t\treturn b, nil\n\t\t\t}\n\t\t\treturn b, err\n\t\t}\n\n\t\tif len(b) == cap(b) {\n\t\t\t// At capacity\n\t\t\treturn b, nil\n\t\t}\n\t}\n}\n```\n\nSentinel values are currently defined as\n- Comparison of an identifier which implements `error`\n- Identifier name does NOT match `^err.|Err|Exception` e.g.\n  - `db.ErrNotFound` - use errors.Is\n  - `internalError` - use errors.Is\n  - `cursor.EndOfData` - sentinel value\n\nIn general, errors in golang should follow the naming pattern `errName/ErrName` so this case should be fairly reasonable in most scenarios, however there are edge casese to be mindful of (and note this is best effort)\n\n```go\nvar ignoredErrors = []error{\n  db.ErrNotFound,\n  context.DeadlineExceeded,\n}\n\nfunc maybeSwallow(err error) error {\n  for _, skip := range ignoredErrors {\n    if err == skip {\n      // accepted by the linter. skip is treated as sentinel\n      return nil\n    }\n    if skipErr := skip; err == skipErr {\n      // LINT ERROR: Expected errors.Is but got ==\n      return nil\n    } else if errors.Is(err, skipErr) {\n      // linter is happy again\n      return nil\n    }\n  }\n  return err\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fr167%2Ferreql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fr167%2Ferreql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fr167%2Ferreql/lists"}