diff --git a/.golangci.yml b/.golangci.yml index 1edf4f94bac37270cec312690c495eea3ad096af_LmdvbGFuZ2NpLnltbA==..df5916ed676ad2d51e4e2a706165e34e2c66521a_LmdvbGFuZ2NpLnltbA== 100644 --- 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 index 1edf4f94bac37270cec312690c495eea3ad096af_TWFrZWZpbGU=..df5916ed676ad2d51e4e2a706165e34e2c66521a_TWFrZWZpbGU= 100644 --- 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 index 1edf4f94bac37270cec312690c495eea3ad096af_ZGF0YWJhc2UvYXJyYXlfY29udGFpbnMuZ28=..df5916ed676ad2d51e4e2a706165e34e2c66521a_ZGF0YWJhc2UvYXJyYXlfY29udGFpbnMuZ28= 100644 --- a/database/array_contains.go +++ b/database/array_contains.go @@ -10,8 +10,8 @@ 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)" @@ -15,7 +15,8 @@ if len(ac) == 0 { // Empty Sql{} evaluates to true. sql = "(1=1)" + return } sortedKeys := getSortedKeys(ac) @@ -18,8 +19,9 @@ return } sortedKeys := getSortedKeys(ac) - var exprs []string + + exprs := make([]string, 0, len(sortedKeys)) for _, key := range sortedKeys { var expr string @@ -49,7 +51,4 @@ } if val == nil { panic("cannot handle NULL values") - } else { - expr = key + " @> ?" - args = append(args, val) } @@ -55,5 +54,7 @@ } + expr = key + " @> ?" + args = append(args, val) exprs = append(exprs, expr) } sql = strings.Join(exprs, " AND ") @@ -56,7 +57,8 @@ exprs = append(exprs, expr) } sql = strings.Join(exprs, " AND ") + return } diff --git a/database/migrations.go b/database/migrations.go index 1edf4f94bac37270cec312690c495eea3ad096af_ZGF0YWJhc2UvbWlncmF0aW9ucy5nbw==..df5916ed676ad2d51e4e2a706165e34e2c66521a_ZGF0YWJhc2UvbWlncmF0aW9ucy5nbw== 100644 --- 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 index 1edf4f94bac37270cec312690c495eea3ad096af_ZGF0YWJhc2Uvc3FsLmdv..df5916ed676ad2d51e4e2a706165e34e2c66521a_ZGF0YWJhc2Uvc3FsLmdv 100644 --- a/database/sql.go +++ b/database/sql.go @@ -30,6 +30,7 @@ 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 { @@ -33,7 +34,7 @@ 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), }) @@ -37,6 +38,9 @@ "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 index 1edf4f94bac37270cec312690c495eea3ad096af_Z2VuZXJhdGUtY29uZmlnLmdv..df5916ed676ad2d51e4e2a706165e34e2c66521a_Z2VuZXJhdGUtY29uZmlnLmdv 100644 --- 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 index 1edf4f94bac37270cec312690c495eea3ad096af_bG9nLWNvbnNvbGUtd3JpdGVyLmdv..df5916ed676ad2d51e4e2a706165e34e2c66521a_bG9nLWNvbnNvbGUtd3JpdGVyLmdv 100644 --- a/log-console-writer.go +++ b/log-console-writer.go @@ -10,6 +10,7 @@ import ( "bytes" "encoding/json" + "errors" "fmt" "io" "os" @@ -97,8 +98,8 @@ } // 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() } @@ -101,8 +102,11 @@ 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,5 +366,5 @@ var l string if ll, ok := i.(string); ok { switch ll { - case "trace": + case zerolog.LevelTraceValue: l = colorize("TRC", colorMagenta, noColor) @@ -366,3 +370,3 @@ l = colorize("TRC", colorMagenta, noColor) - case "debug": + case zerolog.LevelDebugValue: l = colorize("DBG", colorYellow, noColor) @@ -368,3 +372,3 @@ l = colorize("DBG", colorYellow, noColor) - case "info": + case zerolog.LevelInfoValue: l = colorize("INF", colorGreen, noColor) @@ -370,3 +374,3 @@ l = colorize("INF", colorGreen, noColor) - case "warn": + case zerolog.LevelWarnValue: l = colorize("WRN", colorRed, noColor) @@ -372,3 +376,3 @@ l = colorize("WRN", colorRed, noColor) - case "error": + case zerolog.LevelErrorValue: l = colorize(colorize("ERR", colorRed, noColor), colorBold, noColor) @@ -374,3 +378,3 @@ l = colorize(colorize("ERR", colorRed, noColor), colorBold, noColor) - case "fatal": + case zerolog.LevelFatalValue: l = colorize(colorize("FTL", colorRed, noColor), colorBold, noColor) @@ -376,5 +380,5 @@ 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 index 1edf4f94bac37270cec312690c495eea3ad096af_bG9nZ2luZy5nbw==..df5916ed676ad2d51e4e2a706165e34e2c66521a_bG9nZ2luZy5nbw== 100644 --- 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,6 +122,7 @@ // 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 { @@ -126,5 +128,5 @@ } else { - value = "warn" + value = zerolog.LevelWarnValue } } diff --git a/logging_test.go b/logging_test.go index 1edf4f94bac37270cec312690c495eea3ad096af_bG9nZ2luZ190ZXN0Lmdv..df5916ed676ad2d51e4e2a706165e34e2c66521a_bG9nZ2luZ190ZXN0Lmdv 100644 --- a/logging_test.go +++ b/logging_test.go @@ -31,6 +31,6 @@ 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()) @@ -35,6 +35,6 @@ 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 index 1edf4f94bac37270cec312690c495eea3ad096af_cGFuaWNfbWlkZGxld2FyZS5nbw==..df5916ed676ad2d51e4e2a706165e34e2c66521a_cGFuaWNfbWlkZGxld2FyZS5nbw== 100644 --- a/panic_middleware.go +++ b/panic_middleware.go @@ -1,6 +1,7 @@ package orusapi import ( + "context" "fmt" "net/http" "runtime/debug" @@ -13,5 +14,5 @@ // 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 { @@ -17,5 +18,5 @@ 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 index 1edf4f94bac37270cec312690c495eea3ad096af_cmVkb2MuZ28=..df5916ed676ad2d51e4e2a706165e34e2c66521a_cmVkb2MuZ28= 100644 --- 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 index 1edf4f94bac37270cec312690c495eea3ad096af_c2NyaXB0cy9nZW5lcmF0ZV9kYl9oZWxwZXJzLmdv..df5916ed676ad2d51e4e2a706165e34e2c66521a_c2NyaXB0cy9nZW5lcmF0ZV9kYl9oZWxwZXJzLmdv 100644 --- 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 index 1edf4f94bac37270cec312690c495eea3ad096af_c2VudHJ5Lmdv..df5916ed676ad2d51e4e2a706165e34e2c66521a_c2VudHJ5Lmdv 100644 --- a/sentry.go +++ b/sentry.go @@ -2,8 +2,9 @@ import ( "bytes" + "errors" "fmt" "io" "github.com/getsentry/sentry-go" jsoniter "github.com/json-iterator/go" @@ -5,7 +6,8 @@ "fmt" "io" "github.com/getsentry/sentry-go" jsoniter "github.com/json-iterator/go" + "github.com/rs/zerolog" ) @@ -10,5 +12,7 @@ ) +var ErrNotAString = errors.New("not a string") + // SentryOptions ... type SentryOptions struct { SentryDSN func(string) `long:"dsn" env:"DSN" ini-name:"dsn" description:"Sentry DSN"` @@ -12,7 +16,7 @@ // 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 index 1edf4f94bac37270cec312690c495eea3ad096af_c2VydmVyLmdv..df5916ed676ad2d51e4e2a706165e34e2c66521a_c2VydmVyLmdv 100644 --- 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 index 1edf4f94bac37270cec312690c495eea3ad096af_dmVyc2lvbi5nbw==..df5916ed676ad2d51e4e2a706165e34e2c66521a_dmVyc2lvbi5nbw== 100644 --- 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 {