diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 652b65d..a92adf7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,21 +9,6 @@ on: pull_request: jobs: - check-modules: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Install Go(lang) - uses: actions/setup-go@v5 - with: - go-version: 1.21.x - - name: Check module dependencies - run: | - go version - test -z "$(go mod tidy && git status go.* --porcelain)" - go mod verify - run-unit-tests: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 0000000..b7f4360 --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,42 @@ +name: Lint + +on: + push: + pull_request: + +jobs: + check-modules: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Go(lang) + uses: actions/setup-go@v5 + with: + go-version: 1.21.x + - name: Check module dependencies + run: | + go version + test -z "$(go mod tidy && git status go.* --porcelain)" + go mod verify + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: 1.21.x + - name: Retrieve golangci-lint version + run: | + echo "Version=$(head -n 1 "${GITHUB_WORKSPACE}/.golangci.yml" | tr -d '# ')" >> $GITHUB_OUTPUT + id: version + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: ${{ steps.version.outputs.Version }} + only-new-issues: true diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..b51eb56 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,95 @@ +# v1.55.2 +# Please don't remove the first line. It is used in CI to determine the golangci-lint version. +run: + timeout: 5m + +issues: + exclude-rules: + - path: _(test|gen)\.go + linters: + - cyclop + - dupl + - gocognit + - funlen + - lll + - path: (cmd|env|migrations)\/ + linters: + - gochecknoglobals + +linters-settings: + govet: + check-shadowing: true + funlen: + lines: 80 + statements: 60 + forbidigo: + forbid: + - '^(fmt\\.Print(|f|ln)|print|println)$' + +linters: + disable-all: true + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - contextcheck + - cyclop + - dogsled + - dupl + - durationcheck + - errcheck + - errchkjson + - errname + - errorlint + - exhaustive + - exportloopref + - forbidigo + - forcetypeassert + - funlen + - gocheckcompilerdirectives + - gochecknoglobals + - gocognit + - goconst + - gocritic + - gofmt + - gofumpt + - goimports + - gomoddirectives + - goprintffuncname + - gosec + - gosimple + - govet + - importas + - ineffassign + - interfacebloat + - lll + - makezero + - misspell + - nakedret + - nestif + - nilerr + - nilnil + - noctx + - nolintlint + - nosprintfhostport + - paralleltest + - prealloc + - predeclared + - promlinter + - revive + - reassign + - rowserrcheck + - sqlclosecheck + - staticcheck + - stylecheck + - tenv + - tparallel + - typecheck + - unconvert + - unparam + - unused + - usestdlibvars + - wastedassign + - whitespace + fast: false diff --git a/api/api.go b/api/api.go index 1430056..e603525 100644 --- a/api/api.go +++ b/api/api.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "github.com/pkg/errors" + "github.com/google/uuid" "github.com/gorilla/mux" "github.com/sirupsen/logrus" @@ -69,6 +71,7 @@ func (a *API) handler(f func(*app.Context, http.ResponseWriter, *http.Request) e ctx := a.App.NewContext().WithRemoteAddress(a.addressForRequest(r)).WithTraceID(traceID) defer func() { + //nolint: forcetypeassert statusCode := w.(*statusCodeRecorder).StatusCode if statusCode == 0 { statusCode = 200 @@ -93,29 +96,15 @@ func (a *API) handler(f func(*app.Context, http.ResponseWriter, *http.Request) e w.Header().Set("Content-Type", "application/json") if err := f(ctx, w, r); err != nil { - if verr, ok := err.(*app.ValidationError); ok { - data, err := json.Marshal(verr) - if err == nil { - w.WriteHeader(http.StatusBadRequest) - _, err = w.Write(data) - } - - if err != nil { - ctx.Logger.Error(err) - http.Error(w, "internal server error", http.StatusInternalServerError) - } - } else if uerr, ok := err.(*app.UserError); ok { - data, err := json.Marshal(uerr) - if err == nil { - w.WriteHeader(uerr.StatusCode) - _, err = w.Write(data) - } - - if err != nil { - ctx.Logger.Error(err) - http.Error(w, "internal server error", http.StatusInternalServerError) - } - } else { + var verr *app.ValidationError + var uerr *app.UserError + + switch { + case errors.As(err, &verr): + handleValidationError(ctx, w, verr) + case errors.As(err, &uerr): + handleUserError(ctx, w, uerr) + default: ctx.Logger.Error(err) http.Error(w, "internal server error", http.StatusInternalServerError) } @@ -123,6 +112,32 @@ func (a *API) handler(f func(*app.Context, http.ResponseWriter, *http.Request) e }) } +func handleValidationError(ctx *app.Context, w http.ResponseWriter, verr *app.ValidationError) { + data, err := json.Marshal(verr) + if err == nil { + w.WriteHeader(http.StatusBadRequest) + _, err = w.Write(data) + } + + if err != nil { + ctx.Logger.Error(err) + http.Error(w, "internal server error", http.StatusInternalServerError) + } +} + +func handleUserError(ctx *app.Context, w http.ResponseWriter, uerr *app.UserError) { + data, err := json.Marshal(uerr) + if err == nil { + w.WriteHeader(uerr.StatusCode) + _, err = w.Write(data) + } + + if err != nil { + ctx.Logger.Error(err) + http.Error(w, "internal server error", http.StatusInternalServerError) + } +} + func (a *API) helloHandler(ctx *app.Context, w http.ResponseWriter, _ *http.Request) error { _, err := w.Write([]byte( fmt.Sprintf(`{"hello":"world","remote_address":%q,"trace_id":%q}`, diff --git a/api/place.go b/api/place.go index 6ca94aa..cd87c48 100644 --- a/api/place.go +++ b/api/place.go @@ -50,7 +50,7 @@ func (a *API) createPlace(ctx *app.Context, w http.ResponseWriter, r *http.Reque return err } - if err := json.Unmarshal(body, &input); err != nil { + if err = json.Unmarshal(body, &input); err != nil { return err } @@ -61,7 +61,7 @@ func (a *API) createPlace(ctx *app.Context, w http.ResponseWriter, r *http.Reque Longitude: input.Longitude, } - if err := ctx.CreatePlace(place); err != nil { + if err = ctx.CreatePlace(place); err != nil { return err } @@ -110,7 +110,7 @@ func (a *API) updatePlaceByID(ctx *app.Context, w http.ResponseWriter, r *http.R return err } - if err := json.Unmarshal(body, &input); err != nil { + if err = json.Unmarshal(body, &input); err != nil { return err } diff --git a/cmd/serve.go b/cmd/serve.go index b5b21cf..2aa09b7 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -36,6 +36,7 @@ func serveAPI(ctx context.Context, api *api.API) { done := make(chan struct{}) go func() { <-ctx.Done() + //nolint:contextcheck if err := s.Shutdown(context.Background()); err != nil { logrus.Error(err) }