# HG changeset patch
# User Axel Prel <axel.prel@xcg-consulting.fr>
# Date 1718790054 -7200
#      Wed Jun 19 11:40:54 2024 +0200
# Node ID df5916ed676ad2d51e4e2a706165e34e2c66521a
# Parent  1edf4f94bac37270cec312690c495eea3ad096af
lint the not-so-obvious

diff --git a/.golangci.yml b/.golangci.yml
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -150,5 +150,8 @@
     - "var-naming: don't use an underscore in package name"
     - "ST1003: should not use underscores in package names"
   exclude-rules:
+    - path: 'server.go'
+      linters:
+        - lll
   exclude-dirs:
   exclude-files:
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -23,3 +23,6 @@
 .PHONY: lint-fix
 lint-fix: $(GOLANGCI_LINT_BIN) ## Lint & fix the files
 	$(GOLANGCI_LINT_BIN) run --fix $(GOLANGCI_LINT_ARGS)
+
+test:
+	go test ./...
diff --git a/database/array_contains.go b/database/array_contains.go
--- a/database/array_contains.go
+++ b/database/array_contains.go
@@ -10,16 +10,18 @@
 
 type ArrayContains map[string]interface{}
 
-//nolint:revive
-func (ac ArrayContains) ToSql() (sql string, args []interface{}, err error) { //nolint:nonamedreturns
+//nolint:nakedret
+func (ac ArrayContains) ToSql() (sql string, args []interface{}, err error) { //nolint:nonamedreturns,stylecheck,revive
 	if len(ac) == 0 {
 		// Empty Sql{} evaluates to true.
 		sql = "(1=1)"
+
 		return
 	}
 
 	sortedKeys := getSortedKeys(ac)
-	var exprs []string
+
+	exprs := make([]string, 0, len(sortedKeys))
 
 	for _, key := range sortedKeys {
 		var expr string
@@ -49,14 +51,14 @@
 		}
 		if val == nil {
 			panic("cannot handle NULL values")
-		} else {
-			expr = key + " @> ?"
-			args = append(args, val)
 		}
+		expr = key + " @> ?"
+		args = append(args, val)
 		exprs = append(exprs, expr)
 	}
 
 	sql = strings.Join(exprs, " AND ")
+
 	return
 }
 
diff --git a/database/migrations.go b/database/migrations.go
--- a/database/migrations.go
+++ b/database/migrations.go
@@ -82,7 +82,8 @@
 	if err == nil {
 		return nil
 	}
-	if _, ok := err.(ErrDBNeedUpgrade); err != ErrDBNotVersioned == !ok {
+	var upgradeErr ErrDBNeedUpgrade
+	if ok := errors.As(err, &upgradeErr); !errors.Is(err, ErrDBNotVersioned) == !ok {
 		return err
 	}
 	log.Warn().Msg("Database is not up-to-date, it will be migrated automatically")
@@ -114,7 +115,8 @@
 	}
 	for {
 		next, err := sourceDriver.Next(lastVersion)
-		if pathError, ok := err.(*os.PathError); err == os.ErrNotExist || ok && pathError.Err == os.ErrNotExist {
+		var pathErr *os.PathError
+		if errors.As(err, &pathErr) && errors.Is(err, os.ErrNotExist) {
 			break
 		} else if err != nil {
 			return err
diff --git a/database/sql.go b/database/sql.go
--- a/database/sql.go
+++ b/database/sql.go
@@ -30,13 +30,17 @@
 		for _, arg := range args {
 			b, err := json.Marshal(arg)
 			if err != nil {
+				log.Err(err).Msg("error Marshaling arguments")
 				arr.Interface(arg)
 			} else {
 				if len(b) > 512 {
-					b, _ = json.Marshal(map[string]interface{}{
+					b, err = json.Marshal(map[string]interface{}{
 						"arg first 512 bytes": b[:512],
 						"len":                 len(b),
 					})
+					if err != nil {
+						log.Err(err).Msg("error Marshaling first 512 bytes of arguments")
+					}
 				}
 				arr.RawJSON(b)
 			}
@@ -56,7 +60,12 @@
 }
 
 func setPlaceHolderFormat(query squirrel.Sqlizer) squirrel.Sqlizer {
-	return builder.Set(query, "PlaceholderFormat", squirrel.Dollar).(squirrel.Sqlizer)
+	sqlizer, ok := builder.Set(query, "PlaceholderFormat", squirrel.Dollar).(squirrel.Sqlizer)
+	if !ok {
+		panic("could not assert sqlizer from builder")
+	}
+
+	return sqlizer
 }
 
 // Get loads an object.
diff --git a/generate-config.go b/generate-config.go
--- a/generate-config.go
+++ b/generate-config.go
@@ -36,5 +36,6 @@
 	}
 	fp := flags.NewIniParser(c.parser)
 	fp.Write(out, flags.IniIncludeDefaults|flags.IniCommentDefaults|flags.IniDefault)
+
 	return nil
 }
diff --git a/log-console-writer.go b/log-console-writer.go
--- a/log-console-writer.go
+++ b/log-console-writer.go
@@ -10,6 +10,7 @@
 import (
 	"bytes"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"os"
@@ -97,12 +98,15 @@
 }
 
 // Write transforms the JSON input with formatters and appends to w.Out.
-func (w ConsoleWriter) Write(p []byte) (n int, err error) {
+func (w ConsoleWriter) Write(p []byte) (n int, err error) { //nolint:nonamedreturns
 	if w.PartsOrder == nil {
 		w.PartsOrder = consoleDefaultPartsOrder()
 	}
 
-	buf := consoleBufPool.Get().(*bytes.Buffer)
+	buf, ok := consoleBufPool.Get().(*bytes.Buffer)
+	if !ok {
+		return n, errors.New("cannot assert bytes buffer from console pool")
+	}
 	defer func() {
 		buf.Reset()
 		consoleBufPool.Put(buf)
@@ -362,19 +366,19 @@
 		var l string
 		if ll, ok := i.(string); ok {
 			switch ll {
-			case "trace":
+			case zerolog.LevelTraceValue:
 				l = colorize("TRC", colorMagenta, noColor)
-			case "debug":
+			case zerolog.LevelDebugValue:
 				l = colorize("DBG", colorYellow, noColor)
-			case "info":
+			case zerolog.LevelInfoValue:
 				l = colorize("INF", colorGreen, noColor)
-			case "warn":
+			case zerolog.LevelWarnValue:
 				l = colorize("WRN", colorRed, noColor)
-			case "error":
+			case zerolog.LevelErrorValue:
 				l = colorize(colorize("ERR", colorRed, noColor), colorBold, noColor)
-			case "fatal":
+			case zerolog.LevelFatalValue:
 				l = colorize(colorize("FTL", colorRed, noColor), colorBold, noColor)
-			case "panic":
+			case zerolog.LevelPanicValue:
 				l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor)
 			default:
 				l = colorize("???", colorBold, noColor)
diff --git a/logging.go b/logging.go
--- a/logging.go
+++ b/logging.go
@@ -40,8 +40,8 @@
 
 // LoggingOptions holds the logging options.
 type LoggingOptions struct {
-	Level   func(string) error `long:"level" env:"LEVEL" ini-name:"log-level" choice:"trace" choice:"debug" choice:"info" choice:"warn" choice:"error" choice:"fatal" choice:"panic" choice:"auto" default:"auto" description:"log level. 'auto' selects 'info' when stdout is a tty, 'error' otherwise."`
-	Format  func(string) error `long:"format" env:"FORMAT" ini-name:"log-format" choice:"json" choice:"pretty" choice:"auto" default:"auto" description:"Logs format. 'auto' selects 'pretty' if stdout is a tty."`
+	Level   func(string) error `long:"level" env:"LEVEL" ini-name:"log-level" choice:"trace" choice:"debug" choice:"info" choice:"warn" choice:"error" choice:"fatal" choice:"panic" choice:"auto" default:"auto" description:"log level. 'auto' selects 'info' when stdout is a tty, 'error' otherwise."` //nolint:lll
+	Format  func(string) error `long:"format" env:"FORMAT" ini-name:"log-format" choice:"json" choice:"pretty" choice:"auto" default:"auto" description:"Logs format. 'auto' selects 'pretty' if stdout is a tty."`                                                                                        //nolint:lll
 	Verbose func()             `short:"v" long:"verbose" no-ini:"t" description:"Increase log verbosity. Can be repeated"`
 
 	logFinalOutput io.Writer                   `no-flag:"t"`
@@ -93,7 +93,8 @@
 
 	o.Format = func(format string) error {
 		if format == "auto" {
-			if outputFile, hasFd := o.logFinalOutput.(interface{ Fd() uintptr }); hasFd && term.IsTerminal(int(outputFile.Fd())) {
+			if outputFile, hasFd := o.logFinalOutput.(interface{ Fd() uintptr }); hasFd &&
+				term.IsTerminal(int(outputFile.Fd())) {
 				format = "pretty"
 			} else {
 				format = "json"
@@ -121,10 +122,11 @@
 				// default after some potential --verbose that would be ignored
 				return nil
 			}
-			if outputFile, hasFd := o.logFinalOutput.(interface{ Fd() uintptr }); hasFd && term.IsTerminal(int(outputFile.Fd())) {
-				value = "info"
+			if outputFile, hasFd := o.logFinalOutput.(interface{ Fd() uintptr }); hasFd &&
+				term.IsTerminal(int(outputFile.Fd())) {
+				value = zerolog.LevelInfoValue
 			} else {
-				value = "warn"
+				value = zerolog.LevelWarnValue
 			}
 		}
 
diff --git a/logging_test.go b/logging_test.go
--- a/logging_test.go
+++ b/logging_test.go
@@ -31,10 +31,10 @@
 	o.Verbose()
 	assert.Equal(t, zerolog.TraceLevel, o.Logger().GetLevel())
 
-	require.NoError(t, o.Level("fatal"))
+	require.NoError(t, o.Level(zerolog.LevelFatalValue))
 	assert.Equal(t, zerolog.FatalLevel, o.Logger().GetLevel())
 
-	require.NoError(t, o.Level("info"))
+	require.NoError(t, o.Level(zerolog.LevelInfoValue))
 	assert.Equal(t, zerolog.InfoLevel, o.Logger().GetLevel())
 
 	require.NoError(t, o.Format("pretty"))
diff --git a/panic_middleware.go b/panic_middleware.go
--- a/panic_middleware.go
+++ b/panic_middleware.go
@@ -1,6 +1,7 @@
 package orusapi
 
 import (
+	"context"
 	"fmt"
 	"net/http"
 	"runtime/debug"
@@ -13,9 +14,9 @@
 // CatchPanics catches the panics and log them as errors.
 func CatchPanics(next http.Handler) http.Handler {
 	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
-		defer func() {
+		defer func(ctx context.Context) {
 			if r := recover(); r != nil {
-				log := zerolog.Ctx(req.Context())
+				log := zerolog.Ctx(ctx)
 
 				exc := zerolog.Dict().
 					Str("type", fmt.Sprintf("%T", r))
@@ -39,7 +40,7 @@
 
 				rw.WriteHeader(http.StatusInternalServerError)
 			}
-		}()
+		}(req.Context())
 		next.ServeHTTP(rw, req)
 	})
 }
diff --git a/redoc.go b/redoc.go
--- a/redoc.go
+++ b/redoc.go
@@ -16,7 +16,8 @@
 	Path string
 	// SpecURL the url to find the spec for
 	SpecURL string
-	// RedocURL for the js that generates the redoc site, defaults to: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js
+	// RedocURL for the js that generates the redoc site.
+	// defaults to: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js
 	RedocURL string
 	// Title for the documentation site, default to: API documentation
 	Title string
diff --git a/scripts/generate_db_helpers.go b/scripts/generate_db_helpers.go
--- a/scripts/generate_db_helpers.go
+++ b/scripts/generate_db_helpers.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"errors"
 	"fmt"
 	"go/ast"
 	"go/parser"
@@ -14,6 +15,8 @@
 	"github.com/fatih/structtag"
 )
 
+var ErrNoDBFields = errors.New("no db fields")
+
 // Package is the top-level package.
 type Package struct {
 	Name string
@@ -171,6 +174,10 @@
 			dbtag := findStructDocLineWithPrefix(doc, "dbtable:")
 			dbstruct, err := newDBStruct(topLevel, name, dbtag, structType)
 			if err != nil {
+				if errors.Is(err, ErrNoDBFields) {
+					return nil
+				}
+
 				return err
 			}
 			if dbstruct != nil {
@@ -223,7 +230,7 @@
 	}
 
 	if len(dbstruct.Fields) == 0 {
-		return nil, nil
+		return nil, ErrNoDBFields
 	}
 
 	for i, field := range dbstruct.Fields {
diff --git a/sentry.go b/sentry.go
--- a/sentry.go
+++ b/sentry.go
@@ -2,17 +2,21 @@
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"io"
 
 	"github.com/getsentry/sentry-go"
 	jsoniter "github.com/json-iterator/go"
+	"github.com/rs/zerolog"
 )
 
+var ErrNotAString = errors.New("not a string")
+
 // SentryOptions ...
 type SentryOptions struct {
 	SentryDSN func(string) `long:"dsn" env:"DSN" ini-name:"dsn" description:"Sentry DSN"`
-	TagNames  []string     `long:"tag-names" env:"TAG_NAMES" ini-name:"tag-names" description:"log properties that should be set as tags on the sentry event"`
+	TagNames  []string     `long:"tag-names" env:"TAG_NAMES" ini-name:"tag-names" description:"log properties that should be set as tags on the sentry event"` //nolint:lll
 }
 
 // NewSentryOptions ...
@@ -87,7 +91,7 @@
 			event.Extra["error"] = errMsg
 		case "level":
 			level := iter.ReadString()
-			if level == "warn" {
+			if level == zerolog.LevelWarnValue {
 				event.Level = sentry.LevelWarning
 			} else {
 				event.Level = sentry.Level(level)
@@ -137,8 +141,11 @@
 		return nil
 	}
 	if op, ok := event.Extra["api-operation-id"]; ok {
-		tag, _ := event.Extra["api-tag"].(string)
-		event.Message = tag + "/" + op.(string) + ": " + event.Message
+		if strOp, ok := op.(string); ok {
+			event.Message = MustExtraGetStringD(event.Extra, "api-tag", "") + "/" + strOp + ": " + event.Message
+		} else {
+			panic("operation is not a string")
+		}
 	} else if event.Request != nil && event.Request.Method != "" {
 		event.Message = event.Request.Method + " " + event.Request.URL + ": " + event.Message
 	}
@@ -146,6 +153,27 @@
 	return event
 }
 
+func MustExtraGetStringD(extra map[string]interface{}, key, defaultVal string) string {
+	str, err := ExtraGetStringD(extra, key, defaultVal)
+	if err != nil {
+		panic(err)
+	}
+
+	return str
+}
+
+func ExtraGetStringD(extra map[string]interface{}, key, defaultVal string) (string, error) {
+	if value, ok := extra[key]; ok {
+		if strValue, ok := value.(string); ok {
+			return strValue, nil
+		}
+
+		return "", fmt.Errorf("error %w: value %#v", ErrNotAString, value)
+	}
+
+	return defaultVal, nil
+}
+
 // Write ...
 func (l SentryLogger) Write(p []byte) (int, error) {
 	if event := buildEventIfLevelGtWarn(l.options.TagNames, p); event != nil {
diff --git a/server.go b/server.go
--- a/server.go
+++ b/server.go
@@ -145,7 +145,7 @@
 }
 
 // Serve the api.
-func (s *Server) Serve() (err error) {
+func (s *Server) Serve() (err error) { //nolint:nonamedreturns
 	if !s.hasListeners {
 		if err = s.Listen(); err != nil {
 			return err
diff --git a/version.go b/version.go
--- a/version.go
+++ b/version.go
@@ -32,8 +32,8 @@
 
 type swaggerVersion struct {
 	Info struct {
-		Version string
-	}
+		Version string `json:"version"`
+	} `json:"info"`
 }
 
 func GetVersion(swaggerJSON json.RawMessage) string {