@@ -70,6 +70,12 @@ - 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 + - inamedparam # peports interfaces with unnamed method parameters + - forcetypeassert # finds forced type assertions. + - exhaustruct # checks if all structure fields are initialized. + - err113 # forbids usage of dynamic errors + - contextcheck # check whether the function uses a non-inherited context + - ireturn # accept interfaces, return concrete types linters-settings: cyclop:@@ -122,6 +128,13 @@
nakedret: # the gods will judge me but I just don't like naked returns at all max-func-lines: 0 + + exhaustruct: + exclude: + - 'log/slog\.HandlerOptions' + - 'net/http\.Server' + + - 'github.com/golang-jwt/jwt/v5\.RegisteredClaims' issues: # Maximum count of issues with the same text.
@@ -1,3 +1,4 @@
+//nolint:err113 // all errors are shown to the user so it's ok for them to be dynamic package main import (
@@ -1,4 +1,4 @@
-package e2e +package e2e_test import ( "net/http"@@ -47,6 +47,7 @@ }{
{name: "all fiels empty", email: "", password: "", username: ""}, { name: "non valid email", + username: "testing", email: "email", password: "password", },@@ -214,6 +215,7 @@
unactivatedEmail := e.uuid() + "@test.com" e.insertUserIntoDB(e.uuid(), unactivatedEmail, password, false) + //exhaustruct:ignore tests := []struct { name string email string
@@ -1,4 +1,4 @@
-package e2e +package e2e_test import ( "net/http"@@ -28,14 +28,14 @@ assert func(*httptest.ResponseRecorder, apiv1NoteCreateRequest)
}{ { name: "empty request", - inp: apiv1NoteCreateRequest{}, + inp: apiv1NoteCreateRequest{}, //nolint:exhaustruct assert: func(r *httptest.ResponseRecorder, _ apiv1NoteCreateRequest) { e.Equal(r.Code, http.StatusBadRequest) }, }, { name: "content only", - inp: apiv1NoteCreateRequest{Content: e.uuid()}, + inp: apiv1NoteCreateRequest{Content: e.uuid()}, //nolint:exhaustruct assert: func(r *httptest.ResponseRecorder, _ apiv1NoteCreateRequest) { e.Equal(r.Code, http.StatusCreated)@@ -51,7 +51,7 @@ },
}, { name: "set slug", - inp: apiv1NoteCreateRequest{ + inp: apiv1NoteCreateRequest{ //nolint:exhaustruct Slug: e.uuid() + "fuker", Content: e.uuid(), },@@ -67,7 +67,7 @@ },
}, { name: "all possible fields", - inp: apiv1NoteCreateRequest{ + inp: apiv1NoteCreateRequest{ //nolint:exhaustruct Content: e.uuid(), BurnBeforeExpiration: true, ExpiresAt: time.Now().Add(time.Hour),@@ -96,9 +96,14 @@ }
func (e *AppTestSuite) TestNoteV1_Create_authorized() { uid, toks := e.createAndSingIn(e.uuid()+"@test.com", e.uuid(), "password") - httpResp := e.httpRequest(http.MethodPost, "/api/v1/note", e.jsonify(apiv1NoteCreateRequest{ - Content: "some random ass content for the test", - }), toks.AccessToken) + httpResp := e.httpRequest( + http.MethodPost, + "/api/v1/note", + e.jsonify(apiv1NoteCreateRequest{ //nolint:exhaustruct + Content: "some random ass content for the test", + }), + toks.AccessToken, + ) var body apiv1NoteCreateResponse e.readBodyAndUnjsonify(httpResp.Body, &body)@@ -118,9 +123,13 @@ }
func (e *AppTestSuite) TestNoteV1_Get() { content := e.uuid() - httpResp := e.httpRequest(http.MethodPost, "/api/v1/note", e.jsonify(apiv1NoteCreateRequest{ - Content: content, - })) + httpResp := e.httpRequest( + http.MethodPost, + "/api/v1/note", + e.jsonify(apiv1NoteCreateRequest{ //nolint:exhaustruct + Content: content, + }), + ) e.Equal(http.StatusCreated, httpResp.Code) var bodyCreated apiv1NoteCreateResponse
@@ -1,4 +1,4 @@
-package e2e +package e2e_test import ( "context"@@ -152,7 +152,7 @@ e.require.NoError(err)
// run migrations sdb := stdlib.OpenDBFromPool(db.Pool) - driver, err := pgx.WithInstance(sdb, &pgx.Config{}) + driver, err := pgx.WithInstance(sdb, &pgx.Config{}) //nolint:exhaustruct e.require.NoError(err) m, err := migrate.NewWithDatabaseInstance(@@ -175,7 +175,7 @@ })))
} func (e *AppTestSuite) getConfig() *config.Config { - return &config.Config{ + return &config.Config{ //nolint:exhaustruct AppEnv: "testing", ServerPort: "3000", PasswordSalt: "salty-password",
@@ -1,4 +1,4 @@
-package e2e +package e2e_test import ( "errors"@@ -63,7 +63,7 @@ var session models.Session
err = e.postgresDB.QueryRow(e.ctx, query, args...). Scan(&session.RefreshToken, &session.ExpiresAt) if errors.Is(err, pgx.ErrNoRows) { - return models.Session{} + return models.Session{} //nolint:exhaustruct } e.require.NoError(err)@@ -84,7 +84,7 @@ var u models.User
err = e.postgresDB.QueryRow(e.ctx, query, args...). Scan(&u.ID, &u.Username, &u.Activated, &u.Email, &u.Password) if errors.Is(err, pgx.ErrNoRows) { - return models.User{} + return models.User{} //nolint:exhaustruct } e.require.NoError(err)@@ -103,7 +103,7 @@ var note models.Note
err = e.postgresDB.QueryRow(e.ctx, query, args...). Scan(¬e.ID, ¬e.Content, ¬e.Slug, ¬e.BurnBeforeExpiration, ¬e.CreatedAt, ¬e.ExpiresAt) if errors.Is(err, pgx.ErrNoRows) { - return models.Note{} + return models.Note{} //nolint:exhaustruct } e.require.NoError(err)@@ -128,7 +128,7 @@
var na noteAuthorModel err = e.postgresDB.QueryRow(e.ctx, qeuery, args...).Scan(&na.noteID, &na.userID) if errors.Is(err, pgx.ErrNoRows) { - return noteAuthorModel{} + return noteAuthorModel{} //nolint:exhaustruct } e.require.NoError(err)
@@ -68,7 +68,7 @@
func mustParseDurationOrPanic(dur string) time.Duration { d, err := time.ParseDuration(dur) if err != nil { - panic(errors.Join(errors.New("cannot time.ParseDuration"), err)) + panic(errors.Join(errors.New("cannot time.ParseDuration"), err)) //nolint:err113 } return d
@@ -2,5 +2,5 @@ package hasher
type Hasher interface { // Hash takes a string as input and returns its hash - Hash(string) (string, error) + Hash(str string) (string, error) }
@@ -9,6 +9,8 @@
"github.com/golang-jwt/jwt/v5" ) +var ErrUnexpectedSigningMethod = errors.New("unexpected signing method") + type JWTTokenizer interface { // AccessToken generates a new access token with the given payload AccessToken(pl Payload) (string, error)@@ -58,7 +60,7 @@ func (j *JWTUtil) Parse(token string) (Payload, error) {
var claims jwt.RegisteredClaims _, err := jwt.ParseWithClaims(token, &claims, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, errors.New("unexpected signing method") + return nil, ErrUnexpectedSigningMethod } return []byte(j.signingKey), nil })
@@ -20,13 +20,13 @@ assert.Equal(t, "content", m.emails[email])
} func TestMailer_GetLastSentEmailToEmail(t *testing.T) { + email := "test@mail.com" + content := "content" + m := NewTestMailer() assert.Empty(t, m.emails) - email := "test@mail.com" - content := "content" - err := m.Send(context.TODO(), email, "", content) - require.NoError(t, err) + m.emails[email] = content c := m.GetLastSentEmailToEmail(email) assert.Equal(t, content, c)
@@ -17,15 +17,16 @@ }{
// NOTE: there no need to test if note is expired since it tested in IsExpired test { name: "ok", - note: Note{ + note: Note{ //nolint:exhaustruct Content: "some wired ass content", ExpiresAt: time.Now().Add(time.Hour), }, willError: false, + error: nil, }, { name: "content missing", - note: Note{Content: ""}, + note: Note{Content: ""}, //nolint:exhaustruct willError: true, error: ErrNoteContentIsEmpty, },@@ -51,17 +52,17 @@ expected bool
}{ { name: "expired", - note: Note{ExpiresAt: time.Now().Add(-time.Hour)}, + note: Note{ExpiresAt: time.Now().Add(-time.Hour)}, //nolint:exhaustruct expected: true, }, { name: "not expired", - note: Note{ExpiresAt: time.Now().Add(time.Hour)}, + note: Note{ExpiresAt: time.Now().Add(time.Hour)}, //nolint:exhaustruct expected: false, }, { name: "zero expiration", - note: Note{ExpiresAt: time.Time{}}, + note: Note{ExpiresAt: time.Time{}}, //nolint:exhaustruct expected: false, }, }@@ -81,7 +82,7 @@ expected bool
}{ { name: "should be burnt", - note: Note{ + note: Note{ //nolint:exhaustruct BurnBeforeExpiration: true, ExpiresAt: time.Now().Add(time.Hour), },@@ -89,7 +90,7 @@ expected: true,
}, { name: "could not be burnt, no expiration time", - note: Note{ + note: Note{ //nolint:exhaustruct BurnBeforeExpiration: true, ExpiresAt: time.Time{}, },@@ -97,7 +98,7 @@ expected: false,
}, { name: "could not be burnt, burn when expiration and burn is false", - note: Note{ + note: Note{ //nolint:exhaustruct BurnBeforeExpiration: false, ExpiresAt: time.Time{}, },
@@ -34,15 +34,15 @@ func (u User) Validate() error {
// NOTE: there's probably a better way to validate emails _, err := mail.ParseAddress(u.Email) if err != nil { - return errors.New("user: invalid email") + return errors.New("user: invalid email") //nolint:err113 } if len(u.Password) < 6 { - return errors.New("user: password too short, minimum 6 chars") + return errors.New("user: password too short, minimum 6 chars") //nolint:err113 } if len(u.Username) == 0 { - return errors.New("user: username is required") + return errors.New("user: username is required") //nolint:err113 } return nil
@@ -54,7 +54,7 @@ }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := User{ + err := User{ //nolint:exhaustruct Username: tt.username, Email: tt.email, Password: tt.password,
@@ -23,7 +23,7 @@ type NoteSrv struct {
noterepo noterepo.NoteStorer } -func New(noterepo noterepo.NoteStorer) NoteServicer { +func New(noterepo noterepo.NoteStorer) *NoteSrv { return &NoteSrv{ noterepo: noterepo, }@@ -62,7 +62,7 @@ return dtos.NoteDTO{}, err
} // TODO: there should be a better way to do it - m := models.Note{ + m := models.Note{ //nolint:exhaustruct ExpiresAt: note.ExpiresAt, BurnBeforeExpiration: note.BurnBeforeExpiration, }
@@ -55,7 +55,7 @@ hasher hasher.Hasher,
jwtTokenizer jwtutil.JWTTokenizer, mailer mailer.Mailer, refreshTokenTTL, verificationTokenTTL time.Duration, -) UserServicer { +) *UserSrv { return &UserSrv{ userstore: userstore, sessionstore: sessionstore,@@ -93,7 +93,7 @@
// TODO: handle the error that might be returned // i dont think that tehre's need to handle the error, just log it bgCtx, bgCancel := context.WithTimeout(context.Background(), 10*time.Second) - go u.sendVerificationEmail(bgCtx, bgCancel, inp.Email, vtok) //nolint:errcheck + go u.sendVerificationEmail(bgCtx, bgCancel, inp.Email, vtok) //nolint:errcheck,contextcheck return uid, nil }@@ -211,7 +211,7 @@ return err
} bgCtx, bgCancel := context.WithTimeout(context.Background(), 10*time.Second) - go u.sendVerificationEmail(bgCtx, bgCancel, inp.Email, token) //nolint:errcheck + go u.sendVerificationEmail(bgCtx, bgCancel, inp.Email, token) //nolint:errcheck,contextcheck return nil }
@@ -26,7 +26,7 @@ type NoteRepo struct {
db *psqlutil.DB } -func New(db *psqlutil.DB) NoteStorer { +func New(db *psqlutil.DB) *NoteRepo { return &NoteRepo{db} }
@@ -25,7 +25,7 @@ type SessionRepo struct {
db *psqlutil.DB } -func New(db *psqlutil.DB) SessionStorer { +func New(db *psqlutil.DB) *SessionRepo { return &SessionRepo{ db: db, }
@@ -22,7 +22,7 @@ newError(c, http.StatusBadRequest, "invalid request")
return } - user := models.User{ + user := models.User{ //nolint:exhaustruct Username: req.Username, Email: req.Email, Password: req.Password,
@@ -82,7 +82,13 @@ userID, exists := c.Get(userIDCtxKey)
if !exists { return uuid.Nil } - return userID.(uuid.UUID) + + uid, ok := userID.(uuid.UUID) + if !ok { + return uuid.Nil + } + + return uid } func (a *APIV1) validateAuthorizedUser(ctx context.Context, accessToken string) (uuid.UUID, error) {
@@ -27,7 +27,7 @@ newError(c, http.StatusBadRequest, "invalid request")
return } - note := models.Note{ + note := models.Note{ //nolint:exhaustruct Content: req.Content, Slug: req.Slug, BurnBeforeExpiration: req.BurnBeforeExpiration,