diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6f92b0..39b7004 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: [ '1.19', '1.20', '1.21.x' ] + go-version: [ '1.19', '1.20', '1.21', '1.22.x' ] steps: - uses: actions/checkout@v4 @@ -54,7 +54,7 @@ jobs: - name: Install golangci-lint run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.52.2 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.57.2 - name: Run golangci-lint run: ./bin/golangci-lint run -v diff --git a/.golangci.yml b/.golangci.yml index 0467449..5d66255 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,148 +1,127 @@ -## Golden config for golangci-lint v1.52.2 +# This code is licensed under the terms of the MIT license https://opensource.org/license/mit +# Copyright (c) 2021 Marat Reymers + +## Golden config for golangci-lint v1.57.2 # # This is the best config for golangci-lint based on my experience and opinion. # It is very strict, but not extremely strict. -# Feel free to adopt and change it for your needs. +# Feel free to adapt and change it for your needs. run: - timeout: 10m # default 1m + # Timeout for analysis, e.g. 30s, 5m. + # Default: 1m + timeout: 3m + +# This file contains only configs which differ from defaults. +# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml linters-settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 30 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled + # Default: 0.0 + package-average: 10.0 + errcheck: - disable-default-exclusions: false # disable built-in exclude list # default: false - check-type-assertions: true # default false - check-blank: false # default false - ignore: "fmt:.*" # default fmt:.* - exclude-functions: [] # see https://github.com/kisielk/errcheck#excluding-functions for details # default [] - gosimple: - go: "1.20" # default 1.13 - checks: [ "*" ] # https://staticcheck.io/docs/options#checks # default ["*"] - govet: - enable-all: true - disable: - - fieldalignment # too strict - settings: - shadow: - strict: true # default false - staticcheck: - go: "1.20" # default 1.13 - checks: [ "*" ] # https://staticcheck.io/docs/options#checks # default ["*"] - structcheck: - exported-fields: false # default false - unused: - check-exported: false # default false # TODO: enable after fixing false positives - varcheck: - exported-fields: false # default false # TODO: enable after fixing false positives + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: true - bidichk: - # The following configurations check for all mentioned invisible unicode runes. - # All runes are enabled by default. - left-to-right-embedding: true # default true - right-to-left-embedding: true # default true - pop-directional-formatting: true # default true - left-to-right-override: true # default true - right-to-left-override: true # default true - left-to-right-isolate: true # default true - right-to-left-isolate: true # default true - first-strong-isolate: true # default true - pop-directional-isolate: true # default true - cyclop: - max-complexity: 30 # the maximal code complexity to report # default 10 - package-average: 10.0 # the maximal average package complexity. If it's higher than 0.0 (float) the check is enabled # default 0.0 - skip-tests: false # should ignore tests # default false - dupl: - threshold: 150 # default 150 - errorlint: - # Check whether fmt.Errorf uses the %w verb for formatting errors. See the readme for caveats - errorf: true # default true - # Check for plain type assertions and type switches - asserts: true # default true - # Check for plain error comparisons - comparison: true # default true exhaustive: - check-generated: false # indicates whether to check switch statements in generated Go source files # default false - default-signifies-exhaustive: false # if true, presence of "default" case in switch statements satisfies exhaustiveness, even if all enum members are not listed # default false - ignore-enum-members: "" # enum members matching the supplied regex do not have to be listed in switch statements to satisfy exhaustiveness # default "" - package-scope-only: false # consider enums only in package scopes, not in inner scopes # default false - # forbidigo: - # forbid: # forbid the following identifiers # default ^(fmt\.Print(|f|ln)|print|println)$ - # - ^(fmt\.Print(|f|ln)|print|println)$ - # exclude-godoc-examples: true # exclude godoc examples from forbidigo checks # default is true + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + + exhaustruct: + # List of regular expressions to exclude struct packages and their names from checks. + # Regular expressions must match complete canonical struct package/name/structname. + # Default: [] + exclude: + # std libs + - "^net/http.Client$" + - "^net/http.Cookie$" + - "^net/http.Request$" + - "^net/http.Response$" + - "^net/http.Server$" + - "^net/http.Transport$" + - "^net/url.URL$" + - "^os/exec.Cmd$" + - "^reflect.StructField$" + # public libs + - "^github.com/Shopify/sarama.Config$" + - "^github.com/Shopify/sarama.ProducerMessage$" + - "^github.com/mitchellh/mapstructure.DecoderConfig$" + - "^github.com/prometheus/client_golang/.+Opts$" + - "^github.com/spf13/cobra.Command$" + - "^github.com/spf13/cobra.CompletionOptions$" + - "^github.com/stretchr/testify/mock.Mock$" + - "^github.com/testcontainers/testcontainers-go.+Request$" + - "^github.com/testcontainers/testcontainers-go.FromDockerfile$" + - "^golang.org/x/tools/go/analysis.Analyzer$" + - "^google.golang.org/protobuf/.+Options$" + - "^gopkg.in/yaml.v3.Node$" + funlen: - lines: 110 # default 60 - statements: -1 # default 40 + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 120 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 50 + # Ignore comments when counting lines. + # Default false + ignore-comments: true + gocognit: - min-complexity: 20 # minimal code complexity to report, 30 by default (but we recommend 10-20) - goconst: - match-constant: true # look for existing constants matching the values # default true - min-len: 3 # minimal length of string constant # default 3 - min-occurrences: 3 # minimum occurrences of constant string count to trigger issue # default 3 - numbers: true # search also for duplicated numbers # default false - min: 3 # minimum value, only works with goconst.numbers # default 3 - max: 3 # maximum value, only works with goconst.numbers # default 3 - ignore-calls: true # ignore when constant is not used as function argument # default true - ignore-tests: false # ignore test files # default false + # Minimal code complexity to report. + # Default: 30 (but we recommend 10-20) + min-complexity: 20 + gocritic: + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be find in https://go-critic.github.io/overview. settings: captLocal: - paramsOnly: false # whether to restrict checker to params only # default true - elseif: - skipBalanced: false # whether to skip balanced if-else pairs # default true - #hugeParam: # disabled by default - # sizeThreshold: 80 # size in bytes that makes the warning trigger # default 80 - #nestingReduce: # disabled by default - # bodyWidth: 5 # min number of statements inside a branch to trigger a warning # default 5 - #rangeExprCopy: # disabled by default - # sizeThreshold: 512 # size in bytes that makes the warning trigger # default 512 - # skipTestFuncs: true # whether to check test functions # default true - #rangeValCopy: # disabled by default - # sizeThreshold: 128 # size in bytes that makes the warning trigger # default 128 - # skipTestFuncs: true # whether to check test functions # default true - #ruleguard: # disabled by default - # rules: "" # path to a gorules file # default "" - #tooManyResultsChecker: # disabled by default - # maxResults: 5 # maximum number of results # default 5 - #truncateCmp: # disabled by default - # skipArchDependent: true # whether to skip int/uint/uintptr types # default true + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false underef: - skipRecvDeref: false # whether to skip (*x).method() calls where x is a pointer receiver # default true - #unnamedResult: # disabled by default - # checkExported: false # whether to check exported functions # default false - gocyclo: - min-complexity: 40 # default 30 - godot: - scope: declarations # comments to be checked: `declarations` (default), `toplevel`, or `all` - exclude: [] # list of regexps for excluding particular comment lines from check # default [] - capital: false # check that each sentence starts with a capital letter # default false - period: true # check that each sentence ends with a period # default true + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + gomnd: - # List of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description. - checks: # default argument,case,condition,operation,return,assign - - argument - - case - - condition - - operation - - return - - assign - # List of numbers to exclude from analysis. The numbers should be written as string. - # Following values always ignored: "1", "1.0", "0" and "0.0" - ignored-numbers: [] # default [] - # List of file patterns to exclude from analysis. - # Following values always ignored: `.+_test.go` - ignored-files: [] # default [] # List of function patterns to exclude from analysis. - # Following functions always ignored: `time.Date` - ignored-functions: ["strconv.ParseUint"] # default [] - gomoddirectives: - replace-allow-list: [] # list of allowed `replace` directives # default [] - replace-local: false # allow local `replace` directives # default false - exclude-forbidden: false # forbid the use of `exclude` directives # default false - retract-allow-no-explanation: false # allow to use `retract` directives without explanation # default false + # Values always ignored: `time.Date`, + # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`, + # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`. + # Default: [] + ignored-functions: + - flag.Arg + - flag.Duration.* + - flag.Float.* + - flag.Int.* + - flag.Uint.* + - os.Chmod + - os.Mkdir.* + - os.OpenFile + - os.WriteFile + - prometheus.ExponentialBuckets.* + - prometheus.LinearBuckets + gomodguard: - allowed: - modules: [] # default [] - domains: [] # default [] blocked: + # List of blocked modules. + # Default: [] modules: - github.com/golang/protobuf: recommendations: @@ -154,214 +133,208 @@ linters-settings: reason: "satori's package is not maintained" - github.com/gofrs/uuid: recommendations: - - github.com/google/uuid - reason: "see recommendation from dev-infra team: https://confluence.gtforge.com/x/gQI6Aw" - versions: [] # default [] - local_replace_directives: true # default false + - github.com/gofrs/uuid/v5 + reason: "gofrs' package was not go module before v5" + + gosec: + excludes: + - G404 # This error is triggered when we use math/rand instead of crypto/rand (Too strict) + + govet: + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: true + + inamedparam: + # Skips check for interface methods with only a single parameter. + # Default: false + skip-single-param: true + lll: - line-length: 120 # default 120 - makezero: - always: false # default false - maligned: - suggest-new: true # default false - misspell: - locale: US - ignore-words: - - "" # default: "" + # Make an issue if line has more characters than this setting. + # Default: 120 + line-length: 160 + tab-width: 1 + nakedret: - max-func-lines: 0 # default 30 - nestif: - min-complexity: 5 # default 5 - nilnil: - checked-types: # default [ptr, func, iface, map, chan] - - ptr - - func - - iface - - map - - chan + # Make an issue if func has more lines of code than this setting, and it has naked returns. + # Default: 30 + max-func-lines: 0 + nolintlint: - allow-unused: false # default false - allow-leading-space: true # default true - allow-no-explanation: [funlen, gocognit, lll] # default [] - require-explanation: true # default false - require-specific: true # default false - prealloc: - simple: false # default true - range-loops: true # default true - for-loops: false # default false - predeclared: - ignore: "" # comma-separated list of predeclared identifiers to not report on # default "" - q: false # include method names and field names (i.e., qualified names) in checks # default false - promlinter: - # Promlinter cannot infer all metrics name in static analysis. - # Enable strict mode will also include the errors caused by failing to parse the args. - strict: false # default false - # Please refer to https://github.com/yeya24/promlinter#usage for detailed usage. - # disabled-linters: - # - "Help" - # - "MetricUnits" - # - "Counter" - # - "HistogramSummaryReserved" - # - "MetricTypeInName" - # - "ReservedChars" - # - "CamelCase" - # - "lintUnitAbbreviations" - revive: - # default rules are ignored if any of following settings is defined - #max-open-files: 0 # maximum number of open files at the same time # defaults 0 - unlimited - #ignore-generated-header: false # when set to false, ignores files with "GENERATED" header, similar to golint # default false - #confidence: 0.3 # default failure confidence, this means that linting errors with less than X confidence will be ignored # default 0.8 - #severity: "warning" # minimal rule severity to fail {"error", "warning"} # default "warning" - #enable-all-rules: false # default false - # There is a list of default rules, but it can be redefined, see https://github.com/mgechev/revive#available-rules - rules: - - name: "var-naming" - arguments: [["ID"], []] - # - name: "xxx" - # disabled: false - # arguments: [] - # severity: "xxx" - # allows to redefine rule severity (without changing default rules list) - #directives: - # - name: "xxx" - # severity: "xxx" + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: [ funlen, gocognit, lll ] + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + + perfsprint: + # Optimizes into strings concatenation. + # Default: true + strconcat: false + rowserrcheck: + # database/sql is always checked + # Default: [] packages: - - database/sql - github.com/jmoiron/sqlx - # stylecheck: - # go: "1.20" # default 1.13 - # checks: [ "*" ] # https://staticcheck.io/docs/options#checks # default ["*"] - # dot-import-whitelist: [] # https://staticcheck.io/docs/options#dot_import_whitelist # default [] - # initialisms: [ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS" ] # https://staticcheck.io/docs/options#initialisms - # http-status-code-whitelist: [ "200", "400", "404", "500" ] # https://staticcheck.io/docs/options#http_status_code_whitelist + tenv: - all: true # check all functions in _test.go, not only test functions # default false - testpackage: - skip-regexp: (export|internal)_test\.go # default (export|internal)_test\.go - unparam: - check-exported: true # default false - wrapcheck: - ignoreSigs: [] # specifies substrings of signatures to ignore. Overrides default https://github.com/tomarrell/wrapcheck#configuration # default [] - ignoreSigRegexps: [] # this is similar to the ignoreSigs, but gives slightly more flexibility # default [] - ignorePackageGlobs: [] # see https://github.com/tomarrell/wrapcheck#configuration # default [] + # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. + # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. + # Default: false + all: true + linters: disable-all: true enable: ## enabled by default - - errcheck - - gosimple - - govet - - ineffassign - - staticcheck - - typecheck - - unused + - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases + - gosimple # specializes in simplifying a code + - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # detects when assignments to existing variables are not used + - staticcheck # is a go vet on steroids, applying a ton of static analysis checks + - typecheck # like the front-end of a Go compiler, parses and type-checks Go code + - unused # checks for unused constants, variables, functions and types ## disabled by default - - asciicheck - - bidichk - - bodyclose - # - containedctx - - contextcheck - # - cyclop - - decorder - - dogsled - # - dupl - - durationcheck - - errchkjson - - errname - - errorlint - - exhaustive - - exportloopref - - funlen - # - gochecknoglobals - - gochecknoinits - # - gocognit # NEED - - goconst - # - gocritic - # - gocyclo - - godot - # - godox - - goimports - - gomnd - - gomoddirectives - - gomodguard - - goprintffuncname - # - gosec - - grouper - - importas - # - lll # long long lines - # - maintidx - - makezero - - nakedret - #- nestif - - nilerr - - nilnil - - nlreturn - - noctx - - nolintlint - - prealloc - - predeclared - - promlinter - - revive - - rowserrcheck - - sqlclosecheck - - tenv - - testpackage - # - thelper - - tparallel - - unconvert - - unparam - - wastedassign - - whitespace - - wsl + - asasalint # checks for pass []any as any in variadic func(...any) + - asciicheck # checks that your code does not contain non-ASCII identifiers + - bidichk # checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - copyloopvar # detects places where loop variables are copied + - cyclop # checks function and package cyclomatic complexity + - dupl # tool for code clone detection + - durationcheck # checks for two durations multiplied together + - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 + - execinquery # checks query string in Query function which reads your Go src files and warning it finds + - exhaustive # checks exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - forbidigo # forbids identifiers + - funlen # tool for detection of long functions + - gocheckcompilerdirectives # validates go compiler directive comments (//go:) + - gochecknoinits # checks that no init functions are present in Go code + - gochecksumtype # checks exhaustiveness on Go "sum types" + - gocognit # computes and checks the cognitive complexity of functions + - goconst # finds repeated strings that could be replaced by a constant + - gocritic # provides diagnostics that check for bugs, performance and style issues + - gocyclo # computes and checks the cyclomatic complexity of functions + - godot # checks if comments end in a period + - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt + - gomnd # detects magic numbers + - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod + - gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations + - goprintffuncname # checks that printf-like functions are named with f at the end + - gosec # inspects source code for security problems + - intrange # finds places where for loops could make use of an integer range + - lll # reports long lines + - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) + - makezero # finds slice declarations with non-zero initial length + - mirror # reports wrong mirror patterns of bytes/strings usage + - musttag # enforces field tags in (un)marshaled structs + - nakedret # finds naked returns in functions greater than a specified function length + - nestif # reports deeply nested if statements + - nilerr # finds the code that returns nil even if it checks that the error is not nil + - nilnil # checks that there is no simultaneous return of nil error and an invalid value + - noctx # finds sending http request without context.Context + - nolintlint # reports ill-formed or insufficient nolint directives + - nonamedreturns # reports all named returns + - perfsprint # checks that fmt.Sprintf can be replaced with a faster alternative + - prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated + - predeclared # finds code that shadows one of Go's predeclared identifiers + - promlinter # checks Prometheus metrics naming via promlint + - protogetter # reports direct reads from proto message fields when getters should be used + - reassign # checks that package variables are not reassigned + - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint + - rowserrcheck # checks whether Err of rows is checked successfully + - sloglint # ensure consistent code style when using log/slog + - spancheck # checks for mistakes with OpenTelemetry/Census spans + - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed + - stylecheck # is a replacement for golint + - tagalign # checks that struct tags are well aligned + - tenv # detects using os.Setenv instead of t.Setenv since Go1.17 + - testableexamples # checks if examples are testable (have an expected output) + - testifylint # checks usage of github.com/stretchr/testify + - testpackage # makes you use a separate _test package + - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes + - unconvert # removes unnecessary type conversions + - unparam # reports unused function parameters + - usestdlibvars # detects the possibility to use variables/constants from the Go standard library + - wastedassign # finds wasted assignment statements + - whitespace # detects leading and trailing whitespace + + ## you may want to enable + #- decorder # checks declaration order and count of types, constants, variables and functions + #- exhaustruct # [highly recommend to enable] checks if all structure fields are initialized + #- gci # controls golang package import order and makes it always deterministic + #- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega + #- godox # detects FIXME, TODO and other comment keywords + #- gochecknoglobals # checks that no global variables exist + #- goheader # checks is file header matches to pattern + #- inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters + #- interfacebloat # checks the number of methods inside an interface + #- ireturn # accept interfaces, return concrete types + #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope + #- wrapcheck # checks that errors returned from external packages are wrapped + #- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event + ## disabled - #- depguard # replaced with gomodguard - #- exhaustivestruct # too strict - finds structs that have uninitialized fields # TODO: maybe enable for some packages? - #- forbidigo # Forbids identifiers - #- forcetypeassert # errcheck is used instead - #- gci # is not used - sorts imports - #- goerr113 # too strict - checks the errors handling expressions - #- gofumpt # replaced with goimports, gofumports is not available yet - #- goheader # is not used - checks that each file has the licence at the beginning - #- golint # deprecated - revive is used instead - #- interfacer # deprecated and has false positives - #- ireturn # good, but too strict - accept interfaces, return concrete types - #- maligned # deprecated - #- misspell # useless - correct commonly misspelled English words... quickly - #- paralleltest # too many false positives - #- scopelint # deprecated - #- stylecheck # revive does the same - #- tagliatelle # is not used - checks the struct tags - #- wrapcheck # too strict - requires wrapping errors from external packages (even from the same repo) and interfaces - #- varnamelen # great idea, but too many false positives - checking length of variable's name matches its usage scope + #- containedctx # detects struct contained context.Context field + #- contextcheck # [too many false positives] checks the function whether use a non-inherited context + #- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages + #- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + #- dupword # [useless without config] checks for duplicate words in the source code + #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted + #- forcetypeassert # [replaced by errcheck] finds forced type assertions + #- goerr113 # [too strict] checks the errors handling expressions + #- gofmt # [replaced by goimports] checks whether code was gofmt-ed + #- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed + #- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase + #- grouper # analyzes expression groups + #- importas # enforces consistent import aliases + #- maintidx # measures the maintainability index of each function + #- misspell # [useless] finds commonly misspelled English words in comments + #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity + #- nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL + #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test + #- tagliatelle # checks the struct tags + #- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers + #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines -output: - uniq-by-line: false # default true issues: - max-issues-per-linter: 0 - max-same-issues: 0 + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 50 + exclude-rules: - - source: "^//\\s*go:generate\\s" - linters: - - lll - source: "(noinspection|TODO)" - linters: - - godot + linters: [ godot ] - source: "//noinspection" - linters: - - gocritic - - source: "^\\s+if _, ok := err\\.\\([^.]+\\.InternalError\\); ok {" - linters: - - errorlint + linters: [ gocritic ] - path: "_test\\.go" linters: - bodyclose - dupl - funlen - - gochecknoinits - goconst + - gosec - noctx - wrapcheck - - wsl diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index afb03d0..416662e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ We welcome contributions in the form of pull requests. If you want to contribute 1. Fork the repository and create your branch from `v1`. 2. Make sure your code adheres to the [Go coding standards](https://golang.org/doc/effective_go). 3. Make sure your code respects all [linting rules](./.golangci.yml) using [golangci-lint](https://golangci-lint.run/) - version 1.52.2. + version 1.57.2. 4. Write clear commit messages and include documentation if necessary. 5. Make sure your code passes the existing tests. 6. Open a pull request, providing a clear description of your changes. diff --git a/LICENSE b/LICENSE index ffea26c..b59654c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2023 KardinalAI +Copyright (c) 2021 KardinalAI Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 5f3bf0a..275038d 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ This wrapper depends on the official [Go RabbitMQ plugin](https://github.com/rab ### Go module ```bash -go get github.com/KardinalAI/gorabbit/v1 +go get github.com/KardinalAI/gorabbit ``` ### Environment variables diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..55e29c9 --- /dev/null +++ b/TODO.md @@ -0,0 +1,19 @@ +# TODO + +Must have or Nice to have features. + +## Must Haves + +- [ ] Unregister consumer via a `UnregisterConsumer` +- [ ] Send messages with a TTL +- [ ] Send messages with a definable header + +## Nice to have + +- [ ] Example projects for consumers and producers + +## To Revisit/Rethink + +- [ ] Review the `publishingCache` in `channel.go` +- [ ] Review the logger +- [ ] Review the linter and redefine some rules if they are too strict diff --git a/channel.go b/channel.go index 3f92bb8..8cb4d8b 100644 --- a/channel.go +++ b/channel.go @@ -87,7 +87,14 @@ type amqpChannel struct { // - consumer is the MessageConsumer that will hold consumption information. // - maxRetry is the retry header for each message. // - logger is the parent logger. -func newConsumerChannel(ctx context.Context, connection *amqp.Connection, keepAlive bool, retryDelay time.Duration, consumer *MessageConsumer, logger logger) *amqpChannel { +func newConsumerChannel( + ctx context.Context, + connection *amqp.Connection, + keepAlive bool, + retryDelay time.Duration, + consumer *MessageConsumer, + logger logger, +) *amqpChannel { channel := &amqpChannel{ ctx: ctx, connection: connection, @@ -134,7 +141,16 @@ func newConsumerChannel(ctx context.Context, connection *amqp.Connection, keepAl // - publishingCacheSize is the maximum cache size of failed publishing. // - publishingCacheTTL defines the time to live for each failed publishing that was put in cache. // - logger is the parent logger. -func newPublishingChannel(ctx context.Context, connection *amqp.Connection, keepAlive bool, retryDelay time.Duration, maxRetry uint, publishingCacheSize uint64, publishingCacheTTL time.Duration, logger logger) *amqpChannel { +func newPublishingChannel( + ctx context.Context, + connection *amqp.Connection, + keepAlive bool, + retryDelay time.Duration, + maxRetry uint, + publishingCacheSize uint64, + publishingCacheTTL time.Duration, + logger logger, +) *amqpChannel { channel := &amqpChannel{ ctx: ctx, connection: connection, @@ -450,6 +466,8 @@ func (c *amqpChannel) processDelivery(delivery *amqp.Delivery) { } // retryDelivery processes a delivery retry based on its redelivery header. +// +//nolint:gocognit // We can allow the current complexity for now but we should revisit it later. func (c *amqpChannel) retryDelivery(delivery *amqp.Delivery, alreadyAcknowledged bool) { c.logger.Debug("Delivery retry launched") @@ -534,7 +552,7 @@ func (c *amqpChannel) retryDelivery(delivery *amqp.Delivery, alreadyAcknowledged } // publish will publish a message with the given configuration. -func (c *amqpChannel) publish(exchange string, routingKey string, payload []byte, options *publishingOptions) error { +func (c *amqpChannel) publish(exchange string, routingKey string, payload []byte, options *PublishingOptions) error { publishing := &amqp.Publishing{ ContentType: "application/json", Body: payload, @@ -585,7 +603,11 @@ func (c *amqpChannel) publish(exchange string, routingKey string, payload []byte // If the exchange does not exist yet, we want to force a release log with a warning for better visibility. if isErrorNotFound(err) { - c.releaseLogger.Warn("The MQTT message was not sent, exchange does not exist", logField{Key: "exchange", Value: exchange}, logField{Key: "routingKey", Value: routingKey}) + c.releaseLogger.Warn( + "The MQTT message was not sent, exchange does not exist", + logField{Key: "exchange", Value: exchange}, + logField{Key: "routingKey", Value: routingKey}, + ) } return err diff --git a/client.go b/client.go index e31e5e9..2bbc810 100644 --- a/client.go +++ b/client.go @@ -29,7 +29,7 @@ type MQTTClient interface { // - payload is the object you want to send as a byte array. // Optionally you can add publishingOptions for extra customization. // Returns an error if the connection to the RabbitMQ server is down. - PublishWithOptions(exchange, routingKey string, payload interface{}, options *publishingOptions) error + PublishWithOptions(exchange, routingKey string, payload interface{}, options *PublishingOptions) error // RegisterConsumer will register a MessageConsumer for internal queue subscription and message processing. // The MessageConsumer will hold a list of MQTTMessageHandlers to internalize message processing. @@ -171,7 +171,7 @@ func (client *mqttClient) Publish(exchange string, routingKey string, payload in return client.PublishWithOptions(exchange, routingKey, payload, nil) } -func (client *mqttClient) PublishWithOptions(exchange string, routingKey string, payload interface{}, options *publishingOptions) error { +func (client *mqttClient) PublishWithOptions(exchange string, routingKey string, payload interface{}, options *PublishingOptions) error { // client is disabled, so we do nothing and return no error. if client.disabled { return nil diff --git a/connection.go b/connection.go index c90b662..429abd0 100644 --- a/connection.go +++ b/connection.go @@ -66,7 +66,16 @@ func newConsumerConnection(ctx context.Context, uri string, keepAlive bool, retr // - publishingCacheSize defines the maximum length of failed publishing cache. // - publishingCacheTTL defines the time to live for failed publishing in cache. // - logger is the parent logger. -func newPublishingConnection(ctx context.Context, uri string, keepAlive bool, retryDelay time.Duration, maxRetry uint, publishingCacheSize uint64, publishingCacheTTL time.Duration, logger logger) *amqpConnection { +func newPublishingConnection( + ctx context.Context, + uri string, + keepAlive bool, + retryDelay time.Duration, + maxRetry uint, + publishingCacheSize uint64, + publishingCacheTTL time.Duration, + logger logger, +) *amqpConnection { conn := newConnection(ctx, uri, keepAlive, retryDelay, logger, connectionTypePublisher) conn.maxRetry = maxRetry @@ -278,7 +287,7 @@ func (a *amqpConnection) registerConsumer(consumer MessageConsumer) error { return nil } -func (a *amqpConnection) publish(exchange, routingKey string, payload []byte, options *publishingOptions) error { +func (a *amqpConnection) publish(exchange, routingKey string, payload []byte, options *PublishingOptions) error { publishingChannel := a.channels.publishingChannel() if publishingChannel == nil { publishingChannel = newPublishingChannel(a.ctx, a.connection, a.keepAlive, a.retryDelay, a.maxRetry, a.publishingCacheSize, a.publishingCacheTTL, a.logger) diff --git a/connection_manager.go b/connection_manager.go index 9d02c28..be7f783 100644 --- a/connection_manager.go +++ b/connection_manager.go @@ -69,7 +69,7 @@ func (c *connectionManager) registerConsumer(consumer MessageConsumer) error { return c.consumerConnection.registerConsumer(consumer) } -func (c *connectionManager) publish(exchange, routingKey string, payload interface{}, options *publishingOptions) error { +func (c *connectionManager) publish(exchange, routingKey string, payload interface{}, options *PublishingOptions) error { if c.publisherConnection == nil { return errPublisherConnectionNotInitialized } diff --git a/consumer.go b/consumer.go index bae9030..4538214 100644 --- a/consumer.go +++ b/consumer.go @@ -13,6 +13,8 @@ type MQTTMessageHandlers map[string]MQTTMessageHandlerFunc type MQTTMessageHandlerFunc func(payload []byte) error // Validate verifies that all routing keys in the handlers are properly formatted and allowed. +// +//nolint:gocognit // We can allow the current complexity for now but we should revisit it later. func (mh MQTTMessageHandlers) Validate() error { for k := range mh { // A routing key cannot be empty. diff --git a/consumer_test.go b/consumer_test.go index 106bdff..e694d5c 100644 --- a/consumer_test.go +++ b/consumer_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/KardinalAI/gorabbit/v1" + "github.com/KardinalAI/gorabbit" ) func TestMQTTMessageHandlers_Validate(t *testing.T) { @@ -16,59 +16,59 @@ func TestMQTTMessageHandlers_Validate(t *testing.T) { }{ { handlers: gorabbit.MQTTMessageHandlers{ - "event.user.#": func(payload []byte) error { return nil }, - "event.email.*.generated": func(payload []byte) error { return nil }, - "event.*.space.boom": func(payload []byte) error { return nil }, - "*.toto.order.passed": func(payload []byte) error { return nil }, - "#.toto": func(payload []byte) error { return nil }, + "event.user.#": func(_ []byte) error { return nil }, + "event.email.*.generated": func(_ []byte) error { return nil }, + "event.*.space.boom": func(_ []byte) error { return nil }, + "*.toto.order.passed": func(_ []byte) error { return nil }, + "#.toto": func(_ []byte) error { return nil }, }, expectedError: nil, }, { handlers: gorabbit.MQTTMessageHandlers{ - "": func(payload []byte) error { return nil }, + "": func(_ []byte) error { return nil }, }, expectedError: errors.New("a routing key cannot be empty"), }, { handlers: gorabbit.MQTTMessageHandlers{ - " ": func(payload []byte) error { return nil }, + " ": func(_ []byte) error { return nil }, }, expectedError: errors.New("a routing key cannot contain spaces"), }, { handlers: gorabbit.MQTTMessageHandlers{ - "#": func(payload []byte) error { return nil }, + "#": func(_ []byte) error { return nil }, }, expectedError: errors.New("a routing key cannot be the wildcard '#'"), }, { handlers: gorabbit.MQTTMessageHandlers{ - "toto.#.titi": func(payload []byte) error { return nil }, + "toto.#.titi": func(_ []byte) error { return nil }, }, expectedError: errors.New("the wildcard '#' in the routing key 'toto.#.titi' is not allowed"), }, { handlers: gorabbit.MQTTMessageHandlers{ - "toto titi": func(payload []byte) error { return nil }, + "toto titi": func(_ []byte) error { return nil }, }, expectedError: errors.New("a routing key cannot contain spaces"), }, { handlers: gorabbit.MQTTMessageHandlers{ - "toto..titi": func(payload []byte) error { return nil }, + "toto..titi": func(_ []byte) error { return nil }, }, expectedError: errors.New("the routing key 'toto..titi' is not properly formatted"), }, { handlers: gorabbit.MQTTMessageHandlers{ - ".toto.titi": func(payload []byte) error { return nil }, + ".toto.titi": func(_ []byte) error { return nil }, }, expectedError: errors.New("the routing key '.toto.titi' is not properly formatted"), }, { handlers: gorabbit.MQTTMessageHandlers{ - "toto.titi.": func(payload []byte) error { return nil }, + "toto.titi.": func(_ []byte) error { return nil }, }, expectedError: errors.New("the routing key 'toto.titi.' is not properly formatted"), }, @@ -83,11 +83,11 @@ func TestMQTTMessageHandlers_Validate(t *testing.T) { func TestMQTTMessageHandlers_FindFunc(t *testing.T) { handlers := gorabbit.MQTTMessageHandlers{ - "event.user.#": func(payload []byte) error { return nil }, - "event.email.*.generated": func(payload []byte) error { return nil }, - "event.*.space.boom": func(payload []byte) error { return nil }, - "*.toto.order.passed": func(payload []byte) error { return nil }, - "#.toto": func(payload []byte) error { return nil }, + "event.user.#": func(_ []byte) error { return nil }, + "event.email.*.generated": func(_ []byte) error { return nil }, + "event.*.space.boom": func(_ []byte) error { return nil }, + "*.toto.order.passed": func(_ []byte) error { return nil }, + "#.toto": func(_ []byte) error { return nil }, } tests := []struct { diff --git a/go.mod b/go.mod index 161ad27..2f18d64 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,18 @@ -module github.com/KardinalAI/gorabbit/v1 +module github.com/KardinalAI/gorabbit -go 1.20 +go 1.21 require ( github.com/Netflix/go-env v0.0.0-20220526054621-78278af1949d - github.com/google/uuid v1.4.0 + github.com/google/uuid v1.6.0 github.com/rabbitmq/amqp091-go v1.9.0 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.19.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index dcf62bb..ae8709f 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/Netflix/go-env v0.0.0-20220526054621-78278af1949d/go.mod h1:9XMFaCeRy github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -21,13 +21,13 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/model.go b/model.go index c60a9ef..09c5394 100644 --- a/model.go +++ b/model.go @@ -54,39 +54,39 @@ type BindingConfig struct { Exchange string `yaml:"exchange"` } -type publishingOptions struct { - messagePriority *MessagePriority - deliveryMode *DeliveryMode +type PublishingOptions struct { + MessagePriority *MessagePriority + DeliveryMode *DeliveryMode } -func SendOptions() *publishingOptions { - return &publishingOptions{} +func SendOptions() *PublishingOptions { + return &PublishingOptions{} } -func (m *publishingOptions) priority() uint8 { - if m.messagePriority == nil { +func (m *PublishingOptions) priority() uint8 { + if m.MessagePriority == nil { return PriorityMedium.Uint8() } - return m.messagePriority.Uint8() + return m.MessagePriority.Uint8() } -func (m *publishingOptions) mode() uint8 { - if m.deliveryMode == nil { +func (m *PublishingOptions) mode() uint8 { + if m.DeliveryMode == nil { return Persistent.Uint8() } - return m.deliveryMode.Uint8() + return m.DeliveryMode.Uint8() } -func (m *publishingOptions) SetPriority(priority MessagePriority) *publishingOptions { - m.messagePriority = &priority +func (m *PublishingOptions) SetPriority(priority MessagePriority) *PublishingOptions { + m.MessagePriority = &priority return m } -func (m *publishingOptions) SetMode(mode DeliveryMode) *publishingOptions { - m.deliveryMode = &mode +func (m *PublishingOptions) SetMode(mode DeliveryMode) *PublishingOptions { + m.DeliveryMode = &mode return m }